From dbd04be79460cae0f1a5aab98f116ad044348ac0 Mon Sep 17 00:00:00 2001
From: tohosaku <tohosaku@users.osdn.me>
Date: Wed, 17 Jan 2024 01:17:15 +0000
Subject: [PATCH] Add daialog controller and dialog-dispatcher controller and
 helper

---
 app/assets/images/icons.svg                   |  4 +
 app/assets/stylesheets/application.css        | 10 +-
 app/helpers/application_helper.rb             | 24 +++++
 .../controllers/dialog_controller.js          | 95 +++++++++++++++++++
 .../dialog_dispatcher_controller.js           | 29 ++++++
 app/views/common/_dialog.html.erb             | 12 +++
 app/views/email_addresses/index.js.erb        |  4 +-
 app/views/groups/_new_users_modal.html.erb    |  4 +-
 app/views/groups/add_users.js.erb             |  2 +-
 app/views/groups/new_users.js.erb             |  4 +-
 .../issue_categories/_new_modal.html.erb      |  6 +-
 app/views/issue_categories/create.js.erb      |  2 +-
 app/views/issue_categories/new.js.erb         |  4 +-
 app/views/issues/_attributes.html.erb         |  4 +-
 app/views/issues/_form.html.erb               |  5 +-
 .../issues/_issue_status_description.html.erb | 13 ++-
 .../issues/_trackers_description.html.erb     | 13 ++-
 app/views/issues/index.html.erb               | 12 +--
 app/views/layouts/base.html.erb               |  6 +-
 app/views/members/_new_modal.html.erb         |  4 +-
 app/views/members/create.js.erb               |  2 +-
 app/views/members/new.js.erb                  |  4 +-
 .../principal_memberships/_new_modal.html.erb |  6 +-
 app/views/principal_memberships/create.js.erb |  2 +-
 app/views/principal_memberships/new.js.erb    |  2 +-
 app/views/projects/_list.html.erb             |  6 +-
 app/views/projects/index.html.erb             |  2 +-
 app/views/reports/_details.html.erb           | 12 +--
 app/views/roles/permissions.html.erb          | 12 +--
 app/views/sudo_mode/_new_modal.html.erb       |  5 +-
 app/views/sudo_mode/new.js.erb                |  6 +-
 app/views/timelog/index.html.erb              | 13 ++-
 app/views/timelog/report.html.erb             | 13 +--
 app/views/users/index.html.erb                | 11 +--
 app/views/versions/_new_modal.html.erb        |  6 +-
 app/views/versions/create.js.erb              |  2 +-
 app/views/versions/new.js.erb                 |  4 +-
 app/views/watchers/_new.html.erb              | 14 +--
 app/views/watchers/new.js.erb                 | 15 +--
 app/views/wiki/_new_modal.html.erb            |  4 +-
 app/views/wiki/new.js.erb                     |  4 +-
 config/icon_source.yml                        |  4 +-
 config/locales/ar.yml                         |  1 +
 config/locales/az.yml                         |  1 +
 config/locales/bg.yml                         |  1 +
 config/locales/bs.yml                         |  1 +
 config/locales/ca.yml                         |  1 +
 config/locales/cs.yml                         |  1 +
 config/locales/da.yml                         |  1 +
 config/locales/de.yml                         |  1 +
 config/locales/el.yml                         |  1 +
 config/locales/en-GB.yml                      |  1 +
 config/locales/en.yml                         |  1 +
 config/locales/es-PA.yml                      |  2 +
 config/locales/es.yml                         |  1 +
 config/locales/et.yml                         |  1 +
 config/locales/eu.yml                         |  1 +
 config/locales/fa.yml                         |  1 +
 config/locales/fi.yml                         |  1 +
 config/locales/fr.yml                         |  1 +
 config/locales/gl.yml                         |  1 +
 config/locales/he.yml                         |  1 +
 config/locales/hr.yml                         |  1 +
 config/locales/hu.yml                         |  1 +
 config/locales/id.yml                         |  1 +
 config/locales/it.yml                         |  1 +
 config/locales/ja.yml                         |  1 +
 config/locales/ko.yml                         |  1 +
 config/locales/lt.yml                         |  1 +
 config/locales/lv.yml                         |  1 +
 config/locales/mk.yml                         |  1 +
 config/locales/mn.yml                         |  1 +
 config/locales/nl.yml                         |  1 +
 config/locales/no.yml                         |  1 +
 config/locales/pl.yml                         |  1 +
 config/locales/pt-BR.yml                      |  1 +
 config/locales/pt.yml                         |  1 +
 config/locales/ro.yml                         |  1 +
 config/locales/ru.yml                         |  1 +
 config/locales/sk.yml                         |  1 +
 config/locales/sl.yml                         |  1 +
 config/locales/sq.yml                         |  1 +
 config/locales/sr-YU.yml                      |  1 +
 config/locales/sr.yml                         |  1 +
 config/locales/sv.yml                         |  1 +
 config/locales/ta-IN.yml                      |  1 +
 config/locales/th.yml                         |  1 +
 config/locales/tr.yml                         |  1 +
 config/locales/uk.yml                         |  1 +
 config/locales/vi.yml                         |  1 +
 config/locales/zh-TW.yml                      |  1 +
 config/locales/zh.yml                         |  1 +
 test/functional/issues_controller_test.rb     |  4 +-
 93 files changed, 328 insertions(+), 133 deletions(-)
 create mode 100644 app/javascript/controllers/dialog_controller.js
 create mode 100644 app/javascript/controllers/dialog_dispatcher_controller.js
 create mode 100644 app/views/common/_dialog.html.erb

diff --git a/app/assets/images/icons.svg b/app/assets/images/icons.svg
index 55e3a042d0..86a5c040ab 100644
--- a/app/assets/images/icons.svg
+++ b/app/assets/images/icons.svg
@@ -122,6 +122,10 @@
       <path d="M3 5a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v14a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2v-14z"/>
       <path d="M9 9l6 6m0 -6l-6 6"/>
     </symbol>
+    <symbol viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" id="icon--close-dialog">
+      <path d="M18 6l-12 12" />
+      <path d="M6 6l12 12" />
+    </symbol>
     <symbol viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" id="icon--comment">
       <path d="M8 9h8"/>
       <path d="M8 13h6"/>
diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css
index 1918c5b2b4..b1cb6d4ea7 100644
--- a/app/assets/stylesheets/application.css
+++ b/app/assets/stylesheets/application.css
@@ -207,10 +207,15 @@ pre, code {font-family: Consolas, Menlo, "Liberation Mono", Courier, monospace;}
 #login-form a.lost_password {float:right; font-weight:normal;}
 #login-form h3 {text-align: center;}
 
-div.modal { border-radius:5px; background:#fff; z-index:50; padding:4px;}
-div.modal h3.title {display:none;}
+div.modal { position:absolute; border-radius:5px; background:#fff; padding:7px 14px; border: 1px solid #c5c5c5; color: #333; font-family: Arial,Helvetica,sans-serif; font-size: 1em; outline: 0; z-index: 100}
 div.modal p.buttons {margin-bottom:0;}
 div.modal .box p {margin: 0.3em 0;}
+div.modal .handle {display:flex; align-items: center;height:1.5em; cursor: move; padding: .5em; border-radius: 3px; border: 1px solid #ddd; background: #e9e9e9; color: #333; font-weight: bold; touch-action: none}
+div.modal .handle .title {margin: .1em 0; white-space: nowrap; width: 90%; overflow: hidden; text-overflow: ellipsis}
+div.modal .handle button {margin: 0 0 0 auto;padding:0;width: 20px; height: 20px;fill: #454545; text-decoration: none; border: 1px solid #c5c5c5; background: #f6f6f6; font-weight: normal;box-sizing: border-box; white-space: nowrap; line-height: normal; cursor: pointer; text-align: center; user-select: none; overflow: visible;}
+div.modal .handle button svg { stroke-width: 4; stroke: #999 }
+div.modal .handle button .icon {position: absolute; top: 50%; left: 50%; margin-top:-8px; margin-left: -8px; width: 16px; height: 16px; display: inline-block; vertical-align: middle; overflow: hidden; background-repeat: no-repeat}
+.modal-backdrop-open { position: fixed; inset: 0; background: rgba(0,0,0,0.7); display: flex; align-items: center; justify-content: center; }
 
 .clear:after{ content: "."; display: block; height: 0; clear: both; visibility: hidden; }
 
@@ -729,7 +734,6 @@ div#sticky-issue-header .subject {
 #issue_tree .issue > td.assigned_to, #relations .issue > td.assigned_to {
   white-space: nowrap;
 }
-#trackers_description, #issue_statuses_description {display:none;}
 #trackers_description dt, #issue_statuses_description dt {font-weight: bold; text-decoration: underline;}
 #trackers_description dd, #issue_statuses_description dd {margin: 0; padding: 0 0 1em 0;}
 
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 847fb9fddb..9b431c9e61 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -1948,6 +1948,17 @@ module ApplicationHelper
     )
   end
 
+  def remote_dialog(width: nil, modal: true, title: nil, partial: nil, locals: {}, &block)
+    data = {
+      controller: 'dialog',
+      width: width,
+      dialog_modal_value: modal,
+      dialog_dispatcher_target: 'show'
+    }
+    block = -> { render partial: partial, locals: locals } if partial
+    render layout: 'common/dialog', locals: { id: nil, title: title, data: data }, formats: [:html], &block
+  end
+
   private
 
   def wiki_helper
@@ -1968,4 +1979,17 @@ module ApplicationHelper
       wiki_pages: auto_complete_wiki_pages_path(project_id: project, q: ''),
     }
   end
+
+  def modal_dialog(id, title, data: nil, &block)
+    content_for(:modal) { render 'common/dialog', { id: id, title: title, data: data }, &block }
+  end
+
+  def dialog_dispatcher(element_id, width)
+    {
+      controller: 'dialog-dispatcher',
+      action: 'dialog-dispatcher#show',
+      dialog_dispatcher_dialog_outlet: "##{element_id}",
+      dialog_dispatcher_width_param: width
+    }
+  end
 end
diff --git a/app/javascript/controllers/dialog_controller.js b/app/javascript/controllers/dialog_controller.js
new file mode 100644
index 0000000000..c536062796
--- /dev/null
+++ b/app/javascript/controllers/dialog_controller.js
@@ -0,0 +1,95 @@
+/**
+ * Redmine - project management software
+ * Copyright (C) 2006-  Jean-Philippe Lang
+ * This code is released under the GNU General Public License.
+ */
+
+import { Controller } from "@hotwired/stimulus"
+
+// Connects to data-controller="dialog"
+export default class extends Controller {
+
+  static targets = [ 'handler', 'cancel' ]
+  static values = {
+    modal: { type: Boolean, default: true },
+    wrapperId: { type: String, default: 'wrapper' }
+  }
+
+  connect() {
+    this.dragging = null;
+    this.backdrop = null;
+    const rect = this.element.getBoundingClientRect();
+    this._pos = {x: rect.left, y: rect.top}
+    this.element[`${this.identifier}_controller`] = this;
+    this.wrapper = document.getElementById(this.wrapperIdValue)
+  }
+
+  start(e) {
+    if (this.cancelTarget.contains(e.target)) return;
+    if (e.button !== 0) return; // left button only
+
+    const client = this.eventToCoordinates(e);
+    this.dragging = {dx: this.pos.x - client.x, dy: this.pos.y - client.y};
+    this.handlerTarget.classList.add('dragging');
+    this.handlerTarget.setPointerCapture(e.pointerId);
+    this.handlerTarget.style.userSelect = 'none'; // if there's text
+    this.handlerTarget.style.webkitUserSelect = 'none'; // safari
+  }
+
+  end(e) {
+    this.dragging = null;
+    this.handlerTarget.classList.remove('dragging');
+    this.handlerTarget.style.userSelect = ''; // if there's text
+    this.handlerTarget.style.webkitUserSelect = ''; // safari
+  }
+
+  move(e) {
+    if (!this.dragging) return;
+
+    const client = this.eventToCoordinates(e);
+    this.pos = {x: client.x + this.dragging.dx, y: client.y + this.dragging.dy};
+  }
+
+  eventToCoordinates(e) {
+    return {x: e.clientX, y: e.clientY}
+  }
+
+  get pos() { return this._pos }
+
+  set pos(p) {
+    this._pos = p;
+    this.element.style.transform = `translate(${this._pos.x}px,${this._pos.y}px)`;
+  }
+
+  show({width = undefined, backdrop = 'modal-backdrop'}) {
+    this.element.style.display = '';
+    if (width) {
+      this.element.style.width = width;
+    }
+
+    if (this.modalValue) {
+      this.backdrop = document.getElementById(backdrop)
+      this.backdrop.classList.add('modal-backdrop-open')
+      this.wrapper.inert = true
+    }
+  }
+
+  hide(e) {
+    this.hideDialog()
+  }
+
+  cancel(e) {
+    e.preventDefault();
+    this.hideDialog()
+  }
+
+  hideDialog() {
+    this.element.style.display = 'none'
+    if (this.backdrop !== null) {
+      this.backdrop.classList.remove('modal-backdrop-open')
+    }
+    if (this.wrapper !== null) {
+      this.wrapper.inert = false
+    }
+  }
+}
diff --git a/app/javascript/controllers/dialog_dispatcher_controller.js b/app/javascript/controllers/dialog_dispatcher_controller.js
new file mode 100644
index 0000000000..0980c45c4b
--- /dev/null
+++ b/app/javascript/controllers/dialog_dispatcher_controller.js
@@ -0,0 +1,29 @@
+/**
+ * Redmine - project management software
+ * Copyright (C) 2006-  Jean-Philippe Lang
+ * This code is released under the GNU General Public License.
+ */
+
+import { Controller } from "@hotwired/stimulus"
+
+// Connects to data-controller="dialog-dispatcher"
+export default class extends Controller {
+  static targets = [ 'show', 'hide' ];
+  static outlets = [ 'dialog' ];
+
+  show(e) {
+    e.preventDefault();
+    this.dialog = this.dialogOutlet
+    this.dialog.show({width: e.params.width})
+  }
+
+  showTargetConnected(dialog) {
+    this.dialog = dialog.dialog_controller
+    this.dialog.show({width: dialog.dataset.width, backdrop: this.element.id})
+  }
+
+  hideTargetConnected(element) {
+    this.dialog.hide()
+    element.remove()
+  }
+}
diff --git a/app/views/common/_dialog.html.erb b/app/views/common/_dialog.html.erb
new file mode 100644
index 0000000000..fe3efa871b
--- /dev/null
+++ b/app/views/common/_dialog.html.erb
@@ -0,0 +1,12 @@
+<%= tag.div style: 'display:none', class: 'modal', id: id, data: (data || { controller: 'dialog' })  do %>
+  <div class="handle" data-action="pointerdown->dialog#start pointerup->dialog#end pointercancel->dialog#end pointermove->dialog#move touchstart->dialog#noop dragstart->dialog#noop" data-dialog-target="handler">
+    <span class="title"><%= title %></span>
+    <%= tag.button type: "button", title: l(:button_close_dialog), data: {action: "dialog#cancel", dialog_target: 'cancel'} do %>
+      <%= sprite_icon 'close-dialog'%>
+    <% end %>
+  </div>
+
+  <div>
+    <%= yield %>
+  </div>
+<% end %>
diff --git a/app/views/email_addresses/index.js.erb b/app/views/email_addresses/index.js.erb
index 2a7147f362..d900172c65 100644
--- a/app/views/email_addresses/index.js.erb
+++ b/app/views/email_addresses/index.js.erb
@@ -1,3 +1,3 @@
-$('#ajax-modal').html('<%= escape_javascript(render :partial => 'email_addresses/index') %>');
-showModal('ajax-modal', '600px', '<%= escape_javascript l(:label_email_address_plural) %>');
+<% html = remote_dialog(width: '600px', title: l(:label_email_address_plural), partial: 'email_addresses/index') %>
+$('#ajax-modal').html('<%= escape_javascript(html) %>');
 $('#email_address_address').focus();
diff --git a/app/views/groups/_new_users_modal.html.erb b/app/views/groups/_new_users_modal.html.erb
index c68d3f0602..9461cd02d4 100644
--- a/app/views/groups/_new_users_modal.html.erb
+++ b/app/views/groups/_new_users_modal.html.erb
@@ -1,9 +1,7 @@
-<h3 class="title"><%= l(:label_user_new) %></h3>
-
 <%= form_for(@group, :url => group_users_path(@group), :remote => true, :method => :post) do |f| %>
   <%= render :partial => 'new_users_form' %>
   <p class="buttons">
     <%= submit_tag l(:button_add) %>
-    <%= link_to_function l(:button_cancel), "hideModal(this);" %>
+    <%= link_to l(:button_cancel), '#', data: { action: 'dialog#cancel' } %>
   </p>
 <% end %>
diff --git a/app/views/groups/add_users.js.erb b/app/views/groups/add_users.js.erb
index e4af93a04e..504d4a3ed5 100644
--- a/app/views/groups/add_users.js.erb
+++ b/app/views/groups/add_users.js.erb
@@ -1,4 +1,4 @@
-hideModal();
+$('#ajax-modal').append('<template data-dialog-dispatcher-target="hide"></template>')
 $('#tab-content-users').html('<%= escape_javascript(render :partial => 'groups/users') %>');
 <% @users.each do |user| %>
   $('#user-<%= user.id %>').effect("highlight");
diff --git a/app/views/groups/new_users.js.erb b/app/views/groups/new_users.js.erb
index 1775abe317..10886a21b9 100644
--- a/app/views/groups/new_users.js.erb
+++ b/app/views/groups/new_users.js.erb
@@ -1,2 +1,2 @@
-$('#ajax-modal').html('<%= escape_javascript(render :partial => 'groups/new_users_modal') %>');
-showModal('ajax-modal', '700px');
+<% html = remote_dialog(width: '700px', title: l(:label_user_new), partial: 'groups/new_users_modal') %>
+$('#ajax-modal').html('<%= escape_javascript(html) %>');
diff --git a/app/views/issue_categories/_new_modal.html.erb b/app/views/issue_categories/_new_modal.html.erb
index 9ab1595b40..7164b224cf 100644
--- a/app/views/issue_categories/_new_modal.html.erb
+++ b/app/views/issue_categories/_new_modal.html.erb
@@ -1,9 +1,7 @@
-<h3 class="title"><%=l(:label_issue_category_new)%></h3>
-
 <%= labelled_form_for @category, :as => 'issue_category', :url => project_issue_categories_path(@project), :remote => true do |f| %>
 <%= render :partial => 'issue_categories/form', :locals => { :f => f } %>
   <p class="buttons">
-    <%= submit_tag l(:button_create), :name => nil %>
-    <%= link_to_function l(:button_cancel), "hideModal(this);" %>
+    <%= submit_tag l(:button_create), name: nil %>
+    <%= link_to l(:button_cancel), '#', data: { action: 'dialog#cancel' } %>
   </p>
 <% end %>
diff --git a/app/views/issue_categories/create.js.erb b/app/views/issue_categories/create.js.erb
index c4253b98e5..e782420606 100644
--- a/app/views/issue_categories/create.js.erb
+++ b/app/views/issue_categories/create.js.erb
@@ -1,3 +1,3 @@
-hideModal();
+$('#ajax-modal').append('<template data-dialog-dispatcher-target="hide"></template>')
 <% select = content_tag('select', content_tag('option') + options_from_collection_for_select(@project.issue_categories, 'id', 'name', @category.id), :id => 'issue_category_id', :name => 'issue[category_id]') %>
 $('#issue_category_id').replaceWith('<%= escape_javascript(select) %>');
diff --git a/app/views/issue_categories/new.js.erb b/app/views/issue_categories/new.js.erb
index 0d623495e4..00c9d464f8 100644
--- a/app/views/issue_categories/new.js.erb
+++ b/app/views/issue_categories/new.js.erb
@@ -1,2 +1,2 @@
-$('#ajax-modal').html('<%= escape_javascript(render :partial => 'issue_categories/new_modal') %>');
-showModal('ajax-modal', '600px');
+<% html = remote_dialog(width: '600px', title: l(:label_issue_category_new), partial: 'issue_categories/new_modal', locals: {watchables: @watchables, users: @users}) %>
+$('#ajax-modal').html('<%= escape_javascript(html) %>');
diff --git a/app/views/issues/_attributes.html.erb b/app/views/issues/_attributes.html.erb
index 9707674601..d5169b0525 100644
--- a/app/views/issues/_attributes.html.erb
+++ b/app/views/issues/_attributes.html.erb
@@ -7,12 +7,12 @@
   <%= f.select :status_id, (@allowed_statuses.collect {|p| [p.name, p.id]}), {:required => true},
     :onchange => "updateIssueFrom('#{escape_javascript(update_issue_form_path(@project, @issue))}', this)",
     :title => @issue.status.description %>
-  <%= content_tag 'a', sprite_icon('help', l(:label_open_issue_statuses_description)), :class => 'icon-only icon-help', :title => l(:label_open_issue_statuses_description), :onclick => "showModal('issue_statuses_description', '500px'); return false;", :href => '#' if @allowed_statuses.any? {|s| s.description.present? } %>
+  <%= link_to sprite_icon('help', l(:label_open_issue_statuses_description)), '#', class: 'icon-only icon-help', title: l(:label_open_issue_statuses_description), data: dialog_dispatcher('issue_statuses_description', '500px') if @allowed_statuses.any? {|s| s.description.present? } %>
   <% if @issue.transition_warning %>
     <span class="icon-only icon-warning" title="<%= @issue.transition_warning %>"><%= sprite_icon('warning', l(:notice_issue_not_closable_by_open_tasks)) %></span>
   <% end %>
+  <%= render partial: 'issues/issue_status_description', locals: { issue_statuses: @allowed_statuses } %>
 </p>
-<%= render partial: 'issues/issue_status_description', locals: { issue_statuses: @allowed_statuses } %>
 <%= hidden_field_tag 'was_default_status', @issue.status_id, :id => nil if @issue.status == @issue.default_status %>
 <% else %>
 <p><label><%= l(:field_status) %></label> <%= @issue.status %></p>
diff --git a/app/views/issues/_form.html.erb b/app/views/issues/_form.html.erb
index c1d1302360..a67a79465a 100644
--- a/app/views/issues/_form.html.erb
+++ b/app/views/issues/_form.html.erb
@@ -20,9 +20,10 @@
   <%= f.select :tracker_id, trackers_options_for_select(@issue), {:required => true},
                :onchange => "updateIssueFrom('#{escape_javascript update_issue_form_path(@project, @issue)}', this)",
                :title => @issue.tracker.description %>
-  <%= content_tag 'a', sprite_icon('help', l(:label_open_trackers_description)), :class => 'icon-only icon-help', :title => l(:label_open_trackers_description), :onclick => "showModal('trackers_description', '500px'); return false;", :href => '#' if trackers_for_select(@issue).any? {|t| t.description.present? } %>
-</p>
+  <%= link_to sprite_icon('help', l(:label_open_trackers_description)),
+               '#', class: 'icon-only icon-help', title: l(:label_open_trackers_description), data: dialog_dispatcher('trackers_description', '500px') if trackers_for_select(@issue).any? {|t| t.description.present? } %>
   <%= render partial: 'issues/trackers_description', locals: {trackers: trackers_for_select(@issue)} %>
+</p>
 <% end %>
 
 <% if @issue.safe_attribute? 'subject' %>
diff --git a/app/views/issues/_issue_status_description.html.erb b/app/views/issues/_issue_status_description.html.erb
index 4b67d3d82a..0a4926ca0c 100644
--- a/app/views/issues/_issue_status_description.html.erb
+++ b/app/views/issues/_issue_status_description.html.erb
@@ -1,15 +1,14 @@
 <% if issue_statuses.any? {|s| s.description.present? } %>
-  <div class="modal" id="issue_statuses_description">
-    <h3 class="title"><%= l(:label_issue_statuses_description) %></h3>
-      <dl>
+  <%= modal_dialog "issue_statuses_description", l(:label_issue_statuses_description) do %>
+    <dl>
       <% issue_statuses.each do |issue_status| %>
         <% if issue_status.description.present? %>
-          <dt><%= content_tag 'a', issue_status.name, :onclick => "selectIssueStatus('#{issue_status.id}'); return false;", :href => '#', :title => l(:text_select_apply_issue_status) %></dt>
+          <dt><%= link_to issue_status.name, '#', onclick: "selectIssueStatus('#{issue_status.id}'); return false;", title: l(:text_select_apply_issue_status) %></dt>
           <dd><%= issue_status.description %></dd>
         <% end %>
       <% end %>
-      </dl>
-  </div>
+    </dl>
+  <% end %>
 <% end %>
 <%= javascript_tag do %>
   function selectIssueStatus(id) {
@@ -17,6 +16,6 @@
     target.attr("selected", false);
     target.find('option[value="' + id + '"]').prop('selected', true);
     target.trigger('change');
-    hideModal('#issue_statuses_description h3');
+    $('#issue_statuses_description').get(0).dialog_controller.hide();
   }
 <% end %>
diff --git a/app/views/issues/_trackers_description.html.erb b/app/views/issues/_trackers_description.html.erb
index a021141238..9b0c528c75 100644
--- a/app/views/issues/_trackers_description.html.erb
+++ b/app/views/issues/_trackers_description.html.erb
@@ -1,15 +1,14 @@
 <% if trackers.any? {|t| t.description.present? } %>
-  <div class="modal" id="trackers_description">
-    <h3 class="title"><%= l(:label_trackers_description) %></h3>
-      <dl>
+  <%= modal_dialog "trackers_description", l(:label_trackers_description), data: { controller: 'dialog' } do %>
+    <dl>
       <% trackers.each do |tracker| %>
         <% if tracker.description.present? %>
-          <dt><%= content_tag 'a', tracker.name, :onclick => "selectTracker('#{tracker.id}'); return false;", :href => '#', :title => l(:text_select_apply_tracker) %></dt>
+          <dt><%= link_to tracker.name, '#', onclick: "selectTracker('#{tracker.id}'); return false;", title: l(:text_select_apply_tracker) %></dt>
           <dd><%= tracker.description %></dd>
         <% end %>
       <% end %>
-      </dl>
-  </div>
+    </dl>
+  <% end %>
 <% end %>
 <%= javascript_tag do %>
   function selectTracker(id) {
@@ -17,6 +16,6 @@
     target.attr("selected", false);
     target.find('option[value="' + id + '"]').prop('selected', true);
     target.trigger('change');
-    hideModal('#trackers_description h3');
+    $('#trackers_description').get(0).dialog_controller.hide();
   }
 <% end %>
diff --git a/app/views/issues/index.html.erb b/app/views/issues/index.html.erb
index 70f0b740a0..dcbd854987 100644
--- a/app/views/issues/index.html.erb
+++ b/app/views/issues/index.html.erb
@@ -35,13 +35,12 @@
 <% end %>
 
 <% other_formats_links do |f| %>
-  <%= f.link_to_with_query_parameters 'CSV', {}, :onclick => "showModal('csv-export-options', '350px'); return false;" %>
+  <%= f.link_to_with_query_parameters 'CSV', {}, data: dialog_dispatcher('csv-export-options', '350px') %>
   <%= f.link_to_with_query_parameters 'PDF' %>
   <%= f.link_to_with_query_parameters 'Atom', :key => User.current.atom_key %>
 <% end %>
 
-<div id="csv-export-options" style="display:none;">
-  <h3 class="title"><%= l(:label_export_options, :export_format => 'CSV') %></h3>
+<% modal_dialog 'csv-export-options', l(:label_export_options, :export_format => 'CSV') do %>
   <%= form_tag(_project_issues_path(@project, :format => 'csv'), :method => :get, :id => 'csv-export-form') do %>
   <%= query_as_hidden_field_tags(@query) %>
   <%= hidden_field_tag('query_name', @query.name) %>
@@ -67,12 +66,11 @@
   </p>
   <% end %>
   <p class="buttons">
-    <%= submit_tag l(:button_export), :name => nil, :onclick => "hideModal(this);", :data => { :disable_with => false } %>
-    <%= link_to_function l(:button_cancel), "hideModal(this);" %>
+    <%= submit_tag l(:button_export), name: nil, data: { disable_with: false, action: 'dialog#hide' } %>
+    <%= link_to l(:button_cancel), '#', data: { action: 'dialog#cancel' } %>
   </p>
   <% end %>
-</div>
-
+<% end %>
 <% end %>
 <%= call_hook(:view_issues_index_bottom, { :issues => @issues, :project => @project, :query => @query }) %>
 
diff --git a/app/views/layouts/base.html.erb b/app/views/layouts/base.html.erb
index e982f534ce..55c2c9eae7 100644
--- a/app/views/layouts/base.html.erb
+++ b/app/views/layouts/base.html.erb
@@ -130,10 +130,14 @@
 </div>
 
 <div id="ajax-indicator" style="display:none;"><span><%= l(:label_loading) %></span></div>
-<div id="ajax-modal" style="display:none;"></div>
 <div id="icon-copy-source" style="display: none;"><%= sprite_icon('') %></div>
 
 </div>
 <%= call_hook :view_layouts_base_body_bottom %>
+<div id="modal-backdrop">
+  <%= yield :modal %>
+</div>
+<div id="ajax-modal" data-controller="dialog-dispatcher"></div>
+<div id="sudo-modal" data-controller="dialog-dispatcher"></div>
 </body>
 </html>
diff --git a/app/views/members/_new_modal.html.erb b/app/views/members/_new_modal.html.erb
index 3cb3360f5e..a7993d0f00 100644
--- a/app/views/members/_new_modal.html.erb
+++ b/app/views/members/_new_modal.html.erb
@@ -1,9 +1,7 @@
-<h3 class="title"><%= l(:label_member_new) %></h3>
-
 <%= form_for @member, :as => :membership, :url => project_memberships_path(@project), :remote => true, :method => :post do |f| %>
   <%= render :partial => 'new_form' %>
   <p class="buttons">
     <%= submit_tag l(:button_add), :id => 'member-add-submit' %>
-    <%= link_to_function l(:button_cancel), "hideModal(this);" %>
+    <%= link_to l(:button_cancel), '#', data: { action: 'dialog#cancel' } %>
   </p>
 <% end %>
diff --git a/app/views/members/create.js.erb b/app/views/members/create.js.erb
index d716743ef2..67d47776b9 100644
--- a/app/views/members/create.js.erb
+++ b/app/views/members/create.js.erb
@@ -2,7 +2,7 @@ $('#tab-content-members').html('<%= escape_javascript(render :partial => 'projec
 hideOnLoad();
 
 <% if @members.present? && @members.all? {|m| m.valid? } %>
-  hideModal();
+  $('#ajax-modal').append('<template data-dialog-dispatcher-target="hide"></template>')
   <% @members.each do |member| %>
     $("#member-<%= member.id %>").effect("highlight");
   <% end %>
diff --git a/app/views/members/new.js.erb b/app/views/members/new.js.erb
index ba74783897..d95e264d5f 100644
--- a/app/views/members/new.js.erb
+++ b/app/views/members/new.js.erb
@@ -1,2 +1,2 @@
-$('#ajax-modal').html('<%= escape_javascript(render :partial => 'members/new_modal') %>');
-showModal('ajax-modal', '90%');
+<% html = remote_dialog(width: '90%', title: l(:label_member_new), partial: 'members/new_modal') %>
+$('#ajax-modal').html('<%= escape_javascript(html) %>');
diff --git a/app/views/principal_memberships/_new_modal.html.erb b/app/views/principal_memberships/_new_modal.html.erb
index 945eff73cf..64af17534c 100644
--- a/app/views/principal_memberships/_new_modal.html.erb
+++ b/app/views/principal_memberships/_new_modal.html.erb
@@ -1,9 +1,7 @@
-<h3 class="title"><%= l(:label_add_projects) %></h3>
-
 <%= form_for :membership, :remote => true, :url => user_memberships_path(@principal), :method => :post do |f| %>
   <%= render :partial => 'new_form' %>
   <p class="buttons">
-    <%= submit_tag l(:button_add), :name => nil %>
-    <%= link_to_function l(:button_cancel), "hideModal(this);" %>
+    <%= submit_tag l(:button_add), name: nil %>
+    <%= link_to l(:button_cancel), '#', data: { action: 'dialog#cancel' } %>
   </p>
 <% end %>
diff --git a/app/views/principal_memberships/create.js.erb b/app/views/principal_memberships/create.js.erb
index 17d7ee14d3..3a2b78f4eb 100644
--- a/app/views/principal_memberships/create.js.erb
+++ b/app/views/principal_memberships/create.js.erb
@@ -2,7 +2,7 @@ $('#tab-content-memberships').html('<%= escape_javascript(render :partial => 'pr
 hideOnLoad();
 
 <% if @members.present? && @members.all? {|m| m.persisted? } %>
-  hideModal();
+  $('#ajax-modal').append('<template data-dialog-dispatcher-target="hide"></template>')
   <% @members.each do |member| %>
     $("#member-<%= member.id %>").effect("highlight");
   <% end %>
diff --git a/app/views/principal_memberships/new.js.erb b/app/views/principal_memberships/new.js.erb
index 625eeaf68d..3b8db8beac 100644
--- a/app/views/principal_memberships/new.js.erb
+++ b/app/views/principal_memberships/new.js.erb
@@ -1,5 +1,5 @@
+<% html = remote_dialog(width: '700px', title: l(:label_add_projects), partial: 'principal_memberships/new_modal') %>
 $('#ajax-modal').html('<%= escape_javascript(render :partial => 'principal_memberships/new_modal') %>');
-showModal('ajax-modal', '700px');
 
 $('.projects-selection').on('click', 'input[type=checkbox]', function(e){
   if (!$(this).is(':checked')) {
diff --git a/app/views/projects/_list.html.erb b/app/views/projects/_list.html.erb
index 6bfd5eb01a..6f708069ca 100644
--- a/app/views/projects/_list.html.erb
+++ b/app/views/projects/_list.html.erb
@@ -66,7 +66,7 @@
 <% end -%>
 <span class="pagination"><%= pagination_links_full @entry_pages, @entry_count %></span>
 
-<div id="csv-export-options" style="display:none;">
+<div id="csv-export-options" style="display:none;" data-controller="dialog">
   <h3 class="title"><%= l(:label_export_options, :export_format => 'CSV') %></h3>
   <%= form_tag(projects_path(:format => 'csv'), :method => :get, :id => 'csv-export-form') do %>
   <%= query_as_hidden_field_tags(@query) %>
@@ -77,8 +77,8 @@
   <%= export_csv_encoding_select_tag %>
   <%= export_csv_separator_select_tag %>
   <p class="buttons">
-    <%= submit_tag l(:button_export), :name => nil, :onclick => "hideModal(this);", :data => { :disable_with => false } %>
-    <%= link_to_function l(:button_cancel), "hideModal(this);" %>
+    <%= submit_tag l(:button_export), name: nil, data: { disable_with: false, action: 'dialog#hide' } %>
+    <%= link_to l(:button_cancel), '#', data: { action: 'dialog#cancel' } %>
   </p>
   <% end %>
 </div>
diff --git a/app/views/projects/index.html.erb b/app/views/projects/index.html.erb
index 875e736b18..f905d9ef1f 100644
--- a/app/views/projects/index.html.erb
+++ b/app/views/projects/index.html.erb
@@ -32,7 +32,7 @@
 
 <% other_formats_links do |f| %>
   <% if @query.display_type == 'list' %>
-    <%= f.link_to_with_query_parameters 'CSV', {}, :onclick => "showModal('csv-export-options', '350px'); return false;" %>
+    <%= f.link_to_with_query_parameters 'CSV', {}, data: dialog_dispatcher('csv-export-options', '350px') %>
   <% end %>
   <%= f.link_to 'Atom', :url => {:key => User.current.atom_key} %>
 <% end %>
diff --git a/app/views/reports/_details.html.erb b/app/views/reports/_details.html.erb
index 930fca6a84..e2ccab6ef1 100644
--- a/app/views/reports/_details.html.erb
+++ b/app/views/reports/_details.html.erb
@@ -37,19 +37,19 @@
 </tfoot>
 </table>
 <% other_formats_links do |f| %>
-  <%= f.link_to_with_query_parameters 'CSV', {}, :onclick => "showModal('csv-export-options', '330px'); return false;" %>
+  <%= f.link_to_with_query_parameters 'CSV', {}, data: dialog_dispatcher('csv-export-options', '330px') %>
 <% end %>
-<div id="csv-export-options" style="display: none;">
-  <h3 class="title"><%= l(:label_export_options, :export_format => 'CSV') %></h3>
+
+<% modal_dialog "csv-export-options", l(:label_export_options, export_format: 'CSV') do %>
   <%= form_tag(project_issues_report_details_path(@project, :detail => params[:detail], :format => 'csv'), :method => :get, :id => 'csv-export-form') do %>
   <%= export_csv_encoding_select_tag %>
   <%= export_csv_separator_select_tag %>
   <p class="buttons">
-    <%= submit_tag l(:button_export), :name => nil, :onclick => 'hideModal(this);', :data => {:disable_with => false} %>
-    <%= link_to_function l(:button_cancel), 'hideModal(this);' %>
+    <%= submit_tag l(:button_export), name: nil, data: { disable_with: false, action: 'dialog#hide' } %>
+    <%= link_to l(:button_cancel), '#', data: { action: 'dialog#cancel' } %>
   </p>
   <% end %>
-</div>
+<% end %>
 
 <div class="issue-report-graph hide-when-print">
   <canvas id="issues_by_<%= params[:detail] %>"></canvas>
diff --git a/app/views/roles/permissions.html.erb b/app/views/roles/permissions.html.erb
index 573fbc9fa0..5f61c91d5d 100644
--- a/app/views/roles/permissions.html.erb
+++ b/app/views/roles/permissions.html.erb
@@ -83,16 +83,16 @@
 <p><%= submit_tag l(:button_save) %></p>
 <% end %>
 <% other_formats_links do |f| %>
-  <%= f.link_to_with_query_parameters 'CSV', {}, :onclick => "showModal('csv-export-options', '330px'); return false;" %>
+  <%= f.link_to_with_query_parameters 'CSV', {}, data: dialog_dispatcher('csv-export-options', '330px') %>
 <% end %>
-<div id="csv-export-options" style="display: none;">
-  <h3 class="title"><%= l(:label_export_options, :export_format => 'CSV') %></h3>
+
+<% modal_dialog 'csv-export-options', l(:label_export_options, export_format: 'CSV') do %>
   <%= form_tag(permissions_roles_path(:format => 'csv'), :method => :get, :id => 'csv-export-form') do %>
   <%= export_csv_encoding_select_tag %>
   <%= export_csv_separator_select_tag %>
   <p class="buttons">
-    <%= submit_tag l(:button_export), :name => nil, :onclick => 'hideModal(this);', :data => {:disable_with => false} %>
-    <%= link_to_function l(:button_cancel), 'hideModal(this);' %>
+    <%= submit_tag l(:button_export), name: nil, data: { disable_with: false, action: 'dialog#hide' } %>
+    <%= link_to l(:button_cancel), '#', data: { action: 'dialog#cancel' } %>
   </p>
   <% end %>
-</div>
+<% end %>
diff --git a/app/views/sudo_mode/_new_modal.html.erb b/app/views/sudo_mode/_new_modal.html.erb
index d259c5a2a4..08b83dd14b 100644
--- a/app/views/sudo_mode/_new_modal.html.erb
+++ b/app/views/sudo_mode/_new_modal.html.erb
@@ -1,4 +1,3 @@
-<h3 class="title"><%= l(:label_password_required) %></h3>
 <p class="nodata" style="font-size: 1em;"><%= t 'sudo_mode_new_info_html' %></p>
 <%= form_tag({}, remote: true) do %>
 
@@ -14,7 +13,7 @@
   </div>
 
   <p class="buttons">
-    <%= submit_tag l(:button_submit), onclick: "hideModal(this);"  %>
-    <%= link_to_function l(:button_cancel), "hideModal(this);" %>
+    <%= submit_tag l(:button_submit), data: { action: 'dialog#hide' } %>
+    <%= link_to l(:button_cancel), '#', data: { action: 'dialog#cancel' } %>
   </p>
 <% end %>
diff --git a/app/views/sudo_mode/new.js.erb b/app/views/sudo_mode/new.js.erb
index 2eda9b857d..585a31c4b0 100644
--- a/app/views/sudo_mode/new.js.erb
+++ b/app/views/sudo_mode/new.js.erb
@@ -1,4 +1,4 @@
-var sudo_modal = $('#sudo-modal').length ? $('#sudo-modal') : $("<div>", {id: "sudo-modal"}).appendTo($("body"));
-sudo_modal.hide().html('<%= escape_javascript render partial: 'sudo_mode/new_modal' %>');
-showModal('sudo-modal', '400px');
+<% html = remote_dialog(width: '400px', title: l(:label_password_required), partial: 'sudo_mode/new_modal') %>
+const sudo_modal = $('#sudo-modal').length ? $('#sudo-modal') : $("<div>", {id: "sudo-modal"}).appendTo($("body"));
+sudo_modal.hide().html('<%= escape_javascript html %>');
 $('#sudo_password').focus();
diff --git a/app/views/timelog/index.html.erb b/app/views/timelog/index.html.erb
index d9985e922e..422a5433ee 100644
--- a/app/views/timelog/index.html.erb
+++ b/app/views/timelog/index.html.erb
@@ -29,12 +29,11 @@
 <span class="pagination"><%= pagination_links_full @entry_pages, @entry_count %></span>
 
 <% other_formats_links do |f| %>
-  <%= f.link_to_with_query_parameters 'CSV', {}, :onclick => "showModal('csv-export-options', '330px'); return false;" %>
-  <%= f.link_to_with_query_parameters 'Atom', :key => User.current.atom_key %>
+  <%= f.link_to_with_query_parameters 'CSV', {}, data: dialog_dispatcher('csv-export-options', '330px') %>
+  <%= f.link_to_with_query_parameters 'Atom', key: User.current.atom_key %>
 <% end %>
 
-<div id="csv-export-options" style="display:none;">
-  <h3 class="title"><%= l(:label_export_options, :export_format => 'CSV') %></h3>
+<% modal_dialog "csv-export-options", l(:label_export_options, export_format: 'CSV') do %>
   <%= form_tag(_time_entries_path(@project, nil, :format => 'csv'), :method => :get, :id => 'csv-export-form') do %>
   <%= query_as_hidden_field_tags @query %>
   <%= hidden_field_tag('query_name', @query.name) %>
@@ -55,11 +54,11 @@
   <%= export_csv_encoding_select_tag %>
   <%= export_csv_separator_select_tag %>
   <p class="buttons">
-    <%= submit_tag l(:button_export), :name => nil, :onclick => "hideModal(this);", :data => { :disable_with => false } %>
-    <%= link_to_function l(:button_cancel), "hideModal(this);" %>
+    <%= submit_tag l(:button_export), name: nil, data: { disable_with: false, action: 'dialog#hide' } %>
+    <%= link_to l(:button_cancel), '#', data: { action: 'dialog#cancel' } %>
   </p>
   <% end %>
-</div>
+<% end %>
 <% end %>
 <% end %>
 
diff --git a/app/views/timelog/report.html.erb b/app/views/timelog/report.html.erb
index 180ac15128..b1860c5ceb 100644
--- a/app/views/timelog/report.html.erb
+++ b/app/views/timelog/report.html.erb
@@ -66,18 +66,17 @@
 </div>
 
 <% other_formats_links do |f| %>
-  <%= f.link_to_with_query_parameters 'CSV', {}, :onclick => "showModal('csv-export-options', '330px'); return false;" %>
+  <%= f.link_to_with_query_parameters 'CSV', {}, data: dialog_dispatcher('csv-export-options', '330px') %>
 <% end %>
 <% end %>
-<div id="csv-export-options" style="display: none;">
-  <h3 class="title"><%= l(:label_export_options, :export_format => 'CSV') %></h3>
+<% modal_dialog "csv-export-options", l(:label_export_options, export_format: 'CSV') do %>
   <%= export_csv_encoding_select_tag %>
   <%= export_csv_separator_select_tag %>
   <p class="buttons">
-    <%= submit_tag l(:button_export), :name => nil, :id => 'csv-export-button' %>
-    <%= submit_tag l(:button_cancel), :name => nil, :onclick => 'hideModal(this);', :type => 'button' %>
+    <%= submit_tag l(:button_export), name: nil, id: 'csv-export-button', data: { action: 'dialog#hide' } %>
+    <%= link_to l(:button_cancel), '#', data: { action: 'dialog#cancel' } %>
   </p>
-</div>
+<% end %>
 <% end %>
 <% end %>
 
@@ -87,14 +86,12 @@
 
 <% html_title(@query.new_record? ? l(:label_spent_time) : @query.name, l(:label_report)) %>
 
-
 <%= javascript_tag do %>
 $(document).ready(function(){
   $('input#csv-export-button').click(function(){
     $('form input#encoding').val($('select#encoding option:selected').val());
     $('form#query_form').attr('action', '<%= _report_time_entries_path(@project, nil, :format => 'csv') %>').submit();
     $('form#query_form').attr('action', '<%= _report_time_entries_path(@project, nil) %>');
-    hideModal(this);
   });
 });
 <% end %>
diff --git a/app/views/users/index.html.erb b/app/views/users/index.html.erb
index eb975a5ffd..066005d47e 100644
--- a/app/views/users/index.html.erb
+++ b/app/views/users/index.html.erb
@@ -23,11 +23,10 @@
     <span class="pagination"><%= pagination_links_full @user_pages, @user_count %></span>
   <% end %>
   <% other_formats_links do |f| %>
-    <%= f.link_to_with_query_parameters 'CSV', {}, :onclick => "showModal('csv-export-options', '350px'); return false;" %>
+    <%= f.link_to_with_query_parameters 'CSV', {}, data: dialog_dispatcher('csv-export-options', '350px') %>
   <% end %>
 
-  <div id="csv-export-options" style="display:none;">
-    <h3 class="title"><%= l(:label_export_options, :export_format => 'CSV') %></h3>
+  <% modal_dialog "csv-export-options", l(:label_export_options, export_format: 'CSV') do %>
     <%= form_tag(users_path(format: 'csv'), method: :get, id: 'csv-export-form') do %>
     <%= query_as_hidden_field_tags(@query) %>
     <%= hidden_field_tag('query_name', @query.name) %>
@@ -48,11 +47,11 @@
     <%= export_csv_encoding_select_tag %>
     <%= export_csv_separator_select_tag %>
     <p class="buttons">
-      <%= submit_tag l(:button_export), :name => nil, :onclick => "hideModal(this);", :data => { :disable_with => false } %>
-      <%= link_to_function l(:button_cancel), "hideModal(this);" %>
+      <%= submit_tag l(:button_export), name: nil, data: { disable_with: false, action: 'dialog#hide' } %>
+      <%= link_to l(:button_cancel), '#', data: { action: 'dialog#cancel' } %>
     </p>
     <% end %>
-  </div>
+  <% end %>
 <% end %>
 
 <% content_for :sidebar do %>
diff --git a/app/views/versions/_new_modal.html.erb b/app/views/versions/_new_modal.html.erb
index 02c57abc4c..e73196e6c0 100644
--- a/app/views/versions/_new_modal.html.erb
+++ b/app/views/versions/_new_modal.html.erb
@@ -1,9 +1,7 @@
-<h3 class="title"><%=l(:label_version_new)%></h3>
-
 <%= labelled_form_for @version, :url => project_versions_path(@project), :html => {:multipart => true}, :remote => true do |f| %>
 <%= render :partial => 'versions/form', :locals => { :f => f } %>
   <p class="buttons">
-    <%= submit_tag l(:button_create), :name => nil %>
-    <%= link_to_function l(:button_cancel), "hideModal(this);" %>
+    <%= submit_tag l(:button_create), name: nil %>
+    <%= link_to l(:button_cancel), '#', data: { action: 'dialog#cancel' } %>
   </p>
 <% end %>
diff --git a/app/views/versions/create.js.erb b/app/views/versions/create.js.erb
index e6e0bec455..2d00d80ea7 100644
--- a/app/views/versions/create.js.erb
+++ b/app/views/versions/create.js.erb
@@ -1,3 +1,3 @@
-hideModal();
+$('#ajax-modal').append('<template data-dialog-dispatcher-target="hide"></template>')
 <% select = content_tag('select', content_tag('option') + version_options_for_select(@project.shared_versions.open, @version), :id => 'issue_fixed_version_id', :name => 'issue[fixed_version_id]') %>
 $('#issue_fixed_version_id').replaceWith('<%= escape_javascript(select) %>');
diff --git a/app/views/versions/new.js.erb b/app/views/versions/new.js.erb
index 114b9c7c52..b98498d502 100644
--- a/app/views/versions/new.js.erb
+++ b/app/views/versions/new.js.erb
@@ -1,2 +1,2 @@
-$('#ajax-modal').html('<%= escape_javascript(render :partial => 'versions/new_modal') %>');
-showModal('ajax-modal', '600px');
+<% html = remote_dialog(width: '600px', title: l(:label_version_new), partial: 'versions/new_modal') %>
+$('#ajax-modal').html('<%= escape_javascript(html) %>');
diff --git a/app/views/watchers/_new.html.erb b/app/views/watchers/_new.html.erb
index dfff5516c0..24b40af2bc 100644
--- a/app/views/watchers/_new.html.erb
+++ b/app/views/watchers/_new.html.erb
@@ -1,13 +1,3 @@
-<%
-title =
-  if watchables.present?
-    l(:"permission_add_#{watchables.first.class.name.underscore}_watchers")
-  else
-    l(:permission_add_issue_watchers)
-  end
--%>
-<h3 class="title"><%= title %></h3>
-
 <%= form_tag(watchables.present? ? watchers_path : watchers_append_path,
              :remote => true,
              :method => :post,
@@ -42,7 +32,7 @@ title =
   </div>
 
   <p class="buttons">
-    <%= submit_tag l(:button_add), :name => nil, :onclick => "hideModal(this);" %>
-    <%= link_to_function l(:button_cancel), "hideModal(this);" %>
+    <%= submit_tag l(:button_add), name: nil, data: { action: 'dialog#hide' } %>
+    <%= link_to l(:button_cancel), '#', data: { action: 'dialog#cancel' } %>
   </p>
 <% end %>
diff --git a/app/views/watchers/new.js.erb b/app/views/watchers/new.js.erb
index dd3b9c1e35..0c56d0847f 100644
--- a/app/views/watchers/new.js.erb
+++ b/app/views/watchers/new.js.erb
@@ -1,7 +1,8 @@
-$('#ajax-modal').html(
-  '<%= escape_javascript(
-         render(:partial => 'watchers/new',
-                :locals => {:watchables => @watchables, :users => @users})
-       ) %>');
-showModal('ajax-modal', '400px');
-$('#ajax-modal').addClass('new-watcher');
+<% title = if @watchables.present?
+             l(:"permission_add_#{@watchables.first.class.name.underscore}_watchers")
+           else
+             l(:permission_add_issue_watchers)
+           end -%>
+<% html = remote_dialog(width: '400px', title: title, partial: 'watchers/new', locals: {watchables: @watchables, users: @users}) %>
+$('#ajax-modal').html('<%= escape_javascript html %>');
+$('#ajax-modal').addClass('new-watcherwatcher');
diff --git a/app/views/wiki/_new_modal.html.erb b/app/views/wiki/_new_modal.html.erb
index 108c6ad881..26a6b4abfe 100644
--- a/app/views/wiki/_new_modal.html.erb
+++ b/app/views/wiki/_new_modal.html.erb
@@ -1,5 +1,3 @@
-<h3 class="title"><%=l(:label_wiki_page_new)%></h3>
-
 <%= labelled_form_for :page, @page,
             :url => new_project_wiki_page_path(@project),
             :method => 'post',
@@ -24,6 +22,6 @@
 
   <p class="buttons">
     <%= submit_tag l(:label_next), :name => nil %>
-    <%= link_to_function l(:button_cancel), "hideModal(this);" %>
+    <%= link_to l(:button_cancel), '#', data: { action: 'dialog#cancel' } %>
   </p>
 <% end %>
diff --git a/app/views/wiki/new.js.erb b/app/views/wiki/new.js.erb
index c12b353400..d3a6f94a1a 100644
--- a/app/views/wiki/new.js.erb
+++ b/app/views/wiki/new.js.erb
@@ -1,2 +1,2 @@
-$('#ajax-modal').html('<%= escape_javascript(render :partial => 'wiki/new_modal') %>');
-showModal('ajax-modal', '600px');
+<% html = remote_dialog(width: '600px', title: l(:label_wiki_page_new), partial: 'wiki/new_modal') %>
+$('#ajax-modal').html('<%= escape_javascript(html) %>');
diff --git a/config/icon_source.yml b/config/icon_source.yml
index dc1803cdcf..7f139da251 100644
--- a/config/icon_source.yml
+++ b/config/icon_source.yml
@@ -221,4 +221,6 @@
 - name: unwatch
   svg: eye-off
 - name: copy-pre-content
-  svg: clipboard
\ No newline at end of file
+  svg: clipboard
+- name: close-dialog
+  svg: x
diff --git a/config/locales/ar.yml b/config/locales/ar.yml
index f52ec8dca9..83941dc0fe 100644
--- a/config/locales/ar.yml
+++ b/config/locales/ar.yml
@@ -851,6 +851,7 @@ ar:
   button_show: إظهار
   button_edit_section: تعديل هذا الجزء
   button_export: تصدير لملف
+  button_close_dialog: Close
 
   status_active: نشيط
   status_registered: مسجل
diff --git a/config/locales/az.yml b/config/locales/az.yml
index 3710566dfc..4f181b5b47 100644
--- a/config/locales/az.yml
+++ b/config/locales/az.yml
@@ -259,6 +259,7 @@ az:
   button_update: Yeniləmək
   button_view: Baxmaq
   button_watch: İzləmək
+  button_close_dialog: Close
 
   default_activity_design: Layihənin hazırlanması
   default_activity_development: Hazırlanma prosesi
diff --git a/config/locales/bg.yml b/config/locales/bg.yml
index 4f5b54ae32..22aaa3c5ec 100644
--- a/config/locales/bg.yml
+++ b/config/locales/bg.yml
@@ -1220,6 +1220,7 @@ bg:
   button_delete_object: Изтриване %{object_name}
   button_create_and_follow: Създаване и продължаване
   button_apply_issues_filter: Прилагане на филтъра
+  button_close_dialog: Close
 
   status_active: активен
   status_registered: регистриран
diff --git a/config/locales/bs.yml b/config/locales/bs.yml
index db44cf7006..477c557e6e 100644
--- a/config/locales/bs.yml
+++ b/config/locales/bs.yml
@@ -719,6 +719,7 @@ bs:
   button_watch: Posmatraj
   button_configure: Konfiguracija
   button_quote: Citat
+  button_close_dialog: Close
 
   status_active: aktivan
   status_registered: registrovan
diff --git a/config/locales/ca.yml b/config/locales/ca.yml
index a52b03b1a1..25c4f1197e 100644
--- a/config/locales/ca.yml
+++ b/config/locales/ca.yml
@@ -799,6 +799,7 @@ ca:
   button_configure: "Configurar"
   button_quote: "Citar"
   button_show: "Mostrar"
+  button_close_dialog: Close
 
   status_active: "actiu"
   status_registered: "registrat"
diff --git a/config/locales/cs.yml b/config/locales/cs.yml
index 45ed707114..05de911357 100644
--- a/config/locales/cs.yml
+++ b/config/locales/cs.yml
@@ -814,6 +814,7 @@ cs:
   button_configure: Konfigurovat
   button_quote: Citovat
   button_show: Zobrazit
+  button_close_dialog: Close
 
   status_active: aktivní
   status_registered: registrovaný
diff --git a/config/locales/da.yml b/config/locales/da.yml
index 05c9d4c3d4..559f0f0b03 100644
--- a/config/locales/da.yml
+++ b/config/locales/da.yml
@@ -636,6 +636,7 @@ da:
   button_annotate: Annotér
   button_update: Opdatér
   button_configure: Konfigurér
+  button_close_dialog: Close
 
   status_active: aktiv
   status_registered: registreret
diff --git a/config/locales/de.yml b/config/locales/de.yml
index 9ad00adfae..31e64e5ff5 100644
--- a/config/locales/de.yml
+++ b/config/locales/de.yml
@@ -211,6 +211,7 @@ de:
   button_view: Anzeigen
   button_watch: Beobachten
   button_actions: Aktionen
+  button_close_dialog: Close
 
   default_activity_design: Design
   default_activity_development: Entwicklung
diff --git a/config/locales/el.yml b/config/locales/el.yml
index a92609e116..c1209c5d34 100644
--- a/config/locales/el.yml
+++ b/config/locales/el.yml
@@ -731,6 +731,7 @@ el:
   button_update: Ενημέρωση
   button_configure: Ρύθμιση
   button_quote: Παράθεση
+  button_close_dialog: Close
 
   status_active: ενεργό(ς)/ή
   status_registered: εγεγγραμμένο(ς)/η
diff --git a/config/locales/en-GB.yml b/config/locales/en-GB.yml
index 79fd6478d6..f0e7a9856e 100644
--- a/config/locales/en-GB.yml
+++ b/config/locales/en-GB.yml
@@ -827,6 +827,7 @@ en-GB:
   button_configure: Configure
   button_quote: Quote
   button_show: Show
+  button_close_dialog: Close
 
   status_active: active
   status_registered: registered
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 819846e1a4..9415da7b85 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -1223,6 +1223,7 @@ en:
   button_delete_object: "Delete %{object_name}"
   button_create_and_follow: Create and follow
   button_apply_issues_filter: Apply issues filter
+  button_close_dialog: Close
 
   status_active: active
   status_registered: registered
diff --git a/config/locales/es-PA.yml b/config/locales/es-PA.yml
index 8aef90a2d1..a1964c3247 100644
--- a/config/locales/es-PA.yml
+++ b/config/locales/es-PA.yml
@@ -208,6 +208,7 @@ es-PA:
   button_change_password: Cambiar contraseña
   button_check_all: Seleccionar todo
   button_clear: Anular
+  button_close_dialog: Close
   button_configure: Configurar
   button_copy: Copiar
   button_create: Crear
@@ -235,6 +236,7 @@ es-PA:
   button_update: Actualizar
   button_view: Ver
   button_watch: Monitorizar
+
   default_activity_design: Diseño
   default_activity_development: Desarrollo
   default_doc_category_tech: Documentación técnica
diff --git a/config/locales/es.yml b/config/locales/es.yml
index fd41f275b3..e434a9ac84 100644
--- a/config/locales/es.yml
+++ b/config/locales/es.yml
@@ -206,6 +206,7 @@ es:
   button_change_password: Cambiar contraseña
   button_check_all: Seleccionar todo
   button_clear: Anular
+  button_close_dialog: Close
   button_configure: Configurar
   button_copy: Copiar
   button_create: Crear
diff --git a/config/locales/et.yml b/config/locales/et.yml
index 467fcdcb16..f19cb47ee9 100644
--- a/config/locales/et.yml
+++ b/config/locales/et.yml
@@ -887,6 +887,7 @@ et:
   button_edit_section: "Muuda seda sektsiooni"
   button_export: "Ekspordi"
   button_delete_my_account: "Kustuta oma konto"
+  button_close_dialog: Close
 
   status_active: "aktiivne"
   status_registered: "registreeritud"
diff --git a/config/locales/eu.yml b/config/locales/eu.yml
index 40e6ef21eb..9a4e1fe59e 100644
--- a/config/locales/eu.yml
+++ b/config/locales/eu.yml
@@ -777,6 +777,7 @@ eu:
   button_configure: Konfiguratu
   button_quote: Aipatu
   button_show: Ikusi
+  button_close_dialog: Close
 
   status_active: gaituta
   status_registered: izena emanda
diff --git a/config/locales/fa.yml b/config/locales/fa.yml
index b124cbff92..73d77c2b22 100644
--- a/config/locales/fa.yml
+++ b/config/locales/fa.yml
@@ -1198,6 +1198,7 @@ fa:
   button_edit_object: ویرایش %{object_name}
   button_delete_object: حذف %{object_name}
   button_create_and_follow: ساخت و برگشت
+  button_close_dialog: Close
 
   status_active: فعال
   status_registered: ثبت‌نام‌شده
diff --git a/config/locales/fi.yml b/config/locales/fi.yml
index c6c90f1dc6..6c0b943497 100644
--- a/config/locales/fi.yml
+++ b/config/locales/fi.yml
@@ -610,6 +610,7 @@ fi:
   button_copy: Kopioi
   button_annotate: Lisää selitys
   button_update: Päivitä
+  button_close_dialog: Close
 
   status_active: aktiivinen
   status_registered: rekisteröity
diff --git a/config/locales/fr.yml b/config/locales/fr.yml
index 7a072e32fb..9fc5a6275a 100644
--- a/config/locales/fr.yml
+++ b/config/locales/fr.yml
@@ -1078,6 +1078,7 @@ fr:
   button_reopen: Réouvrir
   button_import: Importer
   button_filter: Filtrer
+  button_close_dialog: Close
 
   status_active: actif
   status_registered: enregistré
diff --git a/config/locales/gl.yml b/config/locales/gl.yml
index 51e5ed99e6..dedda18405 100644
--- a/config/locales/gl.yml
+++ b/config/locales/gl.yml
@@ -183,6 +183,7 @@ gl:
   button_change_password: "Cambiar o contrasinal"
   button_check_all: "Seleccionalo todo"
   button_clear: Anular
+  button_close_dialog: Close
   button_configure: Configurar
   button_copy: Copiar
   button_create: Crear
diff --git a/config/locales/he.yml b/config/locales/he.yml
index 26c37fff68..a050bfa180 100644
--- a/config/locales/he.yml
+++ b/config/locales/he.yml
@@ -805,6 +805,7 @@ he:
   button_configure: אפשרויות
   button_quote: צטט
   button_show: הצג
+  button_close_dialog: Close
 
   status_active: פעיל
   status_registered: רשום
diff --git a/config/locales/hr.yml b/config/locales/hr.yml
index 83af380f07..7ef02f900c 100644
--- a/config/locales/hr.yml
+++ b/config/locales/hr.yml
@@ -767,6 +767,7 @@ hr:
   button_configure: Konfiguracija
   button_quote: Navod
   button_show: Show
+  button_close_dialog: Close
 
   status_active: aktivan
   status_registered: Registriran
diff --git a/config/locales/hu.yml b/config/locales/hu.yml
index b4a8648264..4aa9edfbec 100644
--- a/config/locales/hu.yml
+++ b/config/locales/hu.yml
@@ -651,6 +651,7 @@
   button_annotate: Jegyzetel
   button_update: Módosít
   button_configure: Konfigurál
+  button_close_dialog: Close
 
   status_active: aktív
   status_registered: regisztrált
diff --git a/config/locales/id.yml b/config/locales/id.yml
index 21291a9940..f9a9bca898 100644
--- a/config/locales/id.yml
+++ b/config/locales/id.yml
@@ -749,6 +749,7 @@ id:
   button_update: Perbarui
   button_configure: Konfigur
   button_quote: Kutip
+  button_close_dialog: Close
 
   status_active: aktif
   status_registered: terdaftar
diff --git a/config/locales/it.yml b/config/locales/it.yml
index 66d084cdc9..4b5cc32861 100644
--- a/config/locales/it.yml
+++ b/config/locales/it.yml
@@ -1226,6 +1226,7 @@ it:
   button_delete_object: "Elimina %{object_name}"
   button_create_and_follow: Crea e segui
   button_apply_issues_filter: Applica filtro segnalazioni
+  button_close_dialog: Close
 
   status_active: attivo
   status_registered: registrato
diff --git a/config/locales/ja.yml b/config/locales/ja.yml
index d72d136ecc..3be7d29de9 100644
--- a/config/locales/ja.yml
+++ b/config/locales/ja.yml
@@ -864,6 +864,7 @@ ja:
   button_save_object: "%{object_name}を保存"
   button_edit_object: "%{object_name}を編集"
   button_delete_object: "%{object_name}を削除"
+  button_close_dialog: 閉じる
 
   status_active: 有効
   status_registered: 登録
diff --git a/config/locales/ko.yml b/config/locales/ko.yml
index f2c7fa7d7d..ec38071477 100644
--- a/config/locales/ko.yml
+++ b/config/locales/ko.yml
@@ -765,6 +765,7 @@ ko:
   button_update: 업데이트
   button_configure: 설정
   button_quote: 댓글달기
+  button_close_dialog: Close
 
   status_active: 사용중
   status_registered: 등록대기
diff --git a/config/locales/lt.yml b/config/locales/lt.yml
index 30998b050e..eea4b7e8d8 100644
--- a/config/locales/lt.yml
+++ b/config/locales/lt.yml
@@ -1029,6 +1029,7 @@ lt:
   button_close: Uždaryti
   button_reopen: Atidaryti iš naujo
   button_import: Importuoti
+  button_close_dialog: Close
 
   status_active: aktyvus
   status_registered: užregistruotas
diff --git a/config/locales/lv.yml b/config/locales/lv.yml
index 1d43e621d5..055d0916e6 100644
--- a/config/locales/lv.yml
+++ b/config/locales/lv.yml
@@ -774,6 +774,7 @@ lv:
   button_configure: Konfigurēt
   button_quote: Citāts
   button_show: Rādīt
+  button_close_dialog: Close
 
   status_active: aktīvs
   status_registered: reģistrēts
diff --git a/config/locales/mk.yml b/config/locales/mk.yml
index ee950d96e5..be7644938b 100644
--- a/config/locales/mk.yml
+++ b/config/locales/mk.yml
@@ -795,6 +795,7 @@ mk:
   button_configure: Конфигурирај
   button_quote: Цитирај
   button_show: Show
+  button_close_dialog: Close
 
   status_active: активни
   status_registered: регистрирани
diff --git a/config/locales/mn.yml b/config/locales/mn.yml
index af4c0a5ec7..8d4ebbbcbd 100644
--- a/config/locales/mn.yml
+++ b/config/locales/mn.yml
@@ -780,6 +780,7 @@ mn:
   button_configure: Тохируулах
   button_quote: Ишлэл
   button_show: Үзэх
+  button_close_dialog: Close
 
   status_active: идэвхтэй
   status_registered: бүртгүүлсэн
diff --git a/config/locales/nl.yml b/config/locales/nl.yml
index 18644e021f..f8dce53b1e 100644
--- a/config/locales/nl.yml
+++ b/config/locales/nl.yml
@@ -151,6 +151,7 @@ nl:
   button_change_password: Wachtwoord wijzigen
   button_check_all: Alles selecteren
   button_clear: Leegmaken
+  button_close_dialog: Close
   button_configure: Configureren
   button_copy: Kopiëren
   button_create: Aanmaken
diff --git a/config/locales/no.yml b/config/locales/no.yml
index a0a08e8259..fca2a873f2 100644
--- a/config/locales/no.yml
+++ b/config/locales/no.yml
@@ -627,6 +627,7 @@
   button_annotate: Notér
   button_update: Oppdater
   button_configure: Konfigurer
+  button_close_dialog: Close
 
   status_active: aktiv
   status_registered: registrert
diff --git a/config/locales/pl.yml b/config/locales/pl.yml
index 4c43abc0b2..c107cb9f9b 100644
--- a/config/locales/pl.yml
+++ b/config/locales/pl.yml
@@ -1217,6 +1217,7 @@ pl:
   button_delete_object: "Usuń %{object_name}"
   button_create_and_follow: Utwórz i przejdź
   button_apply_issues_filter: Zastosuj filtr na liście zagadnień
+  button_close_dialog: Close
 
   status_active: aktywny
   status_registered: zarejestrowany
diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml
index 1402dcfa1f..f2c3551969 100644
--- a/config/locales/pt-BR.yml
+++ b/config/locales/pt-BR.yml
@@ -671,6 +671,7 @@ pt-BR:
   button_update: Atualizar
   button_configure: Configurar
   button_quote: Responder
+  button_close_dialog: Close
 
   status_active: ativo
   status_registered: registrado
diff --git a/config/locales/pt.yml b/config/locales/pt.yml
index e92bb4bd35..d42b8886ef 100644
--- a/config/locales/pt.yml
+++ b/config/locales/pt.yml
@@ -655,6 +655,7 @@ pt:
   button_update: Atualizar
   button_configure: Configurar
   button_quote: Citar
+  button_close_dialog: Close
 
   status_active: ativo
   status_registered: registado
diff --git a/config/locales/ro.yml b/config/locales/ro.yml
index 38f23ce63b..41c1da8993 100644
--- a/config/locales/ro.yml
+++ b/config/locales/ro.yml
@@ -709,6 +709,7 @@ ro:
   button_update: Actualizează
   button_configure: Configurează
   button_quote: Citează
+  button_close_dialog: Close
 
   status_active: activ
   status_registered: înregistrat
diff --git a/config/locales/ru.yml b/config/locales/ru.yml
index 540d961c4f..2493575ab2 100644
--- a/config/locales/ru.yml
+++ b/config/locales/ru.yml
@@ -269,6 +269,7 @@ ru:
   button_update: Обновить
   button_view: Просмотреть
   button_watch: Следить
+  button_close_dialog: Close
 
   default_activity_design: Проектирование
   default_activity_development: Разработка
diff --git a/config/locales/sk.yml b/config/locales/sk.yml
index 2a35d05379..ee5744d353 100644
--- a/config/locales/sk.yml
+++ b/config/locales/sk.yml
@@ -629,6 +629,7 @@ sk:
   button_annotate: Komentovať
   button_update: Aktualizovať
   button_configure: Konfigurovať
+  button_close_dialog: Close
 
   status_active: aktívny
   status_registered: zaregistrovaný
diff --git a/config/locales/sl.yml b/config/locales/sl.yml
index 9c51b232c8..9b970f480c 100644
--- a/config/locales/sl.yml
+++ b/config/locales/sl.yml
@@ -699,6 +699,7 @@ sl:
   button_update: Posodobi
   button_configure: Konfiguriraj
   button_quote: Citiraj
+  button_close_dialog: Close
 
   status_active: aktivni
   status_registered: registriran
diff --git a/config/locales/sq.yml b/config/locales/sq.yml
index f7b3e4fbd8..9f3f7480f3 100644
--- a/config/locales/sq.yml
+++ b/config/locales/sq.yml
@@ -1152,6 +1152,7 @@ sq:
   button_project_bookmark_delete: Hiqe faqerojtësin
   button_filter: Filtër
   button_actions: Veprime
+  button_close_dialog: Close
 
   status_active: aktive
   status_registered: e regjistruar
diff --git a/config/locales/sr-YU.yml b/config/locales/sr-YU.yml
index 5d83fa6e20..c7f6eda1a9 100644
--- a/config/locales/sr-YU.yml
+++ b/config/locales/sr-YU.yml
@@ -795,6 +795,7 @@ sr-YU:
   button_configure: Podesi
   button_quote: Pod navodnicima
   button_show: Prikaži
+  button_close_dialog: Close
 
   status_active: aktivni
   status_registered: registrovani
diff --git a/config/locales/sr.yml b/config/locales/sr.yml
index baeb0ad21a..08ef6aa84f 100644
--- a/config/locales/sr.yml
+++ b/config/locales/sr.yml
@@ -793,6 +793,7 @@ sr:
   button_configure: Подеси
   button_quote: Под наводницима
   button_show: Прикажи
+  button_close_dialog: Close
 
   status_active: активни
   status_registered: регистровани
diff --git a/config/locales/sv.yml b/config/locales/sv.yml
index 607c434278..b1296c9361 100644
--- a/config/locales/sv.yml
+++ b/config/locales/sv.yml
@@ -965,6 +965,7 @@ sv:
   button_delete_my_account: Ta bort mitt konto
   button_close: Stäng
   button_reopen: Återöppna
+  button_close_dialog: Close
 
   status_active: aktiv
   status_registered: registrerad
diff --git a/config/locales/ta-IN.yml b/config/locales/ta-IN.yml
index 6521837ec2..d06cb222f2 100644
--- a/config/locales/ta-IN.yml
+++ b/config/locales/ta-IN.yml
@@ -825,6 +825,7 @@ ta-IN:
   button_configure: உள்ளமைக்கவும்
   button_quote: மேற்கோள்
   button_show: காட்டு
+  button_close_dialog: Close
 
   status_active: செயலில்
   status_registered: பதிவுசெய்யப்பட்டது
diff --git a/config/locales/th.yml b/config/locales/th.yml
index b84a5d43a2..1ad511efc0 100644
--- a/config/locales/th.yml
+++ b/config/locales/th.yml
@@ -630,6 +630,7 @@ th:
   button_annotate: หมายเหตุประกอบ
   button_update: ปรับปรุง
   button_configure: ปรับแต่ง
+  button_close_dialog: Close
 
   status_active: เปิดใช้งานแล้ว
   status_registered: รอการอนุมัติ
diff --git a/config/locales/tr.yml b/config/locales/tr.yml
index f1c8a8c0db..e36be4b736 100644
--- a/config/locales/tr.yml
+++ b/config/locales/tr.yml
@@ -645,6 +645,7 @@ tr:
   button_annotate: Değişiklik geçmişine göre göster
   button_update: Güncelle
   button_configure: Yapılandır
+  button_close_dialog: Close
 
   status_active: faal
   status_registered: kayıtlı
diff --git a/config/locales/uk.yml b/config/locales/uk.yml
index 83868a72ad..ec15e462c7 100644
--- a/config/locales/uk.yml
+++ b/config/locales/uk.yml
@@ -592,6 +592,7 @@ uk:
   button_change_password: Змінити пароль
   button_copy: Копіювати
   button_annotate: Анотувати
+  button_close_dialog: Close
 
   status_active: Активний
   status_registered: Зареєстрований
diff --git a/config/locales/vi.yml b/config/locales/vi.yml
index e1778a4bf2..7f3b2cc9cd 100644
--- a/config/locales/vi.yml
+++ b/config/locales/vi.yml
@@ -698,6 +698,7 @@ vi:
   button_update: Cập nhật
   button_configure: Cấu hình
   button_quote: Trích dẫn
+  button_close_dialog: Close
 
   status_active: Đang hoạt động
   status_registered: Mới đăng ký
diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml
index 261970d0c2..39cfefd88b 100644
--- a/config/locales/zh-TW.yml
+++ b/config/locales/zh-TW.yml
@@ -1148,6 +1148,7 @@
   button_reopen: 重新開啟
   button_import: 匯入
   button_filter: 篩選器
+  button_close_dialog: Close
 
   status_active: 活動中
   status_registered: 註冊完成
diff --git a/config/locales/zh.yml b/config/locales/zh.yml
index 11a5dcdb9f..55203468ff 100644
--- a/config/locales/zh.yml
+++ b/config/locales/zh.yml
@@ -815,6 +815,7 @@ zh:
   button_configure: 配置
   button_quote: 引用
   button_show: 显示
+  button_close_dialog: Close
 
   status_active: 活动的
   status_registered: 已注册
diff --git a/test/functional/issues_controller_test.rb b/test/functional/issues_controller_test.rb
index b5180fcff2..0863d07419 100644
--- a/test/functional/issues_controller_test.rb
+++ b/test/functional/issues_controller_test.rb
@@ -3976,7 +3976,7 @@ class IssuesControllerTest < Redmine::ControllerTest
     end
 
     assert_select 'div#trackers_description' do
-      assert_select 'h3', :text => 'Trackers description', :count => 1
+      assert_select '.title', :text => 'Trackers description', :count => 1
       # only Bug and Feature have descriptions
       assert_select 'dt', 2
       assert_select 'dt', :text => 'Bug', :count => 1
@@ -4020,7 +4020,7 @@ class IssuesControllerTest < Redmine::ControllerTest
     end
 
     assert_select 'div#issue_statuses_description' do
-      assert_select 'h3', :text => 'Issue statuses description', :count => 1
+      assert_select '.title', :text => 'Issue statuses description', :count => 1
       assert_select 'dt', 2
       assert_select 'dt', :text => 'New', :count => 1
       assert_select 'dd', :text => 'Description for New issue status', :count => 1
-- 
2.39.2

