Move asset pipeline javascripts to frontend (#8482)

* ove reporting and reporting_engine js to frontend page

* Replace webhook assets javascript

* Add path augment service to dynamically load scripts

* Remove JS-Files that are not used/not needed any more

* Include AugmentingModule correctly

* Remove special handling for checkbox selection which is outdated and two jquery libs that are not used any more

* Load scripts dynamically that are not needed globally

* Move plugin JS to frontend

* Move toggable fieldsets to global listeners

* Move top-menu to global listeners and convert to TS

* Move action_menu to frontend and port to TS

* Move settings listener to frontend

* Remove global js assets that were not used anyway

* Move date range to timelog dynamic script where it's still used until removed

* Move colors to global-listeners and port to TS

* Replace tooltip with simple form instructions on the one place used

Tooltips were only used at the one place in our application, when changing a users password.
We can simply remove it there and use a modern solution the next time we need a tooltip

* Move danger zone validation to frontend and port to TS

* Move colorcontrast to backlogs vendor where it's used now

* Remove contents of application.js.erb

* Move project and user scripts to dynamically loaded

* Move onboarding tour to dynamically loaded

* Use correct syntax for jQuery so that $ is  known keyword within this function

* Fix onboarding tour

* Fix onboarding tour for scrum tour

* Fix specs after moving JS

* Remove application.js.erb

* Move locales and ckeditor to the frontend to dynamically load

* Remove bundles caches

These are no longer being used since angular cli

* Ensure locales are exported before angular

Co-authored-by: Henriette Darge <h.darge@openproject.com>
pull/8502/head
Oliver Günther 4 years ago committed by GitHub
parent 97fad89177
commit 10e21154f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      .gitignore
  2. 3
      .jshintignore
  3. 3
      .travis.yml
  4. 3
      Gemfile.lock
  5. 100
      app/assets/javascripts/action_menu.js
  6. 397
      app/assets/javascripts/application.js.erb
  7. 83
      app/assets/javascripts/breadcrumb.js
  8. 63
      app/assets/javascripts/colors.js
  9. 11
      app/assets/javascripts/date-de-DE.js
  10. 11
      app/assets/javascripts/date-en-US.js
  11. 62
      app/assets/javascripts/findDomElement.js
  12. 29
      app/assets/javascripts/jquery_noconflict.js
  13. 30
      app/assets/javascripts/lib/jquery.is_visible_within.js
  14. 203
      app/assets/javascripts/lib/jquery.trap.js
  15. 50
      app/assets/javascripts/onboarding/backlogs_tour.js
  16. 45
      app/assets/javascripts/onboarding/boards_tour.js
  17. 35
      app/assets/javascripts/onboarding/homescreen_tour.js
  18. 22
      app/assets/javascripts/onboarding/menu_tour.js
  19. 149
      app/assets/javascripts/onboarding/onboarding_tour.js
  20. 70
      app/assets/javascripts/onboarding/work_package_tour.js
  21. 36
      app/assets/javascripts/openproject_plugins.js.erb
  22. 66
      app/assets/javascripts/select_list_move.js
  23. 149
      app/assets/javascripts/settings.js.erb
  24. 34
      app/assets/javascripts/styleguide.js
  25. 21
      app/assets/javascripts/tooltips.js
  26. 283
      app/assets/javascripts/top_menu.js
  27. 143
      app/assets/javascripts/types_checkboxes.js
  28. 72
      app/assets/javascripts/versions.js
  29. 2
      app/cells/views/members/role_form/show.erb
  30. 4
      app/helpers/application_helper.rb
  31. 2
      app/helpers/password_helper.rb
  32. 1
      app/helpers/text_formatting_helper.rb
  33. 3
      app/views/custom_fields/_custom_fields_header.html.erb
  34. 2
      app/views/custom_fields/edit.html.erb
  35. 1
      app/views/custom_fields/new.html.erb
  36. 6
      app/views/layouts/base.html.erb
  37. 6
      app/views/members/index.html.erb
  38. 4
      app/views/messages/show.html.erb
  39. 8
      app/views/my/_password_form_fields.html.erb
  40. 4
      app/views/projects/_form.html.erb
  41. 8
      app/views/projects/form/_types.html.erb
  42. 8
      app/views/projects/form/attributes/_identifier.html.erb
  43. 3
      app/views/projects/index.html.erb
  44. 2
      app/views/repositories/_repository_header.html.erb
  45. 4
      app/views/repositories/project_settings.html.erb
  46. 8
      app/views/roles/report.html.erb
  47. 10
      app/views/settings/_display.html.erb
  48. 7
      app/views/settings/_repositories.html.erb
  49. 5
      app/views/timelog/_date_range.html.erb
  50. 4
      app/views/users/_form.html.erb
  51. 4
      app/views/users/_simple_form.html.erb
  52. 4
      app/views/users/new.html.erb
  53. 6
      app/views/workflows/_form.html.erb
  54. 7
      bin/setup_dev
  55. 2
      config/i18n-js.yml
  56. 19
      config/initializers/assets.rb
  57. 8
      frontend/npm-shrinkwrap.json
  58. 3
      frontend/package.json
  59. 11
      frontend/src/app/angular4-modules.ts
  60. 4
      frontend/src/app/components/work-packages/work-package-cache.service.spec.ts
  61. 32
      frontend/src/app/globals/global-helpers.ts
  62. 39
      frontend/src/app/globals/global-listeners.ts
  63. 94
      frontend/src/app/globals/global-listeners/action-menu.ts
  64. 49
      frontend/src/app/globals/global-listeners/color-preview.ts
  65. 34
      frontend/src/app/globals/global-listeners/danger-zone-validation.ts
  66. 122
      frontend/src/app/globals/global-listeners/settings.ts
  67. 158
      frontend/src/app/globals/global-listeners/setup-server-response.ts
  68. 14
      frontend/src/app/globals/global-listeners/toggable-fieldset.ts
  69. 255
      frontend/src/app/globals/global-listeners/top-menu.ts
  70. 39
      frontend/src/app/globals/onboarding/helpers.ts
  71. 94
      frontend/src/app/globals/onboarding/onboarding_tour.ts
  72. 68
      frontend/src/app/globals/onboarding/onboarding_tour_trigger.ts
  73. 52
      frontend/src/app/globals/onboarding/tours/backlogs_tour.ts
  74. 44
      frontend/src/app/globals/onboarding/tours/boards_tour.ts
  75. 35
      frontend/src/app/globals/onboarding/tours/homescreen_tour.ts
  76. 20
      frontend/src/app/globals/onboarding/tours/menu_tour.ts
  77. 70
      frontend/src/app/globals/onboarding/tours/work_package_tour.ts
  78. 3
      frontend/src/app/globals/openproject.ts
  79. 1
      frontend/src/app/init-globals.ts
  80. 2
      frontend/src/app/init-vendors.ts
  81. 10
      frontend/src/app/modules/admin/types/type-form-configuration.component.ts
  82. 3
      frontend/src/app/modules/admin/types/type-form-configuration.html
  83. 0
      frontend/src/app/modules/augmenting/dynamic-scripts/admin_users.js
  84. 48
      frontend/src/app/modules/augmenting/dynamic-scripts/administration_settings.js
  85. 30
      frontend/src/app/modules/augmenting/dynamic-scripts/backlogs.js
  86. 0
      frontend/src/app/modules/augmenting/dynamic-scripts/backlogs/backlog.js
  87. 0
      frontend/src/app/modules/augmenting/dynamic-scripts/backlogs/burndown.js
  88. 161
      frontend/src/app/modules/augmenting/dynamic-scripts/backlogs/common.js
  89. 0
      frontend/src/app/modules/augmenting/dynamic-scripts/backlogs/editable_inplace.js
  90. 0
      frontend/src/app/modules/augmenting/dynamic-scripts/backlogs/impediment.js
  91. 0
      frontend/src/app/modules/augmenting/dynamic-scripts/backlogs/master_backlog.js
  92. 0
      frontend/src/app/modules/augmenting/dynamic-scripts/backlogs/model.js
  93. 0
      frontend/src/app/modules/augmenting/dynamic-scripts/backlogs/show_main.js
  94. 0
      frontend/src/app/modules/augmenting/dynamic-scripts/backlogs/sprint.js
  95. 0
      frontend/src/app/modules/augmenting/dynamic-scripts/backlogs/story.js
  96. 0
      frontend/src/app/modules/augmenting/dynamic-scripts/backlogs/task.js
  97. 0
      frontend/src/app/modules/augmenting/dynamic-scripts/backlogs/taskboard.js
  98. 0
      frontend/src/app/modules/augmenting/dynamic-scripts/backlogs/work_package.js
  99. 0
      frontend/src/app/modules/augmenting/dynamic-scripts/custom_fields.js
  100. 0
      frontend/src/app/modules/augmenting/dynamic-scripts/forums.js
  101. Some files were not shown because too many files have changed in this diff Show More

2
.gitignore vendored

@ -51,9 +51,9 @@ npm-debug.log*
/backup /backup
/.project /.project
/.loadpath /.loadpath
/app/assets/javascripts/bundles/*.*
/app/assets/javascripts/editor/* /app/assets/javascripts/editor/*
/app/assets/javascripts/locales/*.* /app/assets/javascripts/locales/*.*
/frontend/src/locales/*.js
/config/additional_environment.rb /config/additional_environment.rb
/config/configuration.yml /config/configuration.yml
/config/database.yml /config/database.yml

@ -1,5 +1,2 @@
app/assets/javascripts/date-de-DE.js
app/assets/javascripts/date-en-US.js
app/assets/javascripts/jquery_noconflict.js
app/assets/javascripts/project/**/* app/assets/javascripts/project/**/*
app/assets/javascripts/lib/**/* app/assets/javascripts/lib/**/*

@ -38,8 +38,7 @@ cache:
bundler: true bundler: true
directories: directories:
- public/assets - public/assets
- app/assets/javascripts/bundles - frontend/src/locales
- app/assets/javascripts/locales
branches: branches:
only: only:

@ -173,7 +173,6 @@ PATH
remote: modules/reporting remote: modules/reporting
specs: specs:
openproject-reporting (1.0.0) openproject-reporting (1.0.0)
jquery-tablesorter (~> 1.27.0)
openproject-costs openproject-costs
reporting_engine reporting_engine
@ -559,8 +558,6 @@ GEM
iso8601 (0.12.1) iso8601 (0.12.1)
jaro_winkler (1.5.4) jaro_winkler (1.5.4)
jmespath (1.4.0) jmespath (1.4.0)
jquery-tablesorter (1.27.1)
railties (>= 3.2)
json (2.3.0) json (2.3.0)
json-jwt (1.11.0) json-jwt (1.11.0)
activesupport (>= 4.2) activesupport (>= 4.2)

@ -1,100 +0,0 @@
//-- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2020 the OpenProject GmbH
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2017 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// 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.
//
// See docs/COPYRIGHT.rdoc for more details.
//++
/*
The action menu is a menu that usually belongs to an OpenProject entity (like an Issue, WikiPage, Meeting, ..).
Most likely it looks like this:
<ul class="action_menu_main">
<li><a>Menu item text</a></li>
<li><a>Menu item text</a></li>
<li class="drop-down">
<a class="icon icon-more" href="#">More functions</a>
<ul style="display:none;" class="menu-drop-down-container">
<li><a>Menu item text</a></li>
</ul>
</li>
</ul>
The following code is responsible to open and close the "more functions" submenu.
*/
jQuery(function ($) {
var animationSpeed = 100; // ms
function menu_top_position(menu) {
// if an h2 tag follows the submenu should unfold out at the border
var menu_start_position;
if (menu.next().get(0) != undefined && (menu.next().get(0).tagName == 'H2')) {
menu_start_position = menu.next().innerHeight() + menu.next().position().top;
}
else if (menu.next().hasClass("wiki-content") && menu.next().children().next().first().get(0) != undefined && menu.next().children().next().first().get(0).tagName == 'H1') {
var wiki_heading = menu.next().children().next().first();
menu_start_position = wiki_heading.innerHeight() + wiki_heading.position().top;
}
return menu_start_position;
}
function close_menu(event) {
var menu = $(event.data.menu);
// do not close the menu, if the user accidentally clicked next to a menu item (but still within the menu)
if (event.target !== menu.find(" > li.drop-down.open > ul").get(0)) {
menu.find(" > li.drop-down.open").removeClass("open").find("> ul").slideUp(animationSpeed);
// no need to watch for clicks, when the menu is already closed
$('html').off('click', close_menu);
}
}
function open_menu(menu) {
var drop_down = menu.find(" > li.drop-down");
// do not open a menu, which is already open
if (!drop_down.hasClass('open')) {
drop_down.find('> ul').slideDown(animationSpeed, function () {
drop_down.find('li > a:first').focus();
// when clicking on something, which is not the menu, close the menu
$('html').on('click', {menu: menu.get(0)}, close_menu);
});
drop_down.addClass('open');
}
}
// open the given submenu when clicking on it
function install_menu_logic(menu) {
menu.find(" > li.drop-down").click(function (event) {
open_menu(menu);
// and prevent default action (href) for that element
// but not for the menu items.
var target = $(event.target);
if (target.is('.drop-down') || target.closest('li, ul').is('.drop-down')) {
event.preventDefault();
}
});
}
$('.project-actions, .toolbar-items').each(function (idx, menu) {
install_menu_logic($(menu));
});
});

@ -1,397 +0,0 @@
//-- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2020 the OpenProject GmbH
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2013 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// 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.
//
// See docs/COPYRIGHT.rdoc for more details.
//++
//= require lib/jquery.colorcontrast
//= require lib/jquery.trap
//= require lib/jquery.is_visible_within
//= require top_menu
//= require action_menu
//= require breadcrumb
//= require findDomElement
//= require settings
//= require openproject_plugins
//= require versions
//= require forums
//= require_tree ./specific
//= require custom-fields
//= require date-range
//= require search
//= require colors
//= require tooltips
//= require danger_zone_validation
//= require flash_messages
//= require_tree ./onboarding
function checkAll(selector, checked) {
jQuery('#' + selector + ' input:checkbox').not(':disabled').each(function() {
this.checked = checked;
});
}
function toggleCheckboxesBySelector(selector) {
boxes = jQuery(selector);
var all_checked = true;
for (i = 0; i < boxes.length; i++) { if (boxes[i].checked === false) { all_checked = false; } }
for (i = 0; i < boxes.length; i++) { boxes[i].checked = !all_checked; }
}
function setCheckboxesBySelector(checked, selector) {
var boxes = $(selector);
boxes.each(function(ele) {
ele.checked = checked;
});
}
var fileFieldCount = 1;
function addFileField() {
fileFieldCount++;
if (fileFieldCount >= 10) return false;
var clone = jQuery('#attachment_template').clone(true);
clone.removeAttr('id');
clone.html(clone.html().replace(/\[1\]/g, '['+ fileFieldCount + ']'));
jQuery('#attachments_fields').append(clone);
}
function randomKey(size) {
var chars = new Array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z');
var key = '';
for (i = 0; i < size; i++) {
key += chars[Math.floor(Math.random() * chars.length)];
}
return key;
}
// Automatic project identifier generation
var projectIdentifierLocked;
var projectIdentifierDefault;
var projectIdentifierMaxLength;
function generateProjectIdentifier() {
var identifier = jQuery('#project_name').val(); // project name
var diacriticsMap = [
{'base':'a', 'letters':/[\u0061\u24D0\uFF41\u1E9A\u00E0\u00E1\u00E2\u1EA7\u1EA5\u1EAB\u1EA9\u00E3\u0101\u0103\u1EB1\u1EAF\u1EB5\u1EB3\u0227\u01E1\u01DF\u1EA3\u00E5\u01FB\u01CE\u0201\u0203\u1EA1\u1EAD\u1EB7\u1E01\u0105\u2C65\u0250\u0041\u24B6\uFF21\u00C0\u00C1\u00C2\u1EA6\u1EA4\u1EAA\u1EA8\u00C3\u0100\u0102\u1EB0\u1EAE\u1EB4\u1EB2\u0226\u01E0\u01DE\u1EA2\u00C5\u01FA\u01CD\u0200\u0202\u1EA0\u1EAC\u1EB6\u1E00\u0104\u023A\u2C6F]/g},
{'base':'aa','letters':/[\uA733\uA732]/g},
{'base':'ae','letters':/[\u00E4\u00E6\u01FD\u01E3\u00C4\u00C6\u01FC\u01E2]/g},
{'base':'ao','letters':/[\uA735\uA734]/g},
{'base':'au','letters':/[\uA737\uA736]/g},
{'base':'av','letters':/[\uA739\uA73B\uA738\uA73A]/g},
{'base':'ay','letters':/[\uA73D\uA73C]/g},
{'base':'b', 'letters':/[\u0062\u24D1\uFF42\u1E03\u1E05\u1E07\u0180\u0183\u0253\u0042\u24B7\uFF22\u1E02\u1E04\u1E06\u0243\u0182\u0181]/g},
{'base':'c', 'letters':/[\u0063\u24D2\uFF43\u0107\u0109\u010B\u010D\u00E7\u1E09\u0188\u023C\uA73F\u2184\u0043\u24B8\uFF23\u0106\u0108\u010A\u010C\u00C7\u1E08\u0187\u023B\uA73E]/g},
{'base':'d', 'letters':/[\u0064\u24D3\uFF44\u1E0B\u010F\u1E0D\u1E11\u1E13\u1E0F\u0111\u018C\u0256\u0257\uA77A\u0044\u24B9\uFF24\u1E0A\u010E\u1E0C\u1E10\u1E12\u1E0E\u0110\u018B\u018A\u0189\uA779]/g},
{'base':'dz','letters':/[\u01F3\u01C6\u01F1\u01C4\u01F2\u01C5]/g},
{'base':'e', 'letters':/[\u0065\u24D4\uFF45\u00E8\u00E9\u00EA\u1EC1\u1EBF\u1EC5\u1EC3\u1EBD\u0113\u1E15\u1E17\u0115\u0117\u00EB\u1EBB\u011B\u0205\u0207\u1EB9\u1EC7\u0229\u1E1D\u0119\u1E19\u1E1B\u0247\u025B\u01DD\u0045\u24BA\uFF25\u00C8\u00C9\u00CA\u1EC0\u1EBE\u1EC4\u1EC2\u1EBC\u0112\u1E14\u1E16\u0114\u0116\u00CB\u1EBA\u011A\u0204\u0206\u1EB8\u1EC6\u0228\u1E1C\u0118\u1E18\u1E1A\u0190\u018E]/g},
{'base':'f', 'letters':/[\u0066\u24D5\uFF46\u1E1F\u0192\uA77C\u0046\u24BB\uFF26\u1E1E\u0191\uA77B]/g},
{'base':'g', 'letters':/[\u0067\u24D6\uFF47\u01F5\u011D\u1E21\u011F\u0121\u01E7\u0123\u01E5\u0260\uA7A1\u1D79\uA77F\u0047\u24BC\uFF27\u01F4\u011C\u1E20\u011E\u0120\u01E6\u0122\u01E4\u0193\uA7A0\uA77D\uA77E]/g},
{'base':'h', 'letters':/[\u0068\u24D7\uFF48\u0125\u1E23\u1E27\u021F\u1E25\u1E29\u1E2B\u1E96\u0127\u2C68\u2C76\u0265\u0048\u24BD\uFF28\u0124\u1E22\u1E26\u021E\u1E24\u1E28\u1E2A\u0126\u2C67\u2C75\uA78D]/g},
{'base':'hv','letters':/[\u0195]/g},
{'base':'i', 'letters':/[\u0069\u24D8\uFF49\u00EC\u00ED\u00EE\u0129\u012B\u012D\u00EF\u1E2F\u1EC9\u01D0\u0209\u020B\u1ECB\u012F\u1E2D\u0268\u0131\u0049\u24BE\uFF29\u00CC\u00CD\u00CE\u0128\u012A\u012C\u0130\u00CF\u1E2E\u1EC8\u01CF\u0208\u020A\u1ECA\u012E\u1E2C\u0197]/g},
{'base':'j', 'letters':/[\u006A\u24D9\uFF4A\u0135\u01F0\u0249\u004A\u24BF\uFF2A\u0134\u0248]/g},
{'base':'k', 'letters':/[\u006B\u24DA\uFF4B\u1E31\u01E9\u1E33\u0137\u1E35\u0199\u2C6A\uA741\uA743\uA745\uA7A3\u004B\u24C0\uFF2B\u1E30\u01E8\u1E32\u0136\u1E34\u0198\u2C69\uA740\uA742\uA744\uA7A2]/g},
{'base':'l', 'letters':/[\u006C\u24DB\uFF4C\u0140\u013A\u013E\u1E37\u1E39\u013C\u1E3D\u1E3B\u017F\u0142\u019A\u026B\u2C61\uA749\uA781\uA747\u004C\u24C1\uFF2C\u013F\u0139\u013D\u1E36\u1E38\u013B\u1E3C\u1E3A\u0141\u023D\u2C62\u2C60\uA748\uA746\uA780]/g},
{'base':'lj','letters':/[\u01C9\u01C7\u01C8]/g},
{'base':'m', 'letters':/[\u006D\u24DC\uFF4D\u1E3F\u1E41\u1E43\u0271\u026F\u004D\u24C2\uFF2D\u1E3E\u1E40\u1E42\u2C6E\u019C]/g},
{'base':'n', 'letters':/[\u006E\u24DD\uFF4E\u01F9\u0144\u00F1\u1E45\u0148\u1E47\u0146\u1E4B\u1E49\u019E\u0272\u0149\uA791\uA7A5\u004E\u24C3\uFF2E\u01F8\u0143\u00D1\u1E44\u0147\u1E46\u0145\u1E4A\u1E48\u0220\u019D\uA790\uA7A4]/g},
{'base':'nj','letters':/[\u01CC\u01CA\u01CB]/g},
{'base':'o', 'letters':/[\u006F\u24DE\uFF4F\u00F2\u00F3\u00F4\u1ED3\u1ED1\u1ED7\u1ED5\u00F5\u1E4D\u022D\u1E4F\u014D\u1E51\u1E53\u014F\u022F\u0231\u022B\u1ECF\u0151\u01D2\u020D\u020F\u01A1\u1EDD\u1EDB\u1EE1\u1EDF\u1EE3\u1ECD\u1ED9\u01EB\u01ED\u00F8\u01FF\u0254\uA74B\uA74D\u0275\u004F\u24C4\uFF2F\u00D2\u00D3\u00D4\u1ED2\u1ED0\u1ED6\u1ED4\u00D5\u1E4C\u022C\u1E4E\u014C\u1E50\u1E52\u014E\u022E\u0230\u022A\u1ECE\u0150\u01D1\u020C\u020E\u01A0\u1EDC\u1EDA\u1EE0\u1EDE\u1EE2\u1ECC\u1ED8\u01EA\u01EC\u00D8\u01FE\u0186\u019F\uA74A\uA74C]/g},
{'base':'oe','letters': /[\u00F6\u0153\u00D6\u0152]/g},
{'base':'oi','letters':/[\u01A3\u01A2]/g},
{'base':'ou','letters':/[\u0223\u0222]/g},
{'base':'oo','letters':/[\uA74F\uA74E]/g},
{'base':'p','letters':/[\u0070\u24DF\uFF50\u1E55\u1E57\u01A5\u1D7D\uA751\uA753\uA755\u0050\u24C5\uFF30\u1E54\u1E56\u01A4\u2C63\uA750\uA752\uA754]/g},
{'base':'q','letters':/[\u0071\u24E0\uFF51\u024B\uA757\uA759\u0051\u24C6\uFF31\uA756\uA758\u024A]/g},
{'base':'r','letters':/[\u0072\u24E1\uFF52\u0155\u1E59\u0159\u0211\u0213\u1E5B\u1E5D\u0157\u1E5F\u024D\u027D\uA75B\uA7A7\uA783\u0052\u24C7\uFF32\u0154\u1E58\u0158\u0210\u0212\u1E5A\u1E5C\u0156\u1E5E\u024C\u2C64\uA75A\uA7A6\uA782]/g},
{'base':'s','letters':/[\u0073\u24E2\uFF53\u015B\u1E65\u015D\u1E61\u0161\u1E67\u1E63\u1E69\u0219\u015F\u023F\uA7A9\uA785\u1E9B\u0053\u24C8\uFF33\u1E9E\u015A\u1E64\u015C\u1E60\u0160\u1E66\u1E62\u1E68\u0218\u015E\u2C7E\uA7A8\uA784]/g},
{'base':'ss','letters':/[\u00DF]/g},
{'base':'t','letters':/[\u0074\u24E3\uFF54\u1E6B\u1E97\u0165\u1E6D\u021B\u0163\u1E71\u1E6F\u0167\u01AD\u0288\u2C66\uA787\u0054\u24C9\uFF34\u1E6A\u0164\u1E6C\u021A\u0162\u1E70\u1E6E\u0166\u01AC\u01AE\u023E\uA786]/g},
{'base':'tz','letters':/[\uA729\uA728]/g},
{'base':'u','letters':/[\u0075\u24E4\uFF55\u00F9\u00FA\u00FB\u0169\u1E79\u016B\u1E7B\u016D\u01DC\u01D8\u01D6\u01DA\u1EE7\u016F\u0171\u01D4\u0215\u0217\u01B0\u1EEB\u1EE9\u1EEF\u1EED\u1EF1\u1EE5\u1E73\u0173\u1E77\u1E75\u0289\u0055\u24CA\uFF35\u00D9\u00DA\u00DB\u0168\u1E78\u016A\u1E7A\u016C\u01DB\u01D7\u01D5\u01D9\u1EE6\u016E\u0170\u01D3\u0214\u0216\u01AF\u1EEA\u1EE8\u1EEE\u1EEC\u1EF0\u1EE4\u1E72\u0172\u1E76\u1E74\u0244]/g},
{'base':'ue','letters':/[\u00FC\u00DC]/g},
{'base':'v','letters':/[\u0076\u24E5\uFF56\u1E7D\u1E7F\u028B\uA75F\u028C\u0056\u24CB\uFF36\u1E7C\u1E7E\u01B2\uA75E\u0245]/g},
{'base':'vy','letters':/[\uA761\uA760]/g},
{'base':'w','letters':/[\u0077\u24E6\uFF57\u1E81\u1E83\u0175\u1E87\u1E85\u1E98\u1E89\u2C73\u0057\u24CC\uFF37\u1E80\u1E82\u0174\u1E86\u1E84\u1E88\u2C72]/g},
{'base':'x','letters':/[\u0078\u24E7\uFF58\u1E8B\u1E8D\u0058\u24CD\uFF38\u1E8A\u1E8C]/g},
{'base':'y','letters':/[\u0079\u24E8\uFF59\u1EF3\u00FD\u0177\u1EF9\u0233\u1E8F\u00FF\u1EF7\u1E99\u1EF5\u01B4\u024F\u1EFF\u0059\u24CE\uFF39\u1EF2\u00DD\u0176\u1EF8\u0232\u1E8E\u0178\u1EF6\u1EF4\u01B3\u024E\u1EFE]/g},
{'base':'z','letters':/[\u007A\u24E9\uFF5A\u017A\u1E91\u017C\u017E\u1E93\u1E95\u01B6\u0225\u0240\u2C6C\uA763\u005A\u24CF\uFF3A\u0179\u1E90\u017B\u017D\u1E92\u1E94\u01B5\u0224\u2C7F\u2C6B\uA762]/g}
];
for(var i=0; i<diacriticsMap.length; i++) {
identifier = identifier.replace(diacriticsMap[i].letters, diacriticsMap[i].base);
}
identifier = identifier.replace(/[^a-z0-9]+/gi, '-'); // remaining non-alphanumeric => hyphen
identifier = identifier.replace(/^[-\d]*|-*$/g, ''); // remove hyphens and numbers at beginning and hyphens at end
identifier = identifier.toLowerCase(); // to lower
identifier = identifier.substr(0,projectIdentifierMaxLength); // max characters
return identifier;
}
function observeProjectName() {
jQuery('#project_name').keyup(function() {
if(!projectIdentifierLocked) {
jQuery('#project_identifier').val(generateProjectIdentifier());
}
});
}
function observeProjectIdentifier() {
jQuery('#project_identifier').keyup(function() {
if(this.value !== '' && this.value != generateProjectIdentifier()) {
projectIdentifierLocked = true;
} else {
projectIdentifierLocked = false;
}
});
}
function hideOnLoad() {
jQuery('.hol').hide();
}
jQuery(hideOnLoad);
function addClickEventToAllErrorMessages() {
jQuery('a.afocus').each(function() {
var target = jQuery(this);
target.click(function(evt) {
var field = jQuery('#' + target.readAttribute('href').substr(1));
if (field === null) {
// Cut off '_id' (necessary for select boxes)
field = jQuery('#' + target.readAttribute('href').substr(1).concat('_id'));
}
if (field) {
field.down('input, textarea, select').focus();
}
target.unbind(evt);
return false;
});
});
}
// a few constants for animations speeds, etc.
var animationRate = 100;
/* jQuery code from #263 */
// returns viewport height
jQuery.viewportHeight = function() {
return self.innerHeight ||
jQuery.boxModel && document.documentElement.clientHeight ||
document.body.clientHeight;
};
/*
* 1 - registers a callback which copies the csrf token into the
* X-CSRF-Token header with each ajax request. Necessary to
* work with rails applications which have fixed
* CVE-2011-0447
* 2 - shows and hides ajax indicator
*/
jQuery(document).ready(function($) {
jQuery(document).ajaxSend(function (event, request) {
if ($(event.target.activeElement).closest('[ajax-indicated]').length &&
$('ajax-indicator')) {
$('#ajax-indicator').show();
}
var csrf_meta_tag = $('meta[name=csrf-token]');
if (csrf_meta_tag) {
var header = 'X-CSRF-Token',
token = csrf_meta_tag.attr('content');
request.setRequestHeader(header, token);
}
request.setRequestHeader('X-Authentication-Scheme', "Session");
});
// ajaxStop gets called when ALL Requests finish, so we won't need a counter as in PT
jQuery(document).ajaxStop(function () {
if ($('#ajax-indicator')) {
$('#ajax-indicator').hide();
}
addClickEventToAllErrorMessages();
});
// show/hide the files table
jQuery(".attachments h4").click(function() {
jQuery(this).toggleClass("closed").next().slideToggle(animationRate);
});
jQuery(window).resize(function() {
// wait 200 milliseconds for no further resize event
// then readjust breadcrumb
if(this.resizeTO) clearTimeout(this.resizeTO);
this.resizeTO = setTimeout(function() {
jQuery(this).trigger('resizeEnd');
}, 200);
});
// Do not close the login window when using it
jQuery('#nav-login-content').click(function(event){
event.stopPropagation();
});
// Set focus on first error message
var error_focus = $('a.afocus').first();
var input_focus = $('.autofocus').first();
if (error_focus !== undefined) {
error_focus.focus();
}
else if (input_focus !== undefined){
input_focus.focus();
if (input_focus.tagName === "INPUT") {
input_focus.select();
}
}
// Focus on field with error
addClickEventToAllErrorMessages();
// Skip menu on content
jQuery('#skip-navigation--content').click(skipMenu);
// Click handler for formatting help
jQuery(document.body).on('click', '.formatting-help-link-button', function() {
window.open(window.appBasePath + '/help/wiki_syntax',
"",
"resizable=yes, location=no, width=600, height=640, menubar=no, status=no, scrollbars=yes"
);
return false;
});
});
var Administration = (function ($) {
var update_default_language_options,
init_language_selection_handling,
toggle_default_language_select;
update_default_language_options = function (input) {
var default_language_select = $('#setting_default_language select'),
default_language_select_active;
if (input.attr('checked')) {
default_language_select.find('option[value="' + input.val() + '"]').removeAttr('disabled');
} else {
default_language_select.find('option[value="' + input.val() + '"]').attr('disabled', 'disabled');
}
default_language_select_active = default_language_select.find('option:not([disabled="disabled"])');
toggle_disabled_state(default_language_select_active.length === 0);
if (default_language_select_active.length === 1) {
default_language_select_active.attr('selected', true);
} else if (default_language_select.val() === input.val() && !input.attr('checked')) {
default_language_select_active.first().attr('selected', true);
}
};
toggle_disabled_state = function (active) {
jQuery('#setting_default_language select').attr('disabled', active)
.closest('form')
.find('input:submit')
.attr('disabled', active);
};
init_language_selection_handling = function () {
jQuery('#setting_available_languages input:not([checked="checked"])').each(function (index, input) {
update_default_language_options($(input));
});
jQuery('#setting_available_languages input').click(function () {
update_default_language_options($(this));
});
};
return {
init_language_selection_handling: init_language_selection_handling
};
}(jQuery));
var activateFlash = function(selector) {
var flashMessages = jQuery(selector);
// Ignore flash messages of class 'ignored-by-flash-activation' because those
// messages are completely handled via JavaScript (see types_checkboxes.js for
// details). We wouldn't have to ignore this message if the flash element
// would be completely created via JavaScript and not available in the DOM by
// default.
flashMessages.each(function (ix, e) {
flashMessage = jQuery(e);
if (!flashMessage.hasClass('ignored-by-flash-activation')) {
flashMessage.show();
}
});
};
var activateFlashNotice = function () {
var notice = '.flash';
activateFlash(notice);
};
var activateFlashError = function () {
var error = '.errorExplanation[role="alert"]';
activateFlash(error);
};
var focusFirstErroneousField = function() {
var firstErrorSpan = jQuery('span.errorSpan').first();
var erroneousInput = firstErrorSpan.find('*').filter(":input");
erroneousInput.focus();
};
function initMainMenuExpandStatus() {
let wrapper = jQuery('#wrapper');
let upToggle = jQuery('ul.menu_root.closed li.open a.arrow-left-to-project');
if (upToggle.length === 1 && wrapper.hasClass('hidden-navigation')) {
upToggle.trigger('click');
}
}
var setupServerResponse = function() {
initMainMenuExpandStatus();
focusFirstErroneousField();
activateFlashNotice();
activateFlashError();
jQuery(document).ajaxComplete(activateFlashNotice);
jQuery(document).ajaxComplete(activateFlashError);
};
jQuery(document).ready(setupServerResponse);

@ -1,83 +0,0 @@
//-- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2020 the OpenProject GmbH
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2017 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// 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.
//
// See docs/COPYRIGHT.rdoc for more details.
//++
jQuery.fn.reverse = [].reverse;
(function($){
$.fn.adjustBreadcrumbToWindowSize = function(){
var breadcrumbElements = this.find(' > li');
var breadcrumb = this;
var lastChanged;
if (breadcrumb.breadcrumbOutOfBounds()){
breadcrumbElements.each(function(index) {
if (breadcrumb.breadcrumbOutOfBounds()){
if (!$(this).find(' > a').hasClass('nocut')){
$(this).addClass('cutme ellipsis');
}
}
else {
return false;
}
});
}
else {
breadcrumbElements.reverse().each(function(index) {
if (!breadcrumb.breadcrumbOutOfBounds()){
if (!$(this).find(' > a').hasClass('nocut')){
$(this).removeClass('cutme ellipsis');
lastChanged = $(this);
}
}
});
if (breadcrumb.breadcrumbOutOfBounds()){
if (lastChanged != undefined){
lastChanged.addClass('cutme ellipsis');
return false;
}
}
}
};
$.fn.breadcrumbOutOfBounds = function(){
var lastElement = this.find(' > li').last();
if (lastElement) {
var rightCorner = lastElement.width() + lastElement.offset().left;
var windowSize = jQuery(window).width();
if ((Math.max(1000,windowSize) - rightCorner) < 10) {
return true;
}
else {
return false;
}
} else {
return false;
}
};
})(jQuery);

@ -1,63 +0,0 @@
//-- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2020 the OpenProject GmbH
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2017 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// 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.
//
// See docs/COPYRIGHT.rdoc for more details.
//++
(function ($) {
$(function() {
$('.color--preview').each(function() {
var preview, input, func, target;
preview = $(this);
target = preview.data('target');
if(target) {
input = $(target);
} else {
input = preview.next('input');
}
if (input.length === 0) {
return;
}
func = function () {
var previewColor = '';
if(input.val() && input.val().length > 0) {
previewColor = input.val();
} else if (input.attr('placeholder') &&
input.attr('placeholder').length > 0) {
previewColor = input.attr('placeholder')
}
preview.css('background-color', previewColor);
};
input.keyup(func).change(func).focus(func);
func();
});
});
}(jQuery));

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1,62 +0,0 @@
//-- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2020 the OpenProject GmbH
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2017 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// 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.
//
// See docs/COPYRIGHT.rdoc for more details.
//++
(function( $ ){
$.fn.nextElementInDom = function(selector, options) {
return $(this).findElementInDom(selector, $.extend(options, { direction: 'front' }));
};
$.fn.previousElementInDom = function(selector, options) {
return $(this).findElementInDom(selector, $.extend(options, { direction: 'back' }));
};
$.fn.findElementInDom = function(selector, options) {
var defaults, parent, direction, found, children;
defaults = { stopAt : 'body', direction: 'front' };
options = $.extend(defaults, options);
parent = $(this).parent();
direction = (options.direction === 'front') ? ":gt" : ":lt";
children = parent.children(direction + "(" + $(this).index() + ")");
children = (options.direction === 'front') ? children : children.reverse();
found = parent.children(direction + "(" + $(this).index() + ")").find(selector).filter(":first");
if (found.length > 0) {
return found;
} else {
if (parent.length === 0 || parent.is(options.stopAt)) {
return $([]);
} else {
return parent.findElementInDom(selector, options);
}
}
};
})( jQuery );

@ -1,29 +0,0 @@
//-- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2020 the OpenProject GmbH
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2017 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// 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.
//
// See docs/COPYRIGHT.rdoc for more details.
//++
$.noConflict();

@ -1,30 +0,0 @@
/**
* Copyright 2012, Digital Fusion
* Licensed under the MIT license.
* http://teamdf.com/jquery-plugins/license/
*
* @author Sam Sehnert
* @desc A small plugin that checks whether elements are within
* the user visible viewport of a web browser.
* only accounts for vertical position, not horizontal.
*
* Extended here to include an optional container used as parent,
* as the original plugin only supports window.
*/
(function ($) {
$.fn.isVisibleWithin = function (container, partial, hidden) {
var $t = $(this).eq(0),
t = $t.get(0),
$w = (container != null ? container : $(window)),
viewTop = (container != null ? container.offset().top : $w.scrollTop()),
viewBottom = viewTop + $w.height(),
_top = $t.offset().top,
_bottom = _top + $t.height(),
compareTop = partial === true ? _bottom : _top,
compareBottom = partial === true ? _top : _bottom,
clientSize = hidden === true ? t.offsetWidth * t.offsetHeight : true;
return !!clientSize && ((compareBottom <= viewBottom) && (compareTop >= viewTop));
};
})(jQuery);

@ -1,203 +0,0 @@
/*!
Copyright (c) 2011, 2012 Julien Wajsberg <felash@gmail.com>
All rights reserved.
Official repository: https://github.com/julienw/jquery-trap-input
License is there: https://github.com/julienw/jquery-trap-input/blob/master/LICENSE
This is version 1.2.0.
*/
(function( $, undefined ){
/*
(this comment is after the first line of code so that uglifyjs removes it)
Redistribution and use in source and binary forms, with or without
modification, are permitted without condition.
Although that's not an obligation, I would appreciate that you provide a
link to the official repository.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED.
*/
/*jshint boss: true, bitwise: true, curly: true, expr: true, newcap: true, noarg: true, nonew: true, latedef: true, regexdash: true */
var DATA_ISTRAPPING_KEY = "trap.isTrapping";
function onkeypress(e) {
if (e.keyCode === 9) {
var goReverse = !!(e.shiftKey);
if (processTab(this, e.target, goReverse)) {
e.preventDefault();
e.stopPropagation();
}
}
}
// will return true if we could process the tab event
// otherwise, return false
function processTab(container, elt, goReverse) {
var $focussable = getFocusableElementsInContainer(container),
curElt = elt,
index, nextIndex, prevIndex, lastIndex;
do {
index = $focussable.index(curElt);
nextIndex = index + 1;
prevIndex = index - 1;
lastIndex = $focussable.length - 1;
switch(index) {
case -1:
return false; // that's strange, let the browser do its job
case 0:
prevIndex = lastIndex;
break;
case lastIndex:
nextIndex = 0;
break;
}
if (goReverse) {
nextIndex = prevIndex;
}
curElt = $focussable.get(nextIndex);
if (!curElt || curElt === elt) { return true; }
try {
curElt.focus();
} catch(e) { // IE sometimes throws when an element is not visible
return true;
}
} while ($focussable.length > 1 && elt === elt.ownerDocument.activeElement);
return true;
}
function filterKeepSpeciallyFocusable() {
return this.tabIndex > 0;
}
function filterKeepNormalElements() {
return !this.tabIndex; // true if no tabIndex or tabIndex == 0
}
function sortFocusable(a, b) {
return (a.t - b.t) || (a.i - b.i);
}
function getFocusableElementsInContainer(container) {
var $container = $(container);
var result = [],
cnt = 0;
fixIndexSelector.enable && fixIndexSelector.enable();
// leaving away command and details for now
$container.find("a[href], link[href], [draggable=true], [contenteditable=true], :input:enabled, [tabindex=0]")
.filter(":visible")
.filter(filterKeepNormalElements)
.each(function(i, val) {
result.push({
v: val, // value
t: 0, // tabIndex
i: cnt++ // index for stable sort
});
});
$container
.find("[tabindex]")
.filter(":visible")
.filter(filterKeepSpeciallyFocusable)
.each(function(i, val) {
result.push({
v: val, // value
t: val.tabIndex, // tabIndex
i: cnt++ // index
});
});
fixIndexSelector.disable && fixIndexSelector.disable();
result = $.map(result.sort(sortFocusable), // needs stable sort
function(val) {
return val.v;
}
);
return $(result);
}
function trap() {
this.keydown(onkeypress);
this.data(DATA_ISTRAPPING_KEY, true);
return this;
}
function untrap() {
this.unbind('keydown', onkeypress);
this.removeData(DATA_ISTRAPPING_KEY);
return this;
}
function isTrapping() {
return !!this.data(DATA_ISTRAPPING_KEY);
}
$.fn.extend({
trap: trap,
untrap: untrap,
isTrapping: isTrapping
});
// jQuery 1.6.x tabindex attr hooks management
// this triggers problems for tabindex attribute
// selectors in IE7-
// see https://github.com/julienw/jquery-trap-input/issues/3
var fixIndexSelector = {};
if ($.find.find && $.find.attr !== $.attr) {
// jQuery uses Sizzle (this is jQuery >= 1.3)
// sizzle uses its own attribute handling (in jq 1.6.x and below)
(function() {
var tabindexKey = "tabindex";
var sizzleAttrHandle = $.expr.attrHandle;
// this function comes directly from jQuery 1.7.2 (propHooks.tabIndex.get)
// we have to put it here if we want to support jQuery < 1.6 which
// doesn't have an attrHooks object to reference.
function getTabindexAttr(elem) {
// elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
// http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
var attributeNode = elem.getAttributeNode(tabindexKey);
return attributeNode && attributeNode.specified ?
parseInt( attributeNode.value, 10 ) :
undefined;
}
function fixSizzleAttrHook() {
// in jQ <= 1.6.x, we add to Sizzle the attrHook from jQuery's attr method
sizzleAttrHandle[tabindexKey] = sizzleAttrHandle.tabIndex = getTabindexAttr;
}
function unfixSizzleAttrHook() {
delete sizzleAttrHandle[tabindexKey];
delete sizzleAttrHandle.tabIndex;
}
fixIndexSelector = {
enable: fixSizzleAttrHook,
disable: unfixSizzleAttrHook
};
})();
}
})( jQuery );

@ -1,50 +0,0 @@
(function ($) {
$(function() {
window.scrumBacklogsTourSteps = [
{
'next #content-wrapper': I18n.t('js.onboarding.steps.backlogs.overview'),
'showSkip': false,
'nextButton': {text: I18n.t('js.onboarding.buttons.next')},
'containerClass': '-dark -hidden-arrow'
},
{
'event_type': 'next',
'selector': '#sprint_backlogs_container .backlog .menu-trigger',
'description': I18n.t('js.onboarding.steps.backlogs.task_board_arrow'),
'showSkip': false,
'nextButton': {text: I18n.t('js.onboarding.buttons.next')},
onNext: function () {
$('#sprint_backlogs_container .backlog .menu-trigger')[0].click();
}
},
{
'event_type': 'next',
'selector': '#sprint_backlogs_container .backlog .menu .items',
'description': I18n.t('js.onboarding.steps.backlogs.task_board_select'),
'showSkip': false,
'nextButton': {text: I18n.t('js.onboarding.buttons.next')},
'containerClass': '-dark',
onNext: function () {
$('#sprint_backlogs_container .backlog .show_task_board')[0].click();
}
}
];
window.scrumTaskBoardTourSteps = [
{
'next #content-wrapper': I18n.t('js.onboarding.steps.backlogs.task_board'),
'showSkip': false,
'nextButton': {text: I18n.t('js.onboarding.buttons.next')},
'containerClass': '-dark -hidden-arrow'
},
{
'next #main-menu-work-packages-wrapper': I18n.t('js.onboarding.steps.wp.toggler'),
'showSkip': false,
'nextButton': {text: I18n.t('js.onboarding.buttons.next')},
onNext: function () {
$('#main-menu-work-packages')[0].click();
}
},
];
});
}(jQuery))

@ -1,45 +0,0 @@
(function ($) {
$(function() {
window.boardTourSteps = [
{
'next .board-view-menu-item': I18n.t('js.onboarding.steps.boards.overview'),
'showSkip': false,
'nextButton': {text: I18n.t('js.onboarding.buttons.next')},
onNext: function () {
$('.board-view-menu-item ~ .toggler')[0].click();
waitForElement('.boards--menu-items', '#main-menu', function() {
$(".main-menu--children-sub-item:contains('Kanban')")[0].click();
});
}
},
{
'next .board-list--container': I18n.t('js.onboarding.steps.boards.lists'),
'showSkip': false,
'nextButton': {text: I18n.t('js.onboarding.buttons.next')},
'containerClass': '-dark -hidden-arrow',
'timeout': function() {
return new Promise(function(resolve) {
waitForElement('.wp-card', '#content', function() {
resolve();
});
});
}
},
{
'next .board-list--add-button': I18n.t('js.onboarding.steps.boards.add'),
'showSkip': false,
'nextButton': {text: I18n.t('js.onboarding.buttons.next')},
},
{
'next .boards-list--container': I18n.t('js.onboarding.steps.boards.drag'),
'showSkip': false,
'nextButton': {text: I18n.t('js.onboarding.buttons.next')},
'containerClass': '-dark -hidden-arrow',
onNext: function () {
$('.main-menu--arrow-left-to-project')[0].click();
}
}
];
});
}(jQuery))

@ -1,35 +0,0 @@
(function ($) {
$(function() {
window.homescreenOnboardingTourSteps = [
{
'next #top-menu': I18n.t('js.onboarding.steps.welcome'),
'skipButton': {className: 'enjoyhint_btn-transparent', text: I18n.t('js.onboarding.buttons.skip')},
'nextButton': {text: I18n.t('js.onboarding.buttons.next')},
'containerClass': '-hidden-arrow',
'bottom': 7
},
{
'description': I18n.t('js.onboarding.steps.project_selection'),
'selector': '.widget-box.welcome',
'event': 'custom',
'showSkip': false,
'containerClass': '-dark -hidden-arrow',
'clickable': true,
onBeforeStart: function () {
// Handle the correct project selection and redirection
// This will be removed once the project selection is implemented
jQuery(".widget-box.welcome a:contains(" + scrumDemoProjectName + ")").click(function () {
tutorialInstance.trigger('next');
window.location = this.href + '/backlogs/?start_scrum_onboarding_tour=true';
});
jQuery(".widget-box.welcome a:contains(" + demoProjectName + ")").click(function () {
tutorialInstance.trigger('next');
window.location = this.href + '/work_packages/?start_onboarding_tour=true';
});
// Disable clicks on other links
$('.widget-box.welcome a').addClass('-disabled').bind('click', preventClickHandler);
}
}
];
});
}(jQuery))

@ -1,22 +0,0 @@
(function ($) {
$(function() {
window.menuTourSteps = [
{
'next .members-menu-item': I18n.t('js.onboarding.steps.members'),
'showSkip': false,
'nextButton': {text: I18n.t('js.onboarding.buttons.next')},
},
{
'next .wiki-menu--main-item': I18n.t('js.onboarding.steps.wiki'),
'showSkip': false,
'nextButton': {text: I18n.t('js.onboarding.buttons.next')},
},
{
'next .menu-item--help': I18n.t('js.onboarding.steps.help_menu'),
'shape': 'circle',
'showSkip': false,
'nextButton': {text: I18n.t('js.onboarding.buttons.got_it')}
}
];
});
}(jQuery))

@ -1,149 +0,0 @@
(function ($) {
$(function() {
// ------------------------------- Global -------------------------------
window.tutorialInstance;
window.preventClickHandler = function (e) {
e.preventDefault();
e.stopPropagation();
};
window.waitForElement = function(element, container, execFunction) {
// Wait for the element to be ready
var observer = new MutationObserver(function (mutations, observerInstance) {
if ($(element).length) {
observerInstance.disconnect(); // stop observing
execFunction();
return;
}
});
observer.observe($(container)[0], {
childList: true,
subtree: true
});
};
window.demoProjectName = 'Demo project';
window.scrumDemoProjectName = 'Scrum project';
var storageKey = 'openProject-onboardingTour';
var currentTourPart = sessionStorage.getItem(storageKey);
var url = new URL(window.location.href);
var isMobile = document.body.classList.contains('-browser-mobile');
var demoProjectsAvailable = $('meta[name=demo_projects_available]').attr('content') === "true";
var boardsDemoDataAvailable = $('meta[name=boards_demo_data_available]').attr('content') === "true";
var eeTokenAvailable = !$('body').hasClass('ee-banners-visible');
var tourCancelled = false;
// ------------------------------- Initial start -------------------------------
// Do not show the tutorial on mobile or when the demo data has been deleted
if(!isMobile && demoProjectsAvailable) {
// Start after the intro modal (language selection)
// This has to be changed once the project selection is implemented
if (url.searchParams.get("first_time_user") && demoProjectsLinks().length == 2) {
currentTourPart = '';
sessionStorage.setItem(storageKey, 'readyToStart');
// Start automatically when the language selection is closed
$('.op-modal--modal-close-button').click(function () {
tourCancelled = true;
homescreenTour();
});
//Start automatically when the escape button is pressed
document.addEventListener('keydown', function(event) {
if (event.key == "Escape" && !tourCancelled) {
tourCancelled = true;
homescreenTour();
}
}, { once: true });
}
// ------------------------------- Tutorial Homescreen page -------------------------------
if (currentTourPart === "readyToStart") {
homescreenTour();
}
// ------------------------------- Tutorial WP page -------------------------------
if (currentTourPart === "startMainTourFromBacklogs" || url.searchParams.get("start_onboarding_tour")) {
mainTour();
}
// ------------------------------- Tutorial Backlogs page -------------------------------
if (url.searchParams.get("start_scrum_onboarding_tour")) {
if ($('.backlogs-menu-item').length > 0) {
backlogsTour();
}
}
// ------------------------------- Tutorial Task Board page -------------------------------
if (currentTourPart === "startTaskBoardTour") {
taskboardTour();
}
}
function demoProjectsLinks() {
demoProjects = [];
demoProjectsLink = jQuery(".widget-box.welcome a:contains(" + demoProjectName + ")");
scrumDemoProjectsLink = jQuery(".widget-box.welcome a:contains(" + scrumDemoProjectName + ")");
if (demoProjectsLink.length) demoProjects.push(demoProjectsLink);
if (scrumDemoProjectsLink.length) demoProjects.push(scrumDemoProjectsLink);
return demoProjects;
}
function initializeTour(storageValue, disabledElements, projectSelection) {
tutorialInstance = new EnjoyHint({
onStart: function () {
$('#content-wrapper, #menu-sidebar').addClass('-hidden-overflow');
},
onEnd: function () {
sessionStorage.setItem(storageKey, storageValue);
$('#content-wrapper, #menu-sidebar').removeClass('-hidden-overflow');
},
onSkip: function () {
sessionStorage.setItem(storageKey, 'skipped');
if (disabledElements) jQuery(disabledElements).removeClass('-disabled').unbind('click', preventClickHandler);
if (projectSelection) $.each(demoProjectsLinks(), function(i, e) { $(e).off('click')});
$('#content-wrapper, #menu-sidebar').removeClass('-hidden-overflow');
}
});
}
function startTour(steps) {
tutorialInstance.set(steps);
tutorialInstance.run();
}
function homescreenTour() {
initializeTour('startProjectTour', '.widget-box--blocks--buttons a', true);
startTour(homescreenOnboardingTourSteps);
}
function backlogsTour() {
initializeTour('startTaskBoardTour');
startTour(scrumBacklogsTourSteps);
}
function taskboardTour() {
initializeTour('startMainTourFromBacklogs');
startTour(scrumTaskBoardTourSteps);
}
function mainTour() {
initializeTour('mainTourFinished');
waitForElement('.work-package--results-tbody', '#content', function() {
var steps;
// Check for EE edition, and available seed data of boards.
// Then add boards to the tour, otherwise skip it.
if (eeTokenAvailable && boardsDemoDataAvailable) {
steps = wpOnboardingTourSteps.concat(boardTourSteps).concat(menuTourSteps);
} else {
steps = wpOnboardingTourSteps.concat(menuTourSteps);
}
startTour(steps);
});
}
});
}(jQuery));

@ -1,70 +0,0 @@
(function ($) {
$(function() {
window.wpOnboardingTourSteps = [
{
'next .wp-table--row': I18n.t('js.onboarding.steps.wp.list'),
'showSkip': false,
'nextButton': {text: I18n.t('js.onboarding.buttons.next')},
onNext: function () {
$(".inline-edit--display-field.id a ")[0].click();
}
},
{
'next .work-packages-full-view--split-left': I18n.t('js.onboarding.steps.wp.full_view'),
'showSkip': false,
'nextButton': {text: I18n.t('js.onboarding.buttons.next')},
'containerClass': '-dark -hidden-arrow'
},
{
'next .work-packages-back-button': I18n.t('js.onboarding.steps.wp.back_button'),
'showSkip': false,
'nextButton': {text: I18n.t('js.onboarding.buttons.next')},
onNext: function () {
$('.work-packages-back-button')[0].click();
}
},
{
'next .add-work-package': I18n.t('js.onboarding.steps.wp.create_button'),
'showSkip': false,
'nextButton': {text: I18n.t('js.onboarding.buttons.next')},
'shape': 'circle',
'timeout': function() {
return new Promise(function(resolve) {
// We are waiting here for the badge to appear,
// because its the last that appears and it shifts the WP create button to the left.
// Thus it is important that the tour rendering starts after the badge is visible
waitForElement('#work-packages-filter-toggle-button .badge', '#content', function() {
resolve();
});
});
},
onNext: function () {
$('#wp-view-toggle-button').click();
}
},
{
'next #wp-view-toggle-button': I18n.t('js.onboarding.steps.wp.timeline_button'),
'showSkip': false,
'nextButton': {text: I18n.t('js.onboarding.buttons.next')},
'bottom': '-100',
onNext: function () {
$('#wp-view-context-menu .icon-view-timeline')[0].click();
}
},
{
'next .work-packages-tabletimeline--timeline-side': I18n.t('js.onboarding.steps.wp.timeline'),
'showSkip': false,
'nextButton': {text: I18n.t('js.onboarding.buttons.next')},
'containerClass': '-dark -hidden-arrow'
},
{
'next .main-menu--arrow-left-to-project': I18n.t('js.onboarding.steps.sidebar_arrow'),
'showSkip': false,
'nextButton': {text: I18n.t('js.onboarding.buttons.next')},
onNext: function () {
$('.main-menu--arrow-left-to-project')[0].click();
}
}
];
});
}(jQuery))

@ -1,36 +0,0 @@
<%#-- copyright
OpenProject is an open source project management software.
Copyright (C) 2012-2020 the OpenProject GmbH
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 3.
OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
Copyright (C) 2006-2017 Jean-Philippe Lang
Copyright (C) 2010-2013 the ChiliProject Team
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.
See docs/COPYRIGHT.rdoc for more details.
++#%>
<%
Redmine::Plugin.all.collect do |plugin|
plugin.registered_global_assets[:js].each do |path|
require_asset path
end
end
%>

@ -1,66 +0,0 @@
//-- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2020 the OpenProject GmbH
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2017 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// 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.
//
// See docs/COPYRIGHT.rdoc for more details.
//++
function moveOptions(sourceId, destId) {
var sourceSelection = jQuery('#' + sourceId);
var destSelection = jQuery('#' + destId);
var selectedOptions = sourceSelection.find('option:selected');
selectedOptions.each(function() {
var option = jQuery('<option>', { value: this.value,
text: this.text });
destSelection.append(option);
this.remove();
});
}
function swapOptions(option1, option2) {
if (option1.length == 1 && option2.length == 1) {
option2.after(option1);
}
}
function moveOptionUp(selectionId) {
var selection = jQuery('#' + selectionId);
var selectedOptions = selection.find('option:selected');
swapOptions(selectedOptions.prev(), selectedOptions);
}
function moveOptionDown(selectionId) {
var selection = jQuery('#' + selectionId);
var selectedOptions = selection.find('option:selected');
swapOptions(selectedOptions, selectedOptions.next());
}
function selectAllOptions(id) {
jQuery("#" + id + " option").attr('selected',true);
}

@ -1,149 +0,0 @@
<%#-- copyright
OpenProject is an open source project management software.
Copyright (C) 2012-2020 the OpenProject GmbH
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 3.
OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
Copyright (C) 2006-2017 Jean-Philippe Lang
Copyright (C) 2010-2013 the ChiliProject Team
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.
See docs/COPYRIGHT.rdoc for more details.
++#%>
(function($) {
$(function() {
$('#settings_session_ttl_enabled').on('change', function(){
$('#settings_session_ttl_container').toggle($(this).is(':checked'));
}).trigger('change');
/** Sync SCM vendor select when enabled SCMs are changed */
$('[name="settings[enabled_scm][]"]').change(function() {
var wasDisabled = !this.checked,
vendor = this.value,
select = $('#settings_repositories_automatic_managed_vendor'),
option = select.find('option[value="' + vendor + '"]');
// Skip non-manageable SCMs
if (option.length === 0) {
return;
}
option.prop('disabled', wasDisabled);
if (wasDisabled && option.prop('selected')) {
select.val('');
}
});
/* Javascript for Settings::TextSettingCell */
let langSelectSwitchData = function(select) {
let self = $(select);
let id = self.attr("id");
let settingName = id.replace('lang-for-', '');
let newLang = self.val();
let textArea =$(`#settings-${settingName}`)
let editor = textArea.siblings('ckeditor-augmented-textarea').data('editor');
return { id: id, settingName: settingName, newLang: newLang, textArea: textArea, editor: editor }
};
// Upon focusing:
// * store the current value of the editor in the hidden field for that lang.
// Upon change:
// * get the current value from the hidden field for that lang and set the editor text to that value.
// * Set the name of the textarea to reflect the current lang so that the value stored in the hidden field
// is overwritten.
$(".lang-select-switch")
.focus(function() {
let data = langSelectSwitchData(this);
$(`#${data.id}-${data.newLang}`).val(data.editor.getData());
})
.change(function() {
let data = langSelectSwitchData(this);
let storedValue = $(`#${data.id}-${data.newLang}`).val();
data.editor.setData(storedValue);
data.textArea.attr('name', `settings[${data.settingName}][${data.newLang}]`)
});
/* end Javascript for Settings::TextSettingCell */
$('.admin-settings--form').submit(function() {
/* Update consent time if consent required */
if ($('#settings_consent_required').is(':checked') && $('#toggle_consent_time').is(':checked')) {
$('#settings_consent_time')
.val(new Date().toISOString())
.prop('disabled', false);
}
return true;
});
/** Toggle notification settings fields */
$("#email_delivery_method_switch").on("change", function() {
delivery_method = $(this).val();
$(".email_delivery_method_settings").hide();
$("#email_delivery_method_" + delivery_method).show();
}).trigger("change");
$('#settings_smtp_authentication').on('change', function() {
var isNone = $(this).val() === 'none';
$('#settings_smtp_user_name,#settings_smtp_password')
.closest('.form--field')
.toggle(!isNone);
});
/** Toggle repository checkout fieldsets required when option is disabled */
$('.settings-repositories--checkout-toggle').change(function() {
var wasChecked = this.checked,
fieldset = $(this).closest('fieldset');
fieldset
.find('input,select')
.filter(':not([type=checkbox])')
.filter(':not([type=hidden])')
.removeAttr('required') // Rails 4.0 still seems to use attribute
.prop('required', wasChecked);
})
/** Toggle highlighted attributes visibility depending on if the highlighting mode 'inline' was selected*/
$('.settings--highlighting-mode select').change(function() {
var highlightingMode = $(this).val();
$(".settings--highlighted-attributes").toggle(highlightingMode === "inline")
})
/** Initialize hightlighted attributes checkboxes. If none is selected, it means we want them all. So let's
* show them all as selected.
* On submitting the form, we remove all checkboxes before sending to communicate, we actually want all and not
* only the selected.*/
if ($(".settings--highlighted-attributes input[type='checkbox']:checked").length == 0) {
$(".settings--highlighted-attributes input[type='checkbox']").prop("checked", true);
}
$('#tab-content-work_packages form').submit(function() {
var availableAttributes = $(".settings--highlighted-attributes input[type='checkbox']");
var selectedAttributes = $(".settings--highlighted-attributes input[type='checkbox']:checked");
if (selectedAttributes.length == availableAttributes.length) {
availableAttributes.prop("checked", false);
}
})
});
}(jQuery));

@ -1,34 +0,0 @@
//-- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2020 the OpenProject GmbH
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2017 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// 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.
//
// See docs/COPYRIGHT.rdoc for more details.
//++
//= require application
window.openProject = {
environment: 'dev'
};

@ -1,21 +0,0 @@
jQuery(function($) {
var tooltipTriggers = $('.advanced-tooltip-trigger');
tooltipTriggers.each(function (index, el) {
var tooltip = $("#" + $(el).attr('aria-describedby'));
$(el).bind('mouseover focus', function () {
var top = $(this).offset().top - $(window).scrollTop();
// Adjust top for small elements
var POINTER_HEIGHT = 16.5;
var middle = $(this).outerHeight() / 2;
if (middle < POINTER_HEIGHT) top -= POINTER_HEIGHT - middle;
// On the left side of the element + 5px Distance
var left = $(this).offset().left + $(this).width() + 5;
tooltip.css({'opacity': 1, 'visibility': 'visible', 'top': top, 'left': left});
}).bind('mouseout focusout', function () {
tooltip.css({'opacity': 0, 'visibility': 'hidden'});
});
});
});

@ -1,283 +0,0 @@
//-- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2020 the OpenProject GmbH
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2017 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// 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.
//
// See docs/COPYRIGHT.rdoc for more details.
//++
(function ($, undefined) {
"use strict";
function TopMenu (menu_container) {
this.menu_container = $(menu_container);
this.setup(menu_container);
}
TopMenu.prototype = $.extend(TopMenu.prototype, {
setup: function () {
this.hover = false;
this.menuIsOpen = false;
this.withHeadingFoldOutAtBorder();
this.setupDropdownClick();
this.registerEventHandlers();
this.closeOnBodyClick();
this.accessibility();
},
accessibility: function () {
$(".drop-down > ul").attr("aria-expanded","false");
},
toggleClick: function (dropdown) {
if (this.menuIsOpen) {
if (this.isOpen(dropdown)) {
this.closing();
} else {
this.open(dropdown);
}
} else {
this.opening();
this.open(dropdown);
}
},
// somebody opens the menu via click, hover possible afterwards
opening: function () {
this.startHover();
this.menuIsOpen = true;
this.menu_container.trigger("openedMenu", this.menu_container);
},
// the entire menu gets closed, no hover possible afterwards
closing: function () {
this.stopHover();
this.closeAllItems();
this.menuIsOpen = false;
this.menu_container.trigger("closedMenu", this.menu_container);
},
stopHover: function () {
this.hover = false;
this.menu_container.removeClass("hover");
},
startHover: function () {
this.hover = true;
this.menu_container.addClass("hover");
},
closeAllItems: function () {
var self = this;
this.openDropdowns().each(function (ix, item) {
self.close($(item));
});
},
closeOnBodyClick: function () {
var self = this;
document.getElementById('wrapper').addEventListener('click', function (evt) {
if (self.menuIsOpen && !self.openDropdowns()[0].contains(evt.target)) {
self.closing();
}
}, true);
},
openDropdowns: function () {
return this.dropdowns().filter(".open");
},
dropdowns: function () {
return this.menu_container.find("li.drop-down");
},
withHeadingFoldOutAtBorder: function () {
var menu_start_position;
if (this.menu_container.next().get(0) != undefined && (this.menu_container.next().get(0).tagName == 'H2')){
menu_start_position = this.menu_container.next().innerHeight() + this.menu_container.next().position().top;
this.menu_container.find("ul.menu-drop-down-container").css({ top: menu_start_position });
}
else if(this.menu_container.next().hasClass("wiki-content") && this.menu_container.next().children().next().first().get(0) != undefined && this.menu_container.next().children().next().first().get(0).tagName == 'H1'){
var wiki_heading = this.menu_container.next().children().next().first();
menu_start_position = wiki_heading.innerHeight() + wiki_heading.position().top;
this.menu_container.find("ul.menu-drop-down-container").css({ top: menu_start_position });
}
},
setupDropdownClick: function () {
var self = this;
this.dropdowns().each(function (ix, it) {
$(it).click(function () {
self.toggleClick($(this));
return false;
});
$(it).on('touchstart', function(e) {
// This shall avoid the hover event is fired,
// which would otherwise lead to menu being closed directly after its opened.
// Ignore clicks from within the dropdown
if ($(e.target).closest('.menu-drop-down-container').length) {
return true;
}
e.preventDefault();
$(this).click();
return false;
});
});
},
isOpen: function (dropdown) {
return dropdown.filter(".open").length == 1;
},
isClosed: function (dropdown) {
return !this.isOpen(dropdown);
},
open: function (dropdown) {
this.dontCloseWhenUsing(dropdown);
this.closeOtherItems(dropdown);
this.slideAndFocus(dropdown, function() {
dropdown.trigger("opened", dropdown);
});
},
close: function (dropdown, immediate) {
this.slideUp(dropdown, immediate);
dropdown.trigger("closed", dropdown);
},
closeOtherItems: function (dropdown) {
var self = this;
this.openDropdowns().each(function (ix, it) {
if ($(it) != $(dropdown)) {
self.close($(it), true);
}
});
},
dontCloseWhenUsing: function (dropdown) {
$(dropdown).find("li").click(function (event) {
event.stopPropagation();
});
$(dropdown).bind("mousedown mouseup click", function (event) {
event.stopPropagation();
});
},
slideAndFocus: function (dropdown, callback) {
this.slideDown(dropdown, callback);
this.focusFirstInputOrLink(dropdown);
},
slideDown: function (dropdown, callback) {
var toDrop = dropdown.find("> ul");
dropdown.addClass("open");
toDrop.slideDown(animationRate, callback).attr("aria-expanded","true");
},
slideUp: function (dropdown, immediate) {
var toDrop = $(dropdown).find("> ul");
dropdown.removeClass("open");
if (immediate) {
toDrop.hide();
} else {
toDrop.slideUp(animationRate);
}
toDrop.attr("aria-expanded","false");
},
// If there is ANY input, it will have precedence over links,
// i.e. links will only get focussed, if there is NO input whatsoever
focusFirstInputOrLink: function (dropdown) {
var toFocus = dropdown.find("ul :input:visible:first");
if (toFocus.length == 0) {
toFocus = dropdown.find("ul a:visible:first");
}
// actually a simple focus should be enough.
// The rest is only there to work around a rendering bug in webkit (as of Oct 2011),
// occuring mostly inside the login/signup dropdown.
toFocus.blur();
setTimeout(function() {
toFocus.focus();
}, 10);
},
registerEventHandlers: function () {
var self = this;
var toggler = $("#main-menu-toggle");
this.menu_container.on("closeDropDown", function (event) {
self.close($(event.target));
}).on("openDropDown", function (event) {
self.open($(event.target));
}).on("closeMenu", function () {
self.closing();
}).on("openMenu", function () {
self.open(self.dropdowns().first());
self.opening();
});
toggler.on("click", function() { // click on hamburger icon is closing other menu
self.closing();
});
}
});
// this holds all top menus currently active.
// if one opens, all others are closed.
var top_menus = [];
$.fn.top_menu = function () {
var new_menu;
$(this).each(function () {
new_menu = new TopMenu($(this));
top_menus.forEach(function (menu) {
menu.menu_container.on("openedMenu", function () {
new_menu.closing();
});
new_menu.menu_container.on("openedMenu", function () {
menu.closing();
});
});
top_menus.push(new_menu);
});
};
}(jQuery));
function skipMenu() {
// Skip to the breadcrumb or the first link in the toolbar or the first link in the content (homescreen)
const selectors = '.first-breadcrumb-element a, .toolbar-container a:first-of-type, #content a:first-of-type';
const visibleLink = jQuery(selectors)
.not(':hidden')
.first();
if (visibleLink.length) {
visibleLink.focus();
}
}
jQuery(document).ready(function($) {
$("#top-menu-items").top_menu();
});

@ -1,143 +0,0 @@
//-- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2020 the OpenProject GmbH
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2017 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// 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.
//
// See docs/COPYRIGHT.rdoc for more details.
//++
(function($) {
var TypesCheckboxes = function () {
this.init();
};
TypesCheckboxes.prototype = $.extend(TypesCheckboxes.prototype, {
init: function () {
this.append_checkbox_listeners();
this.append_check_uncheck_all_listeners();
if (this.everything_unchecked()) {
this.check_and_disable_standard_type();
}
},
append_checkbox_listeners: function () {
var self = this;
this.all_checkboxes().on("change", function () {
if (self.everything_unchecked()) {
self.check_and_disable_standard_type();
self.display_explanation();
} else {
self.hide_explanation();
self.enable_standard_type();
}
});
},
append_check_uncheck_all_listeners: function () {
var self = this;
$("#project_types #check_all_types").click(function (event) {
self.enable_all_checkboxes();
self.check(self.all_checkboxes());
self.hide_explanation();
event.preventDefault();
});
$("#project_types #uncheck_all_types").click(function (event) {
self.enable_all_checkboxes();
self.uncheck(self.all_except_standard());
self.check_and_disable_standard_type();
self.display_explanation();
event.preventDefault();
});
},
everything_unchecked: function () {
return !(this.all_except_standard().filter(":checked").length > 0);
},
check_and_disable_standard_type: function () {
var standard = this.standard_check_boxes();
this.check($(standard));
this.disable($(standard));
},
enable_standard_type: function () {
this.enable(this.standard_check_boxes());
},
enable_all_checkboxes: function () {
this.enable(this.all_checkboxes());
},
check: function (boxes) {
$(boxes).prop("checked", true);
},
uncheck: function (boxes) {
$(boxes).prop("checked", false);
},
disable: function (boxes) {
var self = this;
$(boxes).prop('disabled', true);
$(boxes).each(function (ix, item) {
self.hidden_type_field($(item)).prop("value", $(item).prop("value"));
});
},
enable: function (boxes) {
var self = this;
$(boxes).prop('disabled', false);
$(boxes).each(function (ix, item) {
self.hidden_type_field($(item)).prop("value", "");
});
},
display_explanation: function () {
$("#types_flash_notice").show();
},
hide_explanation: function () {
$("#types_flash_notice").hide();
},
all_checkboxes: function () {
return $(".types :input[type='checkbox']");
},
all_except_standard: function () {
return $(".types :input[type='checkbox'][data-standard='false']");
},
standard_check_boxes: function () {
return $(".types :input[type='checkbox'][data-standard='true']");
},
hidden_type_field: function (for_box) {
return $(".types :input[type='hidden'][data-for='" + $(for_box).prop("id") + "']");
}
});
$('document').ready(function () {
new TypesCheckboxes();
});
})(jQuery);

@ -1,72 +0,0 @@
//-- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2020 the OpenProject GmbH
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2017 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// 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.
//
// See docs/COPYRIGHT.rdoc for more details.
//++
(function ($) {
var applicable,
register_change_wp_by_status,
handle_change_wp_by_status,
init;
applicable = function () {
return $('body.controller-versions.action-show').length === 1;
};
init = function () {
register_change_wp_by_status();
};
register_change_wp_by_status = function () {
$('#status_by_select').change(function () {
handle_change_wp_by_status();
return false;
});
};
handle_change_wp_by_status = function () {
var form = $('#status_by_form'),
url = form.attr('action'),
data = form.serialize();
$.ajax({ url: url,
headers: { Accept: 'text/javascript' },
dataType: 'html',
data: data,
complete: function (jqXHR) {
form.replaceWith(jqXHR.responseText);
register_change_wp_by_status();
}
});
};
$('document').ready(function () {
if (applicable()) {
init();
}
});
})(jQuery);

@ -4,7 +4,7 @@
<form action="<%= form_url %>" <form action="<%= form_url %>"
method="post" method="post"
id="<%= row.roles_css_id %>-form" id="<%= row.roles_css_id %>-form"
class="hol <%= row.toggle_item_class_name %>" class="<%= row.toggle_item_class_name %>"
style="display:none" style="display:none"
accept-charset="UTF-8" accept-charset="UTF-8"
> >

@ -366,9 +366,9 @@ module ApplicationHelper
end end
def check_all_links(form_name) def check_all_links(form_name)
link_to_function(t(:button_check_all), "checkAll('#{form_name}', true)") + link_to_function(t(:button_check_all), "OpenProject.helpers.checkAll('#{form_name}', true)") +
' | ' + ' | ' +
link_to_function(t(:button_uncheck_all), "checkAll('#{form_name}', false)") link_to_function(t(:button_uncheck_all), "OpenProject.helpers.checkAll('#{form_name}', false)")
end end
def current_layout def current_layout

@ -63,7 +63,7 @@ module PasswordHelper
end end
end end
def render_password_complexity_tooltip def render_password_complexity_hint
rules = password_rules_description rules = password_rules_description
s = OpenProject::Passwords::Evaluator.min_length_description s = OpenProject::Passwords::Evaluator.min_length_description

@ -31,7 +31,6 @@
module TextFormattingHelper module TextFormattingHelper
extend Forwardable extend Forwardable
def_delegators :current_formatting_helper, def_delegators :current_formatting_helper,
:text_formatting_js_includes,
:wikitoolbar_for :wikitoolbar_for
def preview_context(object, project = nil) def preview_context(object, project = nil)

@ -28,6 +28,5 @@ See docs/COPYRIGHT.rdoc for more details.
++#%> ++#%>
<% content_for :header_tags do %> <% content_for :header_tags do %>
<%= javascript_include_tag "reporting_engine/reporting_engine" %> <meta name="required_script" content="custom_fields" />
<%= javascript_include_tag "reporting/reporting" %>
<% end %> <% end %>

@ -27,6 +27,8 @@ See docs/COPYRIGHT.rdoc for more details.
++#%> ++#%>
<%= render :partial => 'custom_fields_header' %>
<% html_title l(:label_administration), "#{l(:label_edit)} #{CustomField.model_name.human} #{h @custom_field.name}" %> <% html_title l(:label_administration), "#{l(:label_edit)} #{CustomField.model_name.human} #{h @custom_field.name}" %>
<% local_assigns[:additional_breadcrumb] = link_to(l(@custom_field.type_name), custom_fields_path(tab: @custom_field.type)), <% local_assigns[:additional_breadcrumb] = link_to(l(@custom_field.type_name), custom_fields_path(tab: @custom_field.type)),

@ -26,6 +26,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
See docs/COPYRIGHT.rdoc for more details. See docs/COPYRIGHT.rdoc for more details.
++#%> ++#%>
<%= render :partial => 'custom_fields_header' %>
<% html_title l(:label_administration), l(:label_custom_field_new) %> <% html_title l(:label_administration), l(:label_custom_field_new) %>
<% local_assigns[:additional_breadcrumb] = link_to(l(@custom_field.type_name), custom_fields_path(tab: @custom_field.type)), <% local_assigns[:additional_breadcrumb] = link_to(l(@custom_field.type_name), custom_fields_path(tab: @custom_field.type)),

@ -80,12 +80,6 @@ See docs/COPYRIGHT.rdoc for more details.
media: :all, media: :all,
skip_pipeline: true %> skip_pipeline: true %>
<%= javascript_include_tag 'application' %>
<%= javascript_include_tag "locales/#{I18n.locale}" %>
<!-- Text formatting specific includes -->
<%= text_formatting_js_includes %>
<!-- Onboarding specific includes -->
<%= javascript_include_tag 'vendor/enjoyhint.js'%>
<!-- project specific tags --> <!-- project specific tags -->
<%= call_hook :view_layouts_base_html_head %> <%= call_hook :view_layouts_base_html_head %>
<!-- page specific tags --> <!-- page specific tags -->

@ -26,6 +26,10 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
See docs/COPYRIGHT.rdoc for more details. See docs/COPYRIGHT.rdoc for more details.
++#%> ++#%>
<% content_for :header_tags do %>
<meta name="required_script" content="members_form" />
<% end %>
<% html_title t(:label_member_plural) -%> <% html_title t(:label_member_plural) -%>
<% any_members = @members.any? %> <% any_members = @members.any? %>
@ -55,8 +59,6 @@ See docs/COPYRIGHT.rdoc for more details.
<% end %> <% end %>
<% end %> <% end %>
<%= javascript_include_tag "members_form.js" %>
<%= error_messages_for 'member' %> <%= error_messages_for 'member' %>
<div> <div>

@ -27,6 +27,10 @@ See docs/COPYRIGHT.rdoc for more details.
++#%> ++#%>
<% content_for :header_tags do %>
<meta name="required_script" content="forums" />
<% end %>
<% breadcrumb_paths( <% breadcrumb_paths(
link_to(t(:label_forum_plural), project_forums_path(@project)), link_to(t(:label_forum_plural), project_forums_path(@project)),
link_to(h(@forum.name), project_forum_path(@project, @forum))) link_to(h(@forum.name), project_forum_path(@project, @forum)))

@ -59,13 +59,11 @@ See docs/COPYRIGHT.rdoc for more details.
class: 'advanced-tooltip-trigger', class: 'advanced-tooltip-trigger',
container_class: (defined? input_size) ? "-#{input_size}" : '' %> container_class: (defined? input_size) ? "-#{input_size}" : '' %>
</div> </div>
<div class="advanced-tooltip-content" id="new_password_tooltip"> <div class="form--field-instructions">
<%= render_password_complexity_tooltip %> <%= render_password_complexity_hint %>
</div> </div>
</div> </div>
<div class="form--field -required"> <div class="form--field -required">
<%= styled_label_tag 'new_password_confirmation', <%= styled_label_tag 'new_password_confirmation',
User.human_attribute_name(:password_confirmation) %> User.human_attribute_name(:password_confirmation) %>
@ -78,7 +76,7 @@ See docs/COPYRIGHT.rdoc for more details.
class: 'advanced-tooltip-trigger', class: 'advanced-tooltip-trigger',
container_class: (defined? input_size) ? "-#{input_size}" : '' %> container_class: (defined? input_size) ? "-#{input_size}" : '' %>
</div> </div>
<div class="advanced-tooltip-content" id="confirm_password_tooltip"> <div class="form--field-instructions">
<%= I18n.t(:confirm, <%= I18n.t(:confirm,
scope: [:activerecord, :errors, :models, :user, :attributes, :password, :match]) %> scope: [:activerecord, :errors, :models, :user, :attributes, :password, :match]) %>
<br> <br>

@ -27,6 +27,10 @@ See docs/COPYRIGHT.rdoc for more details.
++#%> ++#%>
<% content_for :header_tags do %>
<meta name="required_script" content="project_identifier_listener" />
<% end %>
<%= error_messages_for_contract(project, errors) unless local_assigns[:no_error_messages] %> <%= error_messages_for_contract(project, errors) unless local_assigns[:no_error_messages] %>
<!--[form:project]--> <!--[form:project]-->

@ -27,14 +27,6 @@ See docs/COPYRIGHT.rdoc for more details.
++#%> ++#%>
<%= javascript_include_tag 'types_checkboxes' %>
<%= render_flash_message :notice,
t(:notice_automatic_set_of_standard_type),
style: "display:none;",
id: "types_flash_notice",
class: "ignored-by-flash-activation" %>
<div class="generic-table--container"> <div class="generic-table--container">
<div class="generic-table--results-container"> <div class="generic-table--results-container">
<table class="generic-table types"> <table class="generic-table types">

@ -39,11 +39,9 @@ See docs/COPYRIGHT.rdoc for more details.
<%= form.text_field :identifier, container_class: '-middle'%> <%= form.text_field :identifier, container_class: '-middle'%>
<%= nonced_javascript_tag do %> <%= nonced_javascript_tag do %>
projectIdentifierMaxLength = <%= id_max_length %>; window.projectIdentifierMaxLength = <%= id_max_length %>;
projectIdentifierDefault = '<%= id %>'; window.projectIdentifierDefault = '<%= id %>';
projectIdentifierLocked = false; window.projectIdentifierLocked = false;
observeProjectIdentifier();
observeProjectName();
<% end %> <% end %>
<div class="form--field-instructions"> <div class="form--field-instructions">

@ -27,8 +27,7 @@ See docs/COPYRIGHT.rdoc for more details.
++#%> ++#%>
<% content_for :header_tags do %> <% content_for :header_tags do %>
<%= javascript_include_tag "project/description_handling.js" %> <meta name="required_script" content="project" />
<%= javascript_include_tag "project/filters.js" %>
<% end %> <% end %>
<% html_title(t(:label_project_plural)) -%> <% html_title(t(:label_project_plural)) -%>

@ -28,7 +28,7 @@ See docs/COPYRIGHT.rdoc for more details.
++#%> ++#%>
<% content_for :header_tags do %> <% content_for :header_tags do %>
<%= javascript_include_tag 'repository_navigation' %> <meta name="required_script" content="repository_navigation" />
<% end %> <% end %>
<persistent-toggle data-identifier="repository.checkout_instructions"></persistent-toggle> <persistent-toggle data-identifier="repository.checkout_instructions"></persistent-toggle>

@ -27,8 +27,8 @@ See docs/COPYRIGHT.rdoc for more details.
++#%> ++#%>
<%= content_for :header_tags do %> <% content_for :header_tags do %>
<%= javascript_include_tag('repository_settings') %> <meta name="required_script" content="repository_settings" />
<% end %> <% end %>
<%= toolbar title: t(:label_repository) %> <%= toolbar title: t(:label_repository) %>

@ -78,7 +78,9 @@ See docs/COPYRIGHT.rdoc for more details.
<div class="generic-table--sort-header"> <div class="generic-table--sort-header">
<span> <span>
<%= content_tag(role.builtin? ? 'em' : 'span', h(role.name)) %> <%= content_tag(role.builtin? ? 'em' : 'span', h(role.name)) %>
<%= link_to_function(icon_wrapper('icon-context icon-checkmark',"#{l(:button_check_all)}/#{l(:button_uncheck_all)}"), "toggleCheckboxesBySelector('input.role-#{role.id}')", <%= link_to_function(
icon_wrapper('icon-context icon-checkmark',"#{l(:button_check_all)}/#{l(:button_uncheck_all)}"),
"OpenProject.helpers.toggleCheckboxesBySelector('input.role-#{role.id}')",
class: 'no-decoration-on-hover', class: 'no-decoration-on-hover',
title: "#{l(:button_check_all)}/#{l(:button_uncheck_all)}") %> title: "#{l(:button_check_all)}/#{l(:button_uncheck_all)}") %>
</span> </span>
@ -92,7 +94,9 @@ See docs/COPYRIGHT.rdoc for more details.
<% perms_by_module[mod].each do |permission| %> <% perms_by_module[mod].each do |permission| %>
<tr class="permission-<%= permission.name %>"> <tr class="permission-<%= permission.name %>">
<td> <td>
<%= link_to_function(icon_wrapper('icon-context icon-checkmark',"#{l(:button_check_all)}/#{l(:button_uncheck_all)}"), "toggleCheckboxesBySelector('.permission-#{permission.name} input')", <%= link_to_function(
icon_wrapper('icon-context icon-checkmark',"#{l(:button_check_all)}/#{l(:button_uncheck_all)}"),
"OpenProject.helpers.toggleCheckboxesBySelector('.permission-#{permission.name} input')",
class: 'no-decoration-on-hover', class: 'no-decoration-on-hover',
title: "#{l(:button_check_all)}/#{l(:button_uncheck_all)}") %> title: "#{l(:button_check_all)}/#{l(:button_uncheck_all)}") %>
<%= l_or_humanize(permission.name, prefix: 'permission_') %> <%= l_or_humanize(permission.name, prefix: 'permission_') %>

@ -27,6 +27,10 @@ See docs/COPYRIGHT.rdoc for more details.
++#%> ++#%>
<% content_for :header_tags do %>
<meta name="required_script" content="administration_settings" />
<% end %>
<%= styled_form_tag(general_settings_path(tab: 'display'), method: :patch) do %> <%= styled_form_tag(general_settings_path(tab: 'display'), method: :patch) do %>
<section class="form--section"> <section class="form--section">
@ -56,9 +60,3 @@ See docs/COPYRIGHT.rdoc for more details.
{ controller: '/admin', action: 'force_user_language' }, { controller: '/admin', action: 'force_user_language' },
method: :post %> method: :post %>
</div> </div>
<%=
content_for(:additional_js_dom_ready) do
"Administration.init_language_selection_handling();".html_safe
end
%>

@ -56,13 +56,6 @@ See docs/COPYRIGHT.rdoc for more details.
disabled: !Setting.sys_api_enabled?, disabled: !Setting.sys_api_enabled?,
label: :setting_mail_handler_api_key, label: :setting_mail_handler_api_key,
container_class: '-middle' %> container_class: '-middle' %>
<span class="form--field-extra-actions">
<%= link_to_function t(:label_generate_key),
%{ if (!jQuery('#settings_sys_api_key').prop('disabled')) {
jQuery('#settings_sys_api_key').val(randomKey(20))
}
} %>
</span>
</div> </div>
<% <%
available_scms = OpenProject::SCM::Manager.registered available_scms = OpenProject::SCM::Manager.registered

@ -26,6 +26,11 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
See docs/COPYRIGHT.rdoc for more details. See docs/COPYRIGHT.rdoc for more details.
++#%> ++#%>
<% content_for :header_tags do %>
<meta name="required_script" content="timelog_date_range" />
<% end %>
<fieldset id="date-range" class="simple-filters--container"> <fieldset id="date-range" class="simple-filters--container">
<legend><%= l(:label_date_range) %></legend> <legend><%= l(:label_date_range) %></legend>
<ul class="simple-filters--filters"> <ul class="simple-filters--filters">

@ -29,7 +29,9 @@ See docs/COPYRIGHT.rdoc for more details.
<%= error_messages_for 'user' %> <%= error_messages_for 'user' %>
<%= javascript_include_tag 'admin_users' %> <% content_for :header_tags do %>
<meta name="required_script" content="admin_users" />
<% end %>
<!--[form:user]--> <!--[form:user]-->
<section class="form--section"> <section class="form--section">

@ -29,7 +29,9 @@ See docs/COPYRIGHT.rdoc for more details.
<%= error_messages_for 'user' %> <%= error_messages_for 'user' %>
<%= javascript_include_tag 'admin_users' %> <% content_for :header_tags do %>
<meta name="required_script" content="two_factor_authentication" />
<% end %>
<!--[form:user]--> <!--[form:user]-->
<section class="form--section"> <section class="form--section">

@ -27,7 +27,9 @@ See docs/COPYRIGHT.rdoc for more details.
++#%> ++#%>
<%= javascript_include_tag 'new_user' %> <% content_for :header_tags do %>
<meta name="required_script" content="new_user" />
<% end %>
<% html_title l(:label_administration), l("label_user_new") %> <% html_title l(:label_administration), l("label_user_new") %>
<% local_assigns[:additional_breadcrumb] = l(:label_user_new) %> <% local_assigns[:additional_breadcrumb] = l(:label_user_new) %>

@ -49,7 +49,8 @@ See docs/COPYRIGHT.rdoc for more details.
<th class="-table-border-bottom -table-border-right"></th> <th class="-table-border-bottom -table-border-right"></th>
<% for new_status in @statuses %> <% for new_status in @statuses %>
<th class="workflow-table--current-status -table-border-top -table-border-bottom"> <th class="workflow-table--current-status -table-border-top -table-border-bottom">
<%= link_to_function(icon_wrapper('icon-context icon-checkmark',"#{l(:label_check_uncheck_all_in_column)}"), "toggleCheckboxesBySelector('table.transitions-#{name} input.new-status-#{new_status.id}')", <%= link_to_function(icon_wrapper('icon-context icon-checkmark',"#{l(:label_check_uncheck_all_in_column)}"),
"OpenProject.helpers.toggleCheckboxesBySelector('table.transitions-#{name} input.new-status-#{new_status.id}')",
class: 'no-decoration-on-hover', class: 'no-decoration-on-hover',
title: "#{l(:label_check_uncheck_all_in_column)}", title: "#{l(:label_check_uncheck_all_in_column)}",
alt: "#{l(:label_check_uncheck_all_in_column)}") %> alt: "#{l(:label_check_uncheck_all_in_column)}") %>
@ -62,7 +63,8 @@ See docs/COPYRIGHT.rdoc for more details.
<% for old_status in @statuses %> <% for old_status in @statuses %>
<tr class="-table-border-left"> <tr class="-table-border-left">
<td class="workflow-table--current-status -table-border-right"> <td class="workflow-table--current-status -table-border-right">
<%= link_to_function(icon_wrapper('icon-context icon-checkmark',"#{l(:label_check_uncheck_all_in_row)}"), "toggleCheckboxesBySelector('table.transitions-#{name} input.old-status-#{old_status.id}')", <%= link_to_function(icon_wrapper('icon-context icon-checkmark',"#{l(:label_check_uncheck_all_in_row)}"),
"OpenProject.helpers.toggleCheckboxesBySelector('table.transitions-#{name} input.old-status-#{old_status.id}')",
class: 'no-decoration-on-hover', class: 'no-decoration-on-hover',
title: "#{l(:label_check_uncheck_all_in_row)}", title: "#{l(:label_check_uncheck_all_in_row)}",
alt: "#{l(:label_check_uncheck_all_in_row)}") %> alt: "#{l(:label_check_uncheck_all_in_row)}") %>

@ -7,13 +7,8 @@ yell() { echo -e "$0: $*" >&2; }
die() { yell "$*"; exit 1; } die() { yell "$*"; exit 1; }
try() { eval "$@" || die "\n\nFailed to run '$*', check log/setup_dev.log for more information."; } try() { eval "$@" || die "\n\nFailed to run '$*', check log/setup_dev.log for more information."; }
printf "Clearing previous bundle ... "
rm -rf app/assets/javascripts/bundles/*
rm -f log/setup_dev.log
echo "done."
printf "Bundling rails dependencies ... " printf "Bundling rails dependencies ... "
try 'bundle install >> log/setup_dev.log' try 'bundle install > log/setup_dev.log'
echo "done." echo "done."
echo "Installing node_modules ... " echo "Installing node_modules ... "

@ -1,4 +1,4 @@
fallbacks: :en fallbacks: :en
translations: translations:
- file: "app/assets/javascripts/locales/%{locale}.js" - file: "frontend/src/locales/%{locale}.js"
only: ['*.js', '*.number.*', '*.time.*', '*.date.*'] only: ['*.js', '*.number.*', '*.time.*', '*.date.*']

@ -1,25 +1,6 @@
OpenProject::Application.configure do OpenProject::Application.configure do
# Precompile additional assets.
# application.js, application.css, and all non-JS/CSS in app/assets folder are already added.
config.assets.precompile += %w( config.assets.precompile += %w(
favicon.ico favicon.ico
admin_users.js
autocompleter.js
copy_issue_actions.js
date-de-DE.js
date-en-US.js
locales/*.js locales/*.js
members_form.js
new_user.js
project/responsible_attribute.js
project/description_handling.js
project/filters.js
repository_navigation.js
repository_settings.js
select_list_move.js
types_checkboxes.js
work_packages.js
vendor/ckeditor/ckeditor.*js
vendor/enjoyhint.js
) )
end end

@ -13659,6 +13659,14 @@
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz",
"integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==" "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ=="
}, },
"tablesorter": {
"version": "2.31.3",
"resolved": "https://registry.npmjs.org/tablesorter/-/tablesorter-2.31.3.tgz",
"integrity": "sha512-ueEzeKiMajDcCWnUoT1dOeNEaS1OmPh9+8J0O2Sjp3TTijMygH74EA9QNJiNkLJqULyNU0RhbKY26UMUq9iurA==",
"requires": {
"jquery": ">=1.2.6"
}
},
"tapable": { "tapable": {
"version": "1.1.3", "version": "1.1.3",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz",

@ -120,6 +120,7 @@
"rxjs": "^6.5.4", "rxjs": "^6.5.4",
"screenfull": "^4.2.1", "screenfull": "^4.2.1",
"shelljs": "^0.8.3", "shelljs": "^0.8.3",
"tablesorter": "^2.31.3",
"ticky": "^1.0.1", "ticky": "^1.0.1",
"tslib": "^1.10.0", "tslib": "^1.10.0",
"typedjson": "^1.5.1", "typedjson": "^1.5.1",
@ -132,7 +133,7 @@
"scripts": { "scripts": {
"analyze": "ng build --prod --stats-json && webpack-bundle-analyzer -p 9999 ../public/assets/frontend/stats.json", "analyze": "ng build --prod --stats-json && webpack-bundle-analyzer -p 9999 ../public/assets/frontend/stats.json",
"prebuild": "./scripts/link_plugin_placeholder.js", "prebuild": "./scripts/link_plugin_placeholder.js",
"build": "ng build --prod --named-chunks --source-map --extract-css", "build": "node --max_old_space_size=2048 ./node_modules/@angular/cli/bin/ng build --prod --named-chunks --extract-css --source-map",
"preserve": "./scripts/link_plugin_placeholder.js", "preserve": "./scripts/link_plugin_placeholder.js",
"serve": "node --max_old_space_size=8096 ./node_modules/@angular/cli/bin/ng serve --public-host http://localhost:4200", "serve": "node --max_old_space_size=8096 ./node_modules/@angular/cli/bin/ng serve --public-host http://localhost:4200",
"pretest": "./scripts/link_plugin_placeholder.js", "pretest": "./scripts/link_plugin_placeholder.js",

@ -50,7 +50,6 @@ import {ProjectMenuAutocompleteComponent} from "core-components/projects/project
import {OpenProjectFileUploadService} from "core-components/api/op-file-upload/op-file-upload.service"; import {OpenProjectFileUploadService} from "core-components/api/op-file-upload/op-file-upload.service";
import {LinkedPluginsModule} from "core-app/modules/plugins/linked-plugins.module"; import {LinkedPluginsModule} from "core-app/modules/plugins/linked-plugins.module";
import {HookService} from "core-app/modules/plugins/hook-service"; import {HookService} from "core-app/modules/plugins/hook-service";
import {ModalWrapperAugmentService} from "core-app/globals/augmenting/modal-wrapper.augment.service";
import {DynamicBootstrapper} from "core-app/globals/dynamic-bootstrapper"; import {DynamicBootstrapper} from "core-app/globals/dynamic-bootstrapper";
import {OpenprojectWorkPackagesModule} from 'core-app/modules/work_packages/openproject-work-packages.module'; import {OpenprojectWorkPackagesModule} from 'core-app/modules/work_packages/openproject-work-packages.module';
import {OpenprojectAttachmentsModule} from 'core-app/modules/attachments/openproject-attachments.module'; import {OpenprojectAttachmentsModule} from 'core-app/modules/attachments/openproject-attachments.module';
@ -75,6 +74,7 @@ import {KeyboardShortcutService} from "core-app/modules/a11y/keyboard-shortcut-s
import {globalDynamicComponents} from "core-app/global-dynamic-components.const"; import {globalDynamicComponents} from "core-app/global-dynamic-components.const";
import {OpenprojectMembersModule} from "core-app/modules/members/members.module"; import {OpenprojectMembersModule} from "core-app/modules/members/members.module";
import {OpenprojectEnterpriseModule} from "core-components/enterprise/openproject-enterprise.module"; import {OpenprojectEnterpriseModule} from "core-components/enterprise/openproject-enterprise.module";
import {OpenprojectAugmentingModule} from "core-app/modules/augmenting/openproject-augmenting.module";
@NgModule({ @NgModule({
imports: [ imports: [
@ -132,7 +132,10 @@ import {OpenprojectEnterpriseModule} from "core-components/enterprise/openprojec
OpenprojectMembersModule, OpenprojectMembersModule,
// Angular Forms // Angular Forms
ReactiveFormsModule ReactiveFormsModule,
// Augmenting Module
OpenprojectAugmentingModule,
], ],
providers: [ providers: [
{ provide: States, useValue: new States() }, { provide: States, useValue: new States() },
@ -193,16 +196,12 @@ export function initializeServices(injector:Injector) {
return () => { return () => {
const ExternalQueryConfiguration = injector.get(ExternalQueryConfigurationService); const ExternalQueryConfiguration = injector.get(ExternalQueryConfigurationService);
const ExternalRelationQueryConfiguration = injector.get(ExternalRelationQueryConfigurationService); const ExternalRelationQueryConfiguration = injector.get(ExternalRelationQueryConfigurationService);
const ModalWrapper = injector.get(ModalWrapperAugmentService);
const PreviewTrigger = injector.get(PreviewTriggerService); const PreviewTrigger = injector.get(PreviewTriggerService);
const mainMenuNavigationService = injector.get(MainMenuNavigationService); const mainMenuNavigationService = injector.get(MainMenuNavigationService);
const keyboardShortcuts = injector.get(KeyboardShortcutService); const keyboardShortcuts = injector.get(KeyboardShortcutService);
mainMenuNavigationService.register(); mainMenuNavigationService.register();
// Setup modal wrapping
ModalWrapper.setupListener();
PreviewTrigger.setupListener(); PreviewTrigger.setupListener();
keyboardShortcuts.register(); keyboardShortcuts.register();

@ -85,10 +85,10 @@ describe('WorkPackageCacheService', () => {
workPackageDmService = TestBed.get(WorkPackageDmService); workPackageDmService = TestBed.get(WorkPackageDmService);
// sinon.stub(WorkPackageDmService, 'loadWorkPackageById').returns(Promise.resolve(true)); // sinon.stub(WorkPackageDmService, 'loadWorkPackageById').returns(Promise.resolve(true));
spyOn(workPackageDmService, 'loadWorkPackageById').and.returnValue(Promise.resolve(true)); spyOn(workPackageDmService, 'loadWorkPackageById').and.returnValue(Promise.resolve(true as any));
// sinon.stub(schemaCacheService, 'ensureLoaded').returns(Promise.resolve(true)); // sinon.stub(schemaCacheService, 'ensureLoaded').returns(Promise.resolve(true));
spyOn(schemaCacheService, 'ensureLoaded').and.returnValue(Promise.resolve(true)); spyOn(schemaCacheService, 'ensureLoaded').and.returnValue(Promise.resolve(true as any));
const workPackage1 = new WorkPackageResource( const workPackage1 = new WorkPackageResource(

@ -26,19 +26,23 @@
// See docs/COPYRIGHT.rdoc for more details. // See docs/COPYRIGHT.rdoc for more details.
//++ //++
(function($) { /**
$(function() { * A set of global helpers that were used in the app/assets/javascript namespace
// This will only work iff there is a single danger zone on the page * but exposed globally.
var dangerZoneVerification = $('.danger-zone--verification'); *
var expectedValue = $('.danger-zone--expected-value').text(); * It is used in some `link_to_function` helpers in Rails templates
*/
export class GlobalHelpers {
public checkAll(selector:any, checked:any) {
jQuery('#' + selector + ' input:checkbox').not(':disabled').each(function(this:HTMLInputElement) {
this.checked = checked;
});
}
dangerZoneVerification.find('input').on('input', function(){ public toggleCheckboxesBySelector(selector:any) {
var actualValue = dangerZoneVerification.find('input').val(); const boxes = jQuery(selector);
if (expectedValue.toLowerCase() === actualValue.toLowerCase()) { var all_checked = true;
dangerZoneVerification.find('button').prop('disabled', false); for (let i = 0; i < boxes.length; i++) { if (boxes[i].checked === false) { all_checked = false; } }
} else { for (let i = 0; i < boxes.length; i++) { boxes[i].checked = !all_checked; }
dangerZoneVerification.find('button').prop('disabled', true); }
} }
});
});
}(jQuery));

@ -32,6 +32,14 @@ import {refreshOnFormChanges} from 'core-app/globals/global-listeners/refresh-on
import {registerRequestForConfirmation} from "core-app/globals/global-listeners/request-for-confirmation"; import {registerRequestForConfirmation} from "core-app/globals/global-listeners/request-for-confirmation";
import {DeviceService} from "core-app/modules/common/browser/device.service"; import {DeviceService} from "core-app/modules/common/browser/device.service";
import {scrollHeaderOnMobile} from "core-app/globals/global-listeners/top-menu-scroll"; import {scrollHeaderOnMobile} from "core-app/globals/global-listeners/top-menu-scroll";
import {setupToggableFieldsets} from "core-app/globals/global-listeners/toggable-fieldset";
import {TopMenu} from "core-app/globals/global-listeners/top-menu";
import {install_menu_logic} from "core-app/globals/global-listeners/action-menu";
import {makeColorPreviews} from "core-app/globals/global-listeners/color-preview";
import {dangerZoneValidation} from "core-app/globals/global-listeners/danger-zone-validation";
import {setupServerResponse} from "core-app/globals/global-listeners/setup-server-response";
import {listenToSettingChanges} from "core-app/globals/global-listeners/settings";
import {detectOnboardingTour} from "core-app/globals/onboarding/onboarding_tour_trigger";
/** /**
* A set of listeners that are relevant on every page to set sensible defaults * A set of listeners that are relevant on every page to set sensible defaults
@ -110,6 +118,37 @@ import {scrollHeaderOnMobile} from "core-app/globals/global-listeners/top-menu-s
if (deviceService.isMobile) { if (deviceService.isMobile) {
scrollHeaderOnMobile(); scrollHeaderOnMobile();
} }
// Detect and trigger the onboarding tour
// through a lazy loaded script
detectOnboardingTour();
//
// Legacy scripts from app/assets that are not yet component based
//
// Toggable fieldsets
setupToggableFieldsets();
// Top menu click handling
new TopMenu(jQuery('#top-menu-items'));
// Action menu logic
jQuery('.project-actions, .toolbar-items').each(function (idx:number, menu:HTMLElement) {
install_menu_logic(jQuery(menu));
});
// Legacy settings listener
listenToSettingChanges();
// Color patches preview the color
makeColorPreviews();
// Danger zone input validation
dangerZoneValidation();
// Bootstrap legacy app code
setupServerResponse();
}); });
}(jQuery)); }(jQuery));

@ -0,0 +1,94 @@
//-- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2020 the OpenProject GmbH
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2017 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// 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.
//
// See docs/COPYRIGHT.rdoc for more details.
//++
/*
The action menu is a menu that usually belongs to an OpenProject entity (like an Issue, WikiPage, Meeting, ..).
Most likely it looks like this:
<ul class="action_menu_main">
<li><a>Menu item text</a></li>
<li><a>Menu item text</a></li>
<li class="drop-down">
<a class="icon icon-more" href="#">More functions</a>
<ul style="display:none;" class="menu-drop-down-container">
<li><a>Menu item text</a></li>
</ul>
</li>
</ul>
The following code is responsible to open and close the "more functions" submenu.
*/
import {ANIMATION_RATE_MS} from "core-app/globals/global-listeners/top-menu";
import ClickEvent = JQuery.ClickEvent;
function menu_top_position(menu:JQuery) {
// if an h2 tag follows the submenu should unfold out at the border
var menu_start_position;
if (menu.next().get(0) !== undefined && (menu.next().get(0).tagName === 'H2')) {
menu_start_position = menu.next().innerHeight()! + menu.next().position().top;
}
else if (menu.next().hasClass("wiki-content") && menu.next().children().next().first().get(0) != undefined && menu.next().children().next().first().get(0).tagName == 'H1') {
var wiki_heading = menu.next().children().next().first();
menu_start_position = wiki_heading.innerHeight()! + wiki_heading.position().top;
}
return menu_start_position;
}
function close_menu(event:any) {
var menu = jQuery(event.data.menu);
// do not close the menu, if the user accidentally clicked next to a menu item (but still within the menu)
if (event.target !== menu.find(" > li.drop-down.open > ul").get(0)) {
menu.find(" > li.drop-down.open").removeClass("open").find("> ul").slideUp(ANIMATION_RATE_MS);
// no need to watch for clicks, when the menu is already closed
jQuery('html').off('click', close_menu);
}
}
function open_menu(menu:JQuery) {
var drop_down = menu.find(" > li.drop-down");
// do not open a menu, which is already open
if (!drop_down.hasClass('open')) {
drop_down.find('> ul').slideDown(ANIMATION_RATE_MS, function () {
drop_down.find('li > a:first').focus();
// when clicking on something, which is not the menu, close the menu
jQuery('html').on('click', {menu: menu.get(0)}, close_menu);
});
drop_down.addClass('open');
}
}
// open the given submenu when clicking on it
export function install_menu_logic(menu:JQuery) {
menu.find(" > li.drop-down").on('click', (event:ClickEvent) => {
open_menu(menu);
// and prevent default action (href) for that element
// but not for the menu items.
var target = jQuery(event.target);
if (target.is('.drop-down') || target.closest('li, ul').is('.drop-down')) {
event.preventDefault();
}
});
}

@ -26,18 +26,43 @@
// See docs/COPYRIGHT.rdoc for more details. // See docs/COPYRIGHT.rdoc for more details.
//++ //++
jQuery(document).ready(function($) { /**
$("#project_responsible_id").autocomplete({ * Moved from app/assets/javascripts/colors.js
ajax: { *
null_element: {id: -1, name: I18n.t("js.filter.noneElement")}, * Make this a component instead of modifying it the next time
data: function (term, page) { * this needs changes
return { */
q: term, //search term export function makeColorPreviews() {
page_limit: 10, // page size jQuery('.color--preview').each(function () {
page: page, // current page number let preview = jQuery(this);
id: $("#project_responsible_id").attr("data-projectId") let input:any;
}; let func:any;
let target = preview.data('target');
if (target) {
input = jQuery(target);
} else {
input = preview.next('input');
} }
if (input.length === 0) {
return;
} }
func = function () {
var previewColor = '';
if (input.val() && input.val().length > 0) {
previewColor = input.val();
} else if (input.attr('placeholder') &&
input.attr('placeholder').length > 0) {
previewColor = input.attr('placeholder')
}
preview.css('background-color', previewColor);
};
input.keyup(func).change(func).focus(func);
func();
}); });
}); }

@ -26,29 +26,19 @@
// See docs/COPYRIGHT.rdoc for more details. // See docs/COPYRIGHT.rdoc for more details.
//++ //++
(function($) { // Moved from app/assets/javascript/danger_zone_validation.js
function toggleDescription() { // Make the whole danger zone a component the next time this needs changes!
let $el = $(this); export function dangerZoneValidation() {
let otherTrigger = $el.siblings('.projects-table--description-toggle'); // This will only work iff there is a single danger zone on the page
let clickedRow = $el.closest('.project'); var dangerZoneVerification = jQuery('.danger-zone--verification');
let descriptionRow = clickedRow.next(); var expectedValue = jQuery('.danger-zone--expected-value').text();
clickedRow.toggleClass('-no-highlighting'); dangerZoneVerification.find('input').on('input', function () {
clickedRow.toggleClass('-expanded'); var actualValue = dangerZoneVerification.find('input').val() as String;
descriptionRow.toggleClass('-expanded'); if (expectedValue.toLowerCase() === actualValue.toLowerCase()) {
dangerZoneVerification.find('button').prop('disabled', false);
if (descriptionRow.hasClass('-expanded')) {
$(descriptionRow).attr('aria-live', 'polite');
} else { } else {
$(descriptionRow).removeAttr('aria-live'); dangerZoneVerification.find('button').prop('disabled', true);
}
otherTrigger.focus();
return false;
} }
$('document').ready(function() {
$('.projects-table--description-toggle').click(toggleDescription);
}); });
})(jQuery); }

@ -0,0 +1,122 @@
/**
* Move from legacy app/assets/javascripts/application.js.erb
*
* This should not be loaded globally and ideally refactored into components
*/
export function listenToSettingChanges() {
jQuery('#settings_session_ttl_enabled').on('change', function () {
jQuery('#settings_session_ttl_container').toggle(jQuery(this).is(':checked'));
}).trigger('change');
/** Sync SCM vendor select when enabled SCMs are changed */
jQuery('[name="settings[enabled_scm][]"]').change(function (this:HTMLInputElement) {
var wasDisabled = !this.checked,
vendor = this.value,
select = jQuery('#settings_repositories_automatic_managed_vendor'),
option = select.find('option[value="' + vendor + '"]');
// Skip non-manageable SCMs
if (option.length === 0) {
return;
}
option.prop('disabled', wasDisabled);
if (wasDisabled && option.prop('selected')) {
select.val('');
}
});
/* Javascript for Settings::TextSettingCell */
let langSelectSwitchData = function (select:any) {
let self = jQuery(select);
let id:string = self.attr("id") || '';
let settingName = id.replace('lang-for-', '');
let newLang = self.val();
let textArea = jQuery(`#settings-${settingName}`);
let editor = textArea.siblings('ckeditor-augmented-textarea').data('editor');
return { id: id, settingName: settingName, newLang: newLang, textArea: textArea, editor: editor };
};
// Upon focusing:
// * store the current value of the editor in the hidden field for that lang.
// Upon change:
// * get the current value from the hidden field for that lang and set the editor text to that value.
// * Set the name of the textarea to reflect the current lang so that the value stored in the hidden field
// is overwritten.
jQuery(".lang-select-switch")
.focus(function () {
let data = langSelectSwitchData(this);
jQuery(`#${data.id}-${data.newLang}`).val(data.editor.getData());
})
.change(function () {
let data = langSelectSwitchData(this);
let storedValue = jQuery(`#${data.id}-${data.newLang}`).val();
data.editor.setData(storedValue);
data.textArea.attr('name', `settings[${data.settingName}][${data.newLang}]`);
});
/* end Javascript for Settings::TextSettingCell */
jQuery('.admin-settings--form').submit(function () {
/* Update consent time if consent required */
if (jQuery('#settings_consent_required').is(':checked') && jQuery('#toggle_consent_time').is(':checked')) {
jQuery('#settings_consent_time')
.val(new Date().toISOString())
.prop('disabled', false);
}
return true;
});
/** Toggle notification settings fields */
jQuery("#email_delivery_method_switch").on("change", function () {
const delivery_method = jQuery(this).val();
jQuery(".email_delivery_method_settings").hide();
jQuery("#email_delivery_method_" + delivery_method).show();
}).trigger("change");
jQuery('#settings_smtp_authentication').on('change', function () {
var isNone = jQuery(this).val() === 'none';
jQuery('#settings_smtp_user_name,#settings_smtp_password')
.closest('.form--field')
.toggle(!isNone);
});
/** Toggle repository checkout fieldsets required when option is disabled */
jQuery('.settings-repositories--checkout-toggle').change(function (this:HTMLInputElement) {
var wasChecked = this.checked,
fieldset = jQuery(this).closest('fieldset');
fieldset
.find('input,select')
.filter(':not([type=checkbox])')
.filter(':not([type=hidden])')
.removeAttr('required') // Rails 4.0 still seems to use attribute
.prop('required', wasChecked);
});
/** Toggle highlighted attributes visibility depending on if the highlighting mode 'inline' was selected*/
jQuery('.settings--highlighting-mode select').change(function () {
var highlightingMode = jQuery(this).val();
jQuery(".settings--highlighted-attributes").toggle(highlightingMode === "inline");
});
/** Initialize hightlighted attributes checkboxes. If none is selected, it means we want them all. So let's
* show them all as selected.
* On submitting the form, we remove all checkboxes before sending to communicate, we actually want all and not
* only the selected.*/
if (jQuery(".settings--highlighted-attributes input[type='checkbox']:checked").length === 0) {
jQuery(".settings--highlighted-attributes input[type='checkbox']").prop("checked", true);
}
jQuery('#tab-content-work_packages form').submit(function () {
var availableAttributes = jQuery(".settings--highlighted-attributes input[type='checkbox']");
var selectedAttributes = jQuery(".settings--highlighted-attributes input[type='checkbox']:checked");
if (selectedAttributes.length === availableAttributes.length) {
availableAttributes.prop("checked", false);
}
});
}

@ -0,0 +1,158 @@
// Legacy code ported from app/assets/javascripts/application.js.erb
// Do not add stuff here, but ideally remove into components whenver changes are necessary
export function setupServerResponse() {
initMainMenuExpandStatus();
focusFirstErroneousField();
activateFlashNotice();
activateFlashError();
autoHideFlashMessage();
flashCloseHandler();
jQuery(document).ajaxComplete(activateFlashNotice);
jQuery(document).ajaxComplete(activateFlashError);
/*
* 1 - registers a callback which copies the csrf token into the
* X-CSRF-Token header with each ajax request. Necessary to
* work with rails applications which have fixed
* CVE-2011-0447
* 2 - shows and hides ajax indicator
*/
jQuery(document).ajaxSend(function (event, request) {
if (jQuery(event.target.activeElement!).closest('[ajax-indicated]').length &&
jQuery('ajax-indicator')) {
jQuery('#ajax-indicator').show();
}
var csrf_meta_tag = jQuery('meta[name=csrf-token]');
if (csrf_meta_tag) {
var header = 'X-CSRF-Token',
token = csrf_meta_tag.attr('content');
request.setRequestHeader(header, token!);
}
request.setRequestHeader('X-Authentication-Scheme', "Session");
});
// ajaxStop gets called when ALL Requests finish, so we won't need a counter as in PT
jQuery(document).ajaxStop(function () {
if (jQuery('#ajax-indicator')) {
jQuery('#ajax-indicator').hide();
}
addClickEventToAllErrorMessages();
});
// show/hide the files table
jQuery(".attachments h4").click(function () {
jQuery(this).toggleClass("closed").next().slideToggle(100);
});
let resizeTo:any = null;
jQuery(window).on('resize', function () {
// wait 200 milliseconds for no further resize event
// then readjust breadcrumb
if (resizeTo) {
clearTimeout(resizeTo);
}
resizeTo = setTimeout(function () {
jQuery(window).trigger('resizeEnd');
}, 200);
});
// Do not close the login window when using it
jQuery('#nav-login-content').click(function (event) {
event.stopPropagation();
});
// Set focus on first error message
var error_focus = jQuery('a.afocus').first();
var input_focus = jQuery('.autofocus').first();
if (error_focus !== undefined) {
error_focus.focus();
} else if (input_focus !== undefined) {
input_focus.focus();
if (input_focus[0].tagName === "INPUT") {
input_focus.select();
}
}
// Focus on field with error
addClickEventToAllErrorMessages();
// Click handler for formatting help
jQuery(document.body).on('click', '.formatting-help-link-button', function () {
window.open(window.appBasePath + '/help/wiki_syntax',
"",
"resizable=yes, location=no, width=600, height=640, menubar=no, status=no, scrollbars=yes"
);
return false;
});
}
function flashCloseHandler() {
jQuery('body').on('click keydown touchend', '.close-handler,.notification-box--close', function (e) {
if (e.type === 'click' || e.which === 13) {
jQuery(this).parent('.flash, .errorExplanation, .notification-box')
.not('.persistent-toggle--notification')
.remove();
}
});
}
function autoHideFlashMessage() {
setTimeout(function () {
jQuery('.flash.autohide-notification').remove();
}, 5000);
}
function addClickEventToAllErrorMessages() {
jQuery('a.afocus').each(function () {
var target = jQuery(this);
target.click(function (evt) {
var field = jQuery('#' + target.attr('href')!.substr(1));
if (field === null) {
// Cut off '_id' (necessary for select boxes)
field = jQuery('#' + target.attr('href')!.substr(1).concat('_id'));
}
target.unbind(evt);
return false;
});
});
}
function initMainMenuExpandStatus() {
let wrapper = jQuery('#wrapper');
let upToggle = jQuery('ul.menu_root.closed li.open a.arrow-left-to-project');
if (upToggle.length === 1 && wrapper.hasClass('hidden-navigation')) {
upToggle.trigger('click');
}
}
function activateFlash(selector:any) {
let flashMessages = jQuery(selector);
flashMessages.each(function (ix, e) {
const flashMessage = jQuery(e);
flashMessage.show();
});
}
function activateFlashNotice() {
activateFlash('.flash');
}
function activateFlashError() {
activateFlash('.errorExplanation[role="alert"]');
}
function focusFirstErroneousField() {
const firstErrorSpan = jQuery('span.errorSpan').first();
const erroneousInput = firstErrorSpan.find('*').filter(":input");
erroneousInput.trigger('focus');
}

@ -26,7 +26,7 @@
// See docs/COPYRIGHT.rdoc for more details. // See docs/COPYRIGHT.rdoc for more details.
//++ //++
function createFieldsetToggleStateLabel(legend, text) { function createFieldsetToggleStateLabel(legend:JQuery, text:string) {
var labelClass = 'fieldset-toggle-state-label'; var labelClass = 'fieldset-toggle-state-label';
var toggleLabel = legend.find('a span.' + labelClass); var toggleLabel = legend.find('a span.' + labelClass);
var legendLink = legend.children('a'); var legendLink = legend.children('a');
@ -41,7 +41,7 @@ function createFieldsetToggleStateLabel(legend, text) {
toggleLabel.text(' ' + text); toggleLabel.text(' ' + text);
} }
function setFieldsetToggleState(fieldset) { function setFieldsetToggleState(fieldset:JQuery) {
var legend = fieldset.children('legend'); var legend = fieldset.children('legend');
@ -52,7 +52,7 @@ function setFieldsetToggleState(fieldset) {
} }
} }
function getFieldset(el) { function getFieldset(el:HTMLElement) {
var element = jQuery(el); var element = jQuery(el);
if (element.is('legend')) { if (element.is('legend')) {
@ -64,17 +64,17 @@ function getFieldset(el) {
throw "Cannot derive fieldset from element!"; throw "Cannot derive fieldset from element!";
} }
function toggleFieldset(el) { function toggleFieldset(el:HTMLElement) {
var fieldset = getFieldset(el); var fieldset = getFieldset(el);
var contentArea = fieldset.find('> div').not('.form--toolbar'); var contentArea = fieldset.find('> div').not('.form--toolbar');
fieldset.toggleClass('collapsed'); fieldset.toggleClass('collapsed');
contentArea.slideToggle('fast', null); contentArea.slideToggle('fast');
setFieldsetToggleState(fieldset); setFieldsetToggleState(fieldset);
} }
jQuery(document).ready(function() { export function setupToggableFieldsets() {
const fieldsets = jQuery('fieldset.form--fieldset.-collapsible'); const fieldsets = jQuery('fieldset.form--fieldset.-collapsible');
// Toggle on click // Toggle on click
@ -97,4 +97,4 @@ jQuery(document).ready(function() {
setFieldsetToggleState(fieldset); setFieldsetToggleState(fieldset);
}); });
}); }

@ -0,0 +1,255 @@
//-- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2020 the OpenProject GmbH
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2017 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// 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.
//
// See docs/COPYRIGHT.rdoc for more details.
//++
export const ANIMATION_RATE_MS = 100;
export class TopMenu {
private hover = false;
private menuIsOpen = false;
constructor(readonly menuContainer:JQuery) {
this.withHeadingFoldOutAtBorder();
this.setupDropdownClick();
this.registerEventHandlers();
this.closeOnBodyClick();
this.accessibility();
this.skipContentClickListener();
}
skipContentClickListener() {
// Skip menu on content
jQuery('#skip-navigation--content').on('click', () => {
// Skip to the breadcrumb or the first link in the toolbar or the first link in the content (homescreen)
const selectors = '.first-breadcrumb-element a, .toolbar-container a:first-of-type, #content a:first-of-type';
const visibleLink = jQuery(selectors)
.not(':hidden')
.first();
if (visibleLink.length) {
visibleLink.trigger('focus');
}
});
}
accessibility() {
jQuery(".drop-down > ul").attr("aria-expanded", "false");
}
toggleClick(dropdown:JQuery) {
if (this.menuIsOpen) {
if (this.isOpen(dropdown)) {
this.closing();
} else {
this.open(dropdown);
}
} else {
this.opening();
this.open(dropdown);
}
}
// somebody opens the menu via click, hover possible afterwards
opening() {
this.startHover();
this.menuIsOpen = true;
this.menuContainer.trigger("openedMenu", this.menuContainer);
}
// the entire menu gets closed, no hover possible afterwards
closing() {
this.stopHover();
this.closeAllItems();
this.menuIsOpen = false;
this.menuContainer.trigger("closedMenu", this.menuContainer);
}
stopHover() {
this.hover = false;
this.menuContainer.removeClass("hover");
}
startHover() {
this.hover = true;
this.menuContainer.addClass("hover");
}
closeAllItems() {
this.openDropdowns().each((ix, item) => {
this.close(jQuery(item));
});
}
closeOnBodyClick() {
var self = this;
document.getElementById('wrapper')!.addEventListener('click', function (evt) {
if (self.menuIsOpen && !self.openDropdowns()[0].contains(evt.target as HTMLElement)) {
self.closing();
}
}, true);
}
openDropdowns() {
return this.dropdowns().filter(".open");
}
dropdowns() {
return this.menuContainer.find("li.drop-down");
}
withHeadingFoldOutAtBorder() {
var menu_start_position;
if (this.menuContainer.next().get(0) !== undefined && (this.menuContainer.next().get(0).tagName === 'H2')) {
menu_start_position = this.menuContainer.next().innerHeight()! + this.menuContainer.next().position().top;
this.menuContainer.find("ul.menu-drop-down-container").css({ top: menu_start_position });
} else if (this.menuContainer.next().hasClass("wiki-content") &&
this.menuContainer.next().children().next().first().get(0) !== undefined &&
this.menuContainer.next().children().next().first().get(0).tagName === 'H1') {
var wiki_heading = this.menuContainer.next().children().next().first();
menu_start_position = wiki_heading.innerHeight()! + wiki_heading.position().top;
this.menuContainer.find("ul.menu-drop-down-container").css({ top: menu_start_position });
}
}
setupDropdownClick() {
var self = this;
this.dropdowns().each(function (ix, it) {
jQuery(it).click(function () {
self.toggleClick(jQuery(this));
return false;
});
jQuery(it).on('touchstart', function (e) {
// This shall avoid the hover event is fired,
// which would otherwise lead to menu being closed directly after its opened.
// Ignore clicks from within the dropdown
if (jQuery(e.target).closest('.menu-drop-down-container').length) {
return true;
}
e.preventDefault();
jQuery(this).click();
return false;
});
});
}
isOpen(dropdown:JQuery) {
return dropdown.filter(".open").length === 1;
}
isClosed(dropdown:JQuery) {
return !this.isOpen(dropdown);
}
open(dropdown:JQuery) {
this.dontCloseWhenUsing(dropdown);
this.closeOtherItems(dropdown);
this.slideAndFocus(dropdown, function () {
dropdown.trigger("opened", dropdown);
});
}
close(dropdown:JQuery, immediate?:any) {
this.slideUp(dropdown, immediate);
dropdown.trigger("closed", dropdown);
}
closeOtherItems(dropdown:JQuery) {
var self = this;
this.openDropdowns().each(function (ix, it) {
if (jQuery(it) !== jQuery(dropdown)) {
self.close(jQuery(it), true);
}
});
}
dontCloseWhenUsing(dropdown:JQuery) {
jQuery(dropdown).find("li").click(function (event) {
event.stopPropagation();
});
jQuery(dropdown).bind("mousedown mouseup click", function (event) {
event.stopPropagation();
});
}
slideAndFocus(dropdown:JQuery, callback:any) {
this.slideDown(dropdown, callback);
this.focusFirstInputOrLink(dropdown);
}
slideDown(dropdown:JQuery, callback:any) {
var toDrop = dropdown.find("> ul");
dropdown.addClass("open");
toDrop.slideDown(ANIMATION_RATE_MS, callback).attr("aria-expanded", "true");
}
slideUp(dropdown:JQuery, immediate:any) {
var toDrop = jQuery(dropdown).find("> ul");
dropdown.removeClass("open");
if (immediate) {
toDrop.hide();
} else {
toDrop.slideUp(ANIMATION_RATE_MS);
}
toDrop.attr("aria-expanded", "false");
}
// If there is ANY input, it will have precedence over links,
// i.e. links will only get focussed, if there is NO input whatsoever
focusFirstInputOrLink(dropdown:JQuery) {
var toFocus = dropdown.find("ul :input:visible:first");
if (toFocus.length === 0) {
toFocus = dropdown.find("ul a:visible:first");
}
// actually a simple focus should be enough.
// The rest is only there to work around a rendering bug in webkit (as of Oct 2011),
// occuring mostly inside the login/signup dropdown.
toFocus.blur();
setTimeout(function () {
toFocus.focus();
}, 10);
}
registerEventHandlers() {
const toggler = jQuery("#main-menu-toggle");
this.menuContainer.on("closeDropDown", (event) => {
this.close(jQuery(event.target));
}).on("openDropDown", (event) => {
this.open(jQuery(event.target));
}).on("closeMenu", () => {
this.closing();
}).on("openMenu", () => {
this.open(this.dropdowns().first());
this.opening();
});
toggler.on("click", () => { // click on hamburger icon is closing other menu
this.closing();
});
}
}

@ -0,0 +1,39 @@
export const demoProjectName = 'Demo project';
export const scrumDemoProjectName = 'Scrum project';
export const onboardingTourStorageKey = 'openProject-onboardingTour';
export type OnboardingTourNames = 'backlogs'|'taskboard'|'homescreen'|'main';
export function waitForElement(element:string, container:string, execFunction:Function) {
// Wait for the element to be ready
var observer = new MutationObserver(function (mutations, observerInstance) {
if (jQuery(element).length) {
observerInstance.disconnect(); // stop observing
execFunction();
return;
}
});
observer.observe(jQuery(container)[0], {
childList: true,
subtree: true
});
}
export function demoProjectsLinks() {
let demoProjects = [];
let demoProjectsLink = jQuery(".widget-box.welcome a:contains(" + demoProjectName + ")");
let scrumDemoProjectsLink = jQuery(".widget-box.welcome a:contains(" + scrumDemoProjectName + ")");
if (demoProjectsLink.length) {
demoProjects.push(demoProjectsLink);
}
if (scrumDemoProjectsLink.length) {
demoProjects.push(scrumDemoProjectsLink);
}
return demoProjects;
}
export function preventClickHandler(e:any) {
e.preventDefault();
e.stopPropagation();
}

@ -0,0 +1,94 @@
import {wpOnboardingTourSteps} from "core-app/globals/onboarding/tours/work_package_tour";
import {
demoProjectsLinks, OnboardingTourNames,
onboardingTourStorageKey,
preventClickHandler,
waitForElement
} from "core-app/globals/onboarding/helpers";
import {boardTourSteps} from "core-app/globals/onboarding/tours/boards_tour";
import {menuTourSteps} from "core-app/globals/onboarding/tours/menu_tour";
import {homescreenOnboardingTourSteps} from "core-app/globals/onboarding/tours/homescreen_tour";
import {scrumBacklogsTourSteps, scrumTaskBoardTourSteps} from "core-app/globals/onboarding/tours/backlogs_tour";
import {Injector} from "@angular/core";
require('core-vendor/enjoyhint');
declare global {
interface Window {
EnjoyHint:any;
}
}
export function start(name:OnboardingTourNames) {
switch (name) {
case 'backlogs':
initializeTour('startTaskBoardTour');
startTour(scrumBacklogsTourSteps());
break;
case 'taskboard':
initializeTour('startMainTourFromBacklogs');
startTour(scrumTaskBoardTourSteps());
break;
case 'homescreen':
initializeTour('startProjectTour', '.widget-box--blocks--buttons a', true);
startTour(homescreenOnboardingTourSteps());
break;
case 'main':
mainTour();
break;
}
}
function initializeTour(storageValue:any, disabledElements?:any, projectSelection?:any) {
window.onboardingTourInstance = new window.EnjoyHint({
onStart: function () {
jQuery('#content-wrapper, #menu-sidebar').addClass('-hidden-overflow');
},
onEnd: function () {
sessionStorage.setItem(onboardingTourStorageKey, storageValue);
jQuery('#content-wrapper, #menu-sidebar').removeClass('-hidden-overflow');
},
onSkip: function () {
sessionStorage.setItem(onboardingTourStorageKey, 'skipped');
if (disabledElements) {
jQuery(disabledElements).removeClass('-disabled').unbind('click', preventClickHandler);
}
if (projectSelection) {
$.each(demoProjectsLinks(), function (i, e) {
jQuery(e).off('click');
});
}
jQuery('#content-wrapper, #menu-sidebar').removeClass('-hidden-overflow');
}
});
}
function startTour(steps:any) {
window.onboardingTourInstance.set(steps);
window.onboardingTourInstance.run();
}
function mainTour() {
initializeTour('mainTourFinished');
const boardsDemoDataAvailable = jQuery('meta[name=boards_demo_data_available]').attr('content') === "true";
const eeTokenAvailable = !jQuery('body').hasClass('ee-banners-visible');
waitForElement('.work-package--results-tbody', '#content', function () {
let steps:any[];
// Check for EE edition, and available seed data of boards.
// Then add boards to the tour, otherwise skip it.
if (eeTokenAvailable && boardsDemoDataAvailable) {
steps = wpOnboardingTourSteps().concat(boardTourSteps()).concat(menuTourSteps());
} else {
steps = wpOnboardingTourSteps().concat(menuTourSteps());
}
startTour(steps);
});
}

@ -0,0 +1,68 @@
// Dynamically loads and triggers the onboarding tour
// when on the correct spots
import {demoProjectsLinks, OnboardingTourNames, onboardingTourStorageKey} from "core-app/globals/onboarding/helpers";
import {debugLog} from "core-app/helpers/debug_output";
export function detectOnboardingTour() {
// ------------------------------- Global -------------------------------
const url = new URL(window.location.href);
const isMobile = document.body.classList.contains('-browser-mobile');
const demoProjectsAvailable = jQuery('meta[name=demo_projects_available]').attr('content') === "true";
let currentTourPart = sessionStorage.getItem(onboardingTourStorageKey);
let tourCancelled = false;
// ------------------------------- Initial start -------------------------------
// Do not show the tutorial on mobile or when the demo data has been deleted
if (!isMobile && demoProjectsAvailable) {
// Start after the intro modal (language selection)
// This has to be changed once the project selection is implemented
if (url.searchParams.get("first_time_user") && demoProjectsLinks().length === 2) {
currentTourPart = '';
sessionStorage.setItem(onboardingTourStorageKey, 'readyToStart');
// Start automatically when the language selection is closed
jQuery('.op-modal--modal-close-button').click(function () {
tourCancelled = true;
triggerTour('homescreen');
});
//Start automatically when the escape button is pressed
document.addEventListener('keydown', function (event) {
if (event.key === "Escape" && !tourCancelled) {
tourCancelled = true;
triggerTour('homescreen');
}
}, { once: true });
}
// ------------------------------- Tutorial Homescreen page -------------------------------
if (currentTourPart === "readyToStart") {
triggerTour('homescreen');
}
// ------------------------------- Tutorial WP page -------------------------------
if (currentTourPart === "startMainTourFromBacklogs" || url.searchParams.get("start_onboarding_tour")) {
triggerTour('main');
}
// ------------------------------- Tutorial Backlogs page -------------------------------
if (url.searchParams.get("start_scrum_onboarding_tour")) {
if (jQuery('.backlogs-menu-item').length > 0) {
triggerTour('backlogs');
}
}
// ------------------------------- Tutorial Task Board page -------------------------------
if (currentTourPart === "startTaskBoardTour") {
triggerTour('taskboard');
}
}
}
async function triggerTour(name:OnboardingTourNames) {
debugLog("Loading and triggering onboarding tour " + name);
const tour = await import(/* webpackChunkName: "onboarding-tour" */ './onboarding_tour');
tour.start(name);
}

@ -0,0 +1,52 @@
import {onboardingTourStorageKey} from "core-app/globals/onboarding/helpers";
export function scrumBacklogsTourSteps() {
return [
{
'next #content-wrapper': I18n.t('js.onboarding.steps.backlogs.overview'),
'showSkip': false,
'nextButton': { text: I18n.t('js.onboarding.buttons.next') },
'containerClass': '-dark -hidden-arrow'
},
{
'event_type': 'next',
'selector': '#sprint_backlogs_container .backlog .menu-trigger',
'description': I18n.t('js.onboarding.steps.backlogs.task_board_arrow'),
'showSkip': false,
'nextButton': { text: I18n.t('js.onboarding.buttons.next') },
onNext: function () {
jQuery('#sprint_backlogs_container .backlog .menu-trigger')[0].click();
}
},
{
'event_type': 'next',
'selector': '#sprint_backlogs_container .backlog .menu .items',
'description': I18n.t('js.onboarding.steps.backlogs.task_board_select'),
'showSkip': false,
'nextButton': { text: I18n.t('js.onboarding.buttons.next') },
'containerClass': '-dark',
onNext: function () {
jQuery('#sprint_backlogs_container .backlog .show_task_board')[0].click();
}
}
];
}
export function scrumTaskBoardTourSteps() {
return [
{
'next #content-wrapper': I18n.t('js.onboarding.steps.backlogs.task_board'),
'showSkip': false,
'nextButton': { text: I18n.t('js.onboarding.buttons.next') },
'containerClass': '-dark -hidden-arrow'
},
{
'next #main-menu-work-packages-wrapper': I18n.t('js.onboarding.steps.wp.toggler'),
'showSkip': false,
'nextButton': { text: I18n.t('js.onboarding.buttons.next') },
onNext: function () {
jQuery('#main-menu-work-packages')[0].click();
}
},
];
}

@ -0,0 +1,44 @@
import {waitForElement} from "core-app/globals/onboarding/helpers";
export function boardTourSteps() {
return [
{
'next .board-view-menu-item': I18n.t('js.onboarding.steps.boards.overview'),
'showSkip': false,
'nextButton': { text: I18n.t('js.onboarding.buttons.next') },
onNext: function () {
jQuery('.board-view-menu-item ~ .toggler')[0].click();
waitForElement('.boards--menu-items', '#main-menu', function () {
jQuery(".main-menu--children-sub-item:contains('Kanban')")[0].click();
});
}
},
{
'next .board-list--container': I18n.t('js.onboarding.steps.boards.lists'),
'showSkip': false,
'nextButton': { text: I18n.t('js.onboarding.buttons.next') },
'containerClass': '-dark -hidden-arrow',
'timeout': function () {
return new Promise(function (resolve) {
waitForElement('.wp-card', '#content', function () {
resolve();
});
});
}
},
{
'next .board-list--add-button': I18n.t('js.onboarding.steps.boards.add'),
'showSkip': false,
'nextButton': { text: I18n.t('js.onboarding.buttons.next') },
},
{
'next .boards-list--container': I18n.t('js.onboarding.steps.boards.drag'),
'showSkip': false,
'nextButton': { text: I18n.t('js.onboarding.buttons.next') },
'containerClass': '-dark -hidden-arrow',
onNext: function () {
jQuery('.main-menu--arrow-left-to-project')[0].click();
}
}
];
}

@ -0,0 +1,35 @@
import {demoProjectName, preventClickHandler, scrumDemoProjectName} from "core-app/globals/onboarding/helpers";
export function homescreenOnboardingTourSteps() {
return [
{
'next #top-menu': I18n.t('js.onboarding.steps.welcome'),
'skipButton': { className: 'enjoyhint_btn-transparent', text: I18n.t('js.onboarding.buttons.skip') },
'nextButton': { text: I18n.t('js.onboarding.buttons.next') },
'containerClass': '-hidden-arrow',
'bottom': 7
},
{
'description': I18n.t('js.onboarding.steps.project_selection'),
'selector': '.widget-box.welcome',
'event': 'custom',
'showSkip': false,
'containerClass': '-dark -hidden-arrow',
'clickable': true,
onBeforeStart: function () {
// Handle the correct project selection and redirection
// This will be removed once the project selection is implemented
jQuery(".widget-box.welcome a:contains(" + scrumDemoProjectName + ")").click(function (this:HTMLAnchorElement) {
window.onboardingTourInstance.trigger('next');
window.location.href = this.href + '/backlogs/?start_scrum_onboarding_tour=true';
});
jQuery(".widget-box.welcome a:contains(" + demoProjectName + ")").click(function (this:HTMLAnchorElement) {
window.onboardingTourInstance.trigger('next');
window.location.href = this.href + '/work_packages/?start_onboarding_tour=true';
});
// Disable clicks on other links
jQuery('.widget-box.welcome a').addClass('-disabled').bind('click', preventClickHandler);
}
}
];
}

@ -0,0 +1,20 @@
export function menuTourSteps() {
return [
{
'next .members-menu-item': I18n.t('js.onboarding.steps.members'),
'showSkip': false,
'nextButton': { text: I18n.t('js.onboarding.buttons.next') },
},
{
'next .wiki-menu--main-item': I18n.t('js.onboarding.steps.wiki'),
'showSkip': false,
'nextButton': { text: I18n.t('js.onboarding.buttons.next') },
},
{
'next .menu-item--help': I18n.t('js.onboarding.steps.help_menu'),
'shape': 'circle',
'showSkip': false,
'nextButton': { text: I18n.t('js.onboarding.buttons.got_it') }
}
];
}

@ -0,0 +1,70 @@
import {waitForElement} from "core-app/globals/onboarding/helpers";
export function wpOnboardingTourSteps():any[] {
return [
{
'next .wp-table--row': I18n.t('js.onboarding.steps.wp.list'),
'showSkip': false,
'nextButton': { text: I18n.t('js.onboarding.buttons.next') },
onNext: function () {
jQuery(".inline-edit--display-field.id a ")[0].click();
}
},
{
'next .work-packages-full-view--split-left': I18n.t('js.onboarding.steps.wp.full_view'),
'showSkip': false,
'nextButton': { text: I18n.t('js.onboarding.buttons.next') },
'containerClass': '-dark -hidden-arrow'
},
{
'next .work-packages-back-button': I18n.t('js.onboarding.steps.wp.back_button'),
'showSkip': false,
'nextButton': { text: I18n.t('js.onboarding.buttons.next') },
onNext: function () {
jQuery('.work-packages-back-button')[0].click();
}
},
{
'next .add-work-package': I18n.t('js.onboarding.steps.wp.create_button'),
'showSkip': false,
'nextButton': { text: I18n.t('js.onboarding.buttons.next') },
'shape': 'circle',
'timeout': function () {
return new Promise(function (resolve) {
// We are waiting here for the badge to appear,
// because its the last that appears and it shifts the WP create button to the left.
// Thus it is important that the tour rendering starts after the badge is visible
waitForElement('#work-packages-filter-toggle-button .badge', '#content', function () {
resolve();
});
});
},
onNext: function () {
jQuery('#wp-view-toggle-button').click();
}
},
{
'next #wp-view-toggle-button': I18n.t('js.onboarding.steps.wp.timeline_button'),
'showSkip': false,
'nextButton': { text: I18n.t('js.onboarding.buttons.next') },
'bottom': '-100',
onNext: function () {
jQuery('#wp-view-context-menu .icon-view-timeline')[0].click();
}
},
{
'next .work-packages-tabletimeline--timeline-side': I18n.t('js.onboarding.steps.wp.timeline'),
'showSkip': false,
'nextButton': { text: I18n.t('js.onboarding.buttons.next') },
'containerClass': '-dark -hidden-arrow'
},
{
'next .main-menu--arrow-left-to-project': I18n.t('js.onboarding.steps.sidebar_arrow'),
'showSkip': false,
'nextButton': { text: I18n.t('js.onboarding.buttons.next') },
onNext: function () {
jQuery('.main-menu--arrow-left-to-project')[0].click();
}
}
];
}

@ -29,6 +29,7 @@
import {OpenProjectPluginContext} from 'core-app/modules/plugins/plugin-context'; import {OpenProjectPluginContext} from 'core-app/modules/plugins/plugin-context';
import {input, InputState} from 'reactivestates'; import {input, InputState} from 'reactivestates';
import {take} from 'rxjs/operators'; import {take} from 'rxjs/operators';
import {GlobalHelpers} from "core-app/globals/global-helpers";
/** /**
* OpenProject instance methods * OpenProject instance methods
@ -37,6 +38,8 @@ export class OpenProject {
public pluginContext:InputState<OpenProjectPluginContext> = input<OpenProjectPluginContext>(); public pluginContext:InputState<OpenProjectPluginContext> = input<OpenProjectPluginContext>();
public helpers = new GlobalHelpers();
/** Globally setable variable whether the page was edited */ /** Globally setable variable whether the page was edited */
public pageWasEdited:boolean = false; public pageWasEdited:boolean = false;
/** Globally setable variable whether the page form is submitted. /** Globally setable variable whether the page form is submitted.

@ -30,7 +30,6 @@ import 'hammerjs';
// Global scripts previously part of the application.js // Global scripts previously part of the application.js
// Avoid require.context since that crashes angular regularly // Avoid require.context since that crashes angular regularly
require('./globals/augmenting/modal-wrapper.augment.service');
require('./globals/dynamic-bootstrapper'); require('./globals/dynamic-bootstrapper');
require('./globals/global-listeners'); require('./globals/global-listeners');
require('./globals/openproject'); require('./globals/openproject');

@ -75,7 +75,7 @@ require('moment-timezone/builds/moment-timezone-with-data.min.js');
require('expose-loader?URI!urijs'); require('expose-loader?URI!urijs');
require('urijs/src/URITemplate'); require('urijs/src/URITemplate');
require("expose-loader?I18n!../vendor/i18n"); require("expose-loader?I18n!core-vendor/i18n");
// Localization for fullcalendar // Localization for fullcalendar
require("@fullcalendar/core/locales-all.min"); require("@fullcalendar/core/locales-all.min");

@ -1,4 +1,4 @@
import {Component, ElementRef, OnInit} from '@angular/core'; import {AfterViewInit, Component, ElementRef, OnInit} from '@angular/core';
import {TypeBannerService} from 'core-app/modules/admin/types/type-banner.service'; import {TypeBannerService} from 'core-app/modules/admin/types/type-banner.service';
import {I18nService} from 'core-app/modules/common/i18n/i18n.service'; import {I18nService} from 'core-app/modules/common/i18n/i18n.service';
import {NotificationsService} from 'core-app/modules/common/notifications/notifications.service'; import {NotificationsService} from 'core-app/modules/common/notifications/notifications.service';
@ -9,6 +9,7 @@ import {ConfirmDialogService} from 'core-components/modals/confirm-dialog/confir
import {Drake} from 'dragula'; import {Drake} from 'dragula';
import {GonService} from "core-app/modules/common/gon/gon.service"; import {GonService} from "core-app/modules/common/gon/gon.service";
import {UntilDestroyedMixin} from "core-app/helpers/angular/until-destroyed.mixin"; import {UntilDestroyedMixin} from "core-app/helpers/angular/until-destroyed.mixin";
import {install_menu_logic} from "core-app/globals/global-listeners/action-menu";
export type TypeGroupType = 'attribute'|'query'; export type TypeGroupType = 'attribute'|'query';
@ -37,7 +38,7 @@ export const adminTypeFormConfigurationSelector = 'admin-type-form-configuration
TypeBannerService, TypeBannerService,
] ]
}) })
export class TypeFormConfigurationComponent extends UntilDestroyedMixin implements OnInit { export class TypeFormConfigurationComponent extends UntilDestroyedMixin implements OnInit, AfterViewInit {
public text = { public text = {
drag_to_activate: this.I18n.t('js.admin.type_form.drag_to_activate'), drag_to_activate: this.I18n.t('js.admin.type_form.drag_to_activate'),
@ -145,6 +146,11 @@ export class TypeFormConfigurationComponent extends UntilDestroyedMixin implemen
}); });
} }
ngAfterViewInit() {
const menu = jQuery(this.elementRef.nativeElement).find('.toolbar-items');
install_menu_logic(menu);
}
public deactivateAttribute(attribute:TypeFormAttribute) { public deactivateAttribute(attribute:TypeFormAttribute) {
this.updateInactives(this.inactives.concat(attribute)); this.updateInactives(this.inactives.concat(attribute));
} }

@ -10,8 +10,7 @@
</button> </button>
</li> </li>
<li class="toolbar-item drop-down"> <li class="toolbar-item drop-down">
<a ng-click="showGroupsContextMenu($event)" <a class="form-configuration--add-group button -alt-highlight" aria-haspopup="true">
class="form-configuration--add-group button -alt-highlight" aria-haspopup="true">
<op-icon icon-classes="button--icon icon-add"></op-icon> <op-icon icon-classes="button--icon icon-add"></op-icon>
<span class="button--text" [textContent]="text.label_group"></span> <span class="button--text" [textContent]="text.label_group"></span>
<op-icon icon-classes="button--dropdown-indicator"></op-icon> <op-icon icon-classes="button--dropdown-indicator"></op-icon>

@ -0,0 +1,48 @@
var Administration = (function ($) {
var update_default_language_options,
init_language_selection_handling,
toggle_default_language_select;
update_default_language_options = function (input) {
var default_language_select = $('#setting_default_language select'),
default_language_select_active;
if (input.attr('checked')) {
default_language_select.find('option[value="' + input.val() + '"]').removeAttr('disabled');
} else {
default_language_select.find('option[value="' + input.val() + '"]').attr('disabled', 'disabled');
}
default_language_select_active = default_language_select.find('option:not([disabled="disabled"])');
toggle_disabled_state(default_language_select_active.length === 0);
if (default_language_select_active.length === 1) {
default_language_select_active.attr('selected', true);
} else if (default_language_select.val() === input.val() && !input.attr('checked')) {
default_language_select_active.first().attr('selected', true);
}
};
toggle_disabled_state = function (active) {
jQuery('#setting_default_language select').attr('disabled', active)
.closest('form')
.find('input:submit')
.attr('disabled', active);
};
init_language_selection_handling = function () {
jQuery('#setting_available_languages input:not([checked="checked"])').each(function (index, input) {
update_default_language_options($(input));
});
jQuery('#setting_available_languages input').click(function () {
update_default_language_options($(this));
});
};
return {
init_language_selection_handling: init_language_selection_handling
};
}(jQuery));
Administration.init_language_selection_handling();

@ -26,16 +26,22 @@
// See docs/COPYRIGHT.rdoc for more details. // See docs/COPYRIGHT.rdoc for more details.
//++ //++
(function($) { require('core-vendor/jquery.flot/jquery.flot');
$(function() { require('core-vendor/jquery.flot/excanvas');
/* require('core-vendor/jquery.jeditable.mini');
* @see /app/views/search/index.html.erb require('core-vendor/jquery.cookie');
*/ require('core-vendor/jquery.colorcontrast');
if ($('#search-filter').length < 1) {
return;
}
$('#search-input').focus(); require('./backlogs/common');
require('./backlogs/master_backlog');
}); require('./backlogs/backlog');
}(jQuery)); require('./backlogs/burndown');
require('./backlogs/model');
require('./backlogs/editable_inplace');
require('./backlogs/sprint');
require('./backlogs/work_package');
require('./backlogs/story');
require('./backlogs/task');
require('./backlogs/impediment');
require('./backlogs/taskboard');
require('./backlogs/show_main');

@ -0,0 +1,161 @@
//-- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2020 the OpenProject GmbH
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2017 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// 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.
//
// See docs/COPYRIGHT.rdoc for more details.
//++
if (window.RB === null || window.RB === undefined) {
window.RB = {};
}
(function ($) {
var object, Factory, Dialog, UserPreferences,
ajax;
object = {
// Douglas Crockford's technique for object extension
// http://javascript.crockford.com/prototypal.html
create: function () {
var obj, i, methods, methodName;
function F() {
}
F.prototype = arguments[0];
obj = new F();
// Add all the other arguments as mixins that
// 'write over' any existing methods
for (i = 1; i < arguments.length; i += 1) {
methods = arguments[i];
if (typeof methods === 'object') {
for (methodName in methods) {
if (methods.hasOwnProperty(methodName)) {
obj[methodName] = methods[methodName];
}
}
}
}
return obj;
}
};
// Object factory for chiliproject_backlogs
Factory = object.create({
initialize: function (objType, el) {
var obj;
obj = object.create(objType);
obj.initialize(el);
return obj;
}
});
// Utilities
Dialog = object.create({
msg: function (msg) {
var dialog, baseClasses;
baseClasses = 'ui-button ui-widget ui-state-default ui-corner-all';
if ($('#msgBox').length === 0) {
dialog = $('<div id="msgBox"></div>').appendTo('body');
}
else {
dialog = $('#msgBox');
}
dialog.html(msg);
dialog.dialog({
title: 'Backlogs Plugin',
buttons: [
{
text: 'OK',
class: 'button -highlight',
click: function () {
$(this).dialog("close");
}
}],
modal: true
});
$('.button').removeClass(baseClasses);
$('.ui-icon-closethick').prop('title', 'close');
}
});
ajax = (function () {
var ajaxQueue, ajaxOngoing,
processAjaxQueue;
ajaxQueue = [];
ajaxOngoing = false;
processAjaxQueue = function () {
var options = ajaxQueue.shift();
if (options !== null && options !== undefined) {
ajaxOngoing = true;
$.ajax(options);
}
};
// Process outstanding entries in the ajax queue whenever a ajax request
// finishes.
$(document).ajaxComplete(function (event, xhr, settings) {
ajaxOngoing = false;
processAjaxQueue();
});
return function (options) {
ajaxQueue.push(options);
if (!ajaxOngoing) {
processAjaxQueue();
}
};
}());
// Abstract the user preference from the rest of the RB objects
// so that we can change the underlying implementation as needed
UserPreferences = object.create({
get: function (key) {
return $.cookie(key);
},
set: function (key, value) {
$.cookie(key, value, { expires: 365 * 10 });
}
});
RB.Object = object;
RB.Factory = Factory;
RB.Dialog = Dialog;
RB.UserPreferences = UserPreferences;
RB.ajax = ajax;
}(jQuery));

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save