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
parent
97fad89177
commit
10e21154f7
@ -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/lib/**/* |
||||
|
@ -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); |
@ -1,4 +1,4 @@ |
||||
fallbacks: :en |
||||
translations: |
||||
- file: "app/assets/javascripts/locales/%{locale}.js" |
||||
- file: "frontend/src/locales/%{locale}.js" |
||||
only: ['*.js', '*.number.*', '*.time.*', '*.date.*'] |
||||
|
@ -1,25 +1,6 @@ |
||||
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( |
||||
favicon.ico |
||||
admin_users.js |
||||
autocompleter.js |
||||
copy_issue_actions.js |
||||
date-de-DE.js |
||||
date-en-US.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 |
||||
|
@ -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(); |
||||
} |
||||
}); |
||||
} |
@ -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'); |
||||
} |
||||
|
@ -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(); |
||||
} |
||||
} |
||||
]; |
||||
} |
@ -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(); |
@ -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…
Reference in new issue