|
|
|
//-- copyright
|
|
|
|
// OpenProject is a project management system.
|
|
|
|
// Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
|
|
|
|
//
|
|
|
|
// 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 doc/COPYRIGHT.rdoc for more details.
|
|
|
|
//++
|
|
|
|
|
|
|
|
//= require ./bundles/openproject-global
|
|
|
|
|
|
|
|
//# require jquery_noconflict
|
|
|
|
//= require prototype
|
|
|
|
//= require effects
|
|
|
|
//= require dragdrop
|
|
|
|
//= require controls
|
|
|
|
|
|
|
|
//= require lib/jquery.colorcontrast
|
|
|
|
//= require lib/jquery.trap
|
|
|
|
|
|
|
|
//= require top_menu
|
|
|
|
//= require action_menu
|
|
|
|
//= require openproject
|
|
|
|
//= require breadcrumb
|
|
|
|
//= require findDomElement
|
|
|
|
//= require jstoolbar
|
|
|
|
//= require ajaxappender
|
|
|
|
//= require settings
|
|
|
|
//= require modal
|
|
|
|
//= require tab_handling
|
|
|
|
//= require top-shelf
|
|
|
|
//= require unsupported-browsers
|
|
|
|
//= require autocomplete_textareas
|
|
|
|
//= require warn_leaving_unsaved
|
introduce plugin hook for global assets
OpenProject plugins may now register assets to be included globally
on every OpenProject page.
This should be usefull for Tracking plugins, AB-Testing, and similar
things.
If you use the openproject-plugins gem, do the following to register
your asset:
<pre>
module OpenProject::MyFancyPlugin
class Engine < ::Rails::Engine
engine_name :openproject_my_fancy_plugin
include OpenProject::Plugins::ActsAsOpEngine
register 'openproject-MyFancyPlugin',
:author_url => 'http://openproject.org',
:requires_openproject => '>= 3.0.0',
:global_assets => {js: 'fancy.js',
css: ['cool.css', 'another.css']}
initializer 'my_fancy_plugin.precompile_assets' do |app|
app.config.assets.precompile +=
['fancy.js','cool.css','another.css']
end
end
end
</code>
11 years ago
|
|
|
//= require openproject_plugins
|
|
|
|
//= require versions
|
|
|
|
//= require_tree ./specific
|
|
|
|
|
|
|
|
//= require ./bundles/openproject-core-app
|
|
|
|
//= require_tree ./bundles
|
|
|
|
|
|
|
|
//= require custom-fields
|
|
|
|
//= require date-range
|
|
|
|
//= require search
|
|
|
|
//= require colors
|
|
|
|
|
|
|
|
//source: http://stackoverflow.com/questions/8120065/jquery-and-prototype-dont-work-together-with-array-prototype-reverse
|
|
|
|
if (typeof []._reverse == 'undefined') {
|
|
|
|
jQuery.fn.reverse = Array.prototype.reverse;
|
|
|
|
} else {
|
|
|
|
jQuery.fn.reverse = Array.prototype._reverse;
|
|
|
|
}
|
|
|
|
|
|
|
|
jQuery(document).ready(function ($) {
|
|
|
|
//remove modal layout=false if we ctrl-click!
|
|
|
|
$(document.body).on("click", "a", function (e) {
|
|
|
|
if (top != self && e.ctrlKey) {
|
|
|
|
if (e.target && e.target.href) {
|
|
|
|
var url = e.target.href;
|
|
|
|
if (url.match(/(&)?layout=false/)) {
|
|
|
|
url = url.replace(/(&)?layout=false/g, "").replace(/\?$/, "");
|
|
|
|
window.open(url);
|
|
|
|
e.preventDefault();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
$(document).on('click', '#show_more_wp_properties', function(el){
|
|
|
|
$(this).find('.icon').toggleClass('icon-arrow-right6-3 icon-arrow-right6-1');
|
|
|
|
Effect.toggle("work_package_descr_fields", "appear", {duration:0.3});
|
|
|
|
enable_textarea_auto_completion(jQuery("#work_package_description"));
|
|
|
|
|
|
|
|
return false;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
function checkAll (id, checked) {
|
|
|
|
var els = Element.descendants(id);
|
|
|
|
for (var i = 0; i < els.length; i++) {
|
|
|
|
if (els[i].disabled === false) {
|
|
|
|
els[i].checked = checked;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function toggleCheckboxesBySelector(selector) {
|
|
|
|
boxes = $$(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;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO de-implement once table component has been used
|
|
|
|
function setGroupRowStatus(el) {
|
|
|
|
var collapseExpandButton = jQuery(el);
|
|
|
|
var row = jQuery(el).closest("tr");
|
|
|
|
var isOpen = row.hasClass("open");
|
|
|
|
var toggleAllLink = row.find(".toggle-all");
|
|
|
|
var toggleAllLinkText = isOpen ? I18n.t('js.label_collapse_all') : I18n.t('js.label_expand_all');
|
|
|
|
|
|
|
|
collapseExpandButton.toggleClass("icon-minus");
|
|
|
|
collapseExpandButton.toggleClass("icon-plus");
|
|
|
|
toggleAllLink.text(toggleAllLinkText);
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO de-implement once table component has been used
|
|
|
|
function toggleRowGroup(el) {
|
|
|
|
var row = jQuery(el).closest("tr");
|
|
|
|
|
|
|
|
row.toggleClass("open closed");
|
|
|
|
|
|
|
|
setGroupRowStatus(el);
|
|
|
|
|
|
|
|
row.nextUntil("tr.group").each(function(index) {
|
|
|
|
jQuery(this).toggle();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO de-implement once table component has been used
|
|
|
|
function toggleAllRowGroups(el) {
|
|
|
|
var row = jQuery(el).closest("tr");
|
|
|
|
var collapseExpandButton = jQuery(row).find(".expander");
|
|
|
|
var isOpen = row.hasClass("open");
|
|
|
|
|
|
|
|
toggleRowGroup(collapseExpandButton);
|
|
|
|
|
|
|
|
row.siblings("tr.group").each(function(index) {
|
|
|
|
var currentGroupOpen = jQuery(this).hasClass("open");
|
|
|
|
var collapseExpandButton = jQuery(this).find(".expander");
|
|
|
|
|
|
|
|
if (isOpen == currentGroupOpen) {
|
|
|
|
toggleRowGroup(collapseExpandButton);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function hideFieldset(el) {
|
|
|
|
var fieldset = Element.up(el, 'fieldset');
|
|
|
|
fieldset.toggleClassName('collapsed');
|
|
|
|
fieldset.down('>div').hide();
|
|
|
|
}
|
|
|
|
|
|
|
|
var fileFieldCount = 1;
|
|
|
|
|
|
|
|
function addFileField() {
|
|
|
|
fileFieldCount++;
|
|
|
|
if (fileFieldCount >= 10) return false;
|
|
|
|
var clone = $('attachment_template').cloneNode(true);
|
|
|
|
clone.writeAttribute('id', '');
|
|
|
|
clone.innerHTML = clone.innerHTML.replace(/\[1\]/g, '['+ fileFieldCount + ']');
|
|
|
|
$('attachments_fields').appendChild(clone);
|
|
|
|
}
|
|
|
|
|
|
|
|
function setPredecessorFieldsVisibility() {
|
|
|
|
relationType = $('relation_relation_type');
|
|
|
|
if (relationType && (relationType.value == "precedes" || relationType.value == "follows")) {
|
|
|
|
Element.show('predecessor_fields');
|
|
|
|
} else {
|
|
|
|
Element.hide('predecessor_fields');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function promptToRemote(text, param, url) {
|
|
|
|
value = prompt(text + ':', '');
|
|
|
|
if (value) {
|
|
|
|
new Ajax.Request(url + '?' + param + '=' + encodeURIComponent(value), {asynchronous:true, evalScripts:true});
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function collapseScmEntry(id) {
|
|
|
|
var els = document.getElementsByClassName(id, 'browser');
|
|
|
|
for (var i = 0; i < els.length; i++) {
|
|
|
|
if (els[i].hasClassName('open')) {
|
|
|
|
collapseScmEntry(els[i].id);
|
|
|
|
}
|
|
|
|
Element.hide(els[i]);
|
|
|
|
}
|
|
|
|
$(id).removeClassName('open');
|
|
|
|
}
|
|
|
|
|
|
|
|
function expandScmEntry(id) {
|
|
|
|
var els = document.getElementsByClassName(id, 'browser');
|
|
|
|
for (var i = 0; i < els.length; i++) {
|
|
|
|
Element.show(els[i]);
|
|
|
|
if (els[i].hasClassName('loaded') && !els[i].hasClassName('collapsed')) {
|
|
|
|
expandScmEntry(els[i].id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$(id).addClassName('open');
|
|
|
|
}
|
|
|
|
|
|
|
|
function scmEntryClick(id) {
|
|
|
|
el = $(id);
|
|
|
|
if (el.hasClassName('open')) {
|
|
|
|
collapseScmEntry(id);
|
|
|
|
el.addClassName('collapsed');
|
|
|
|
return false;
|
|
|
|
} else if (el.hasClassName('loaded')) {
|
|
|
|
expandScmEntry(id);
|
|
|
|
el.removeClassName('collapsed');
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (el.hasClassName('loading')) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
el.addClassName('loading');
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
function scmEntryLoaded(id) {
|
|
|
|
Element.addClassName(id, 'open');
|
|
|
|
Element.addClassName(id, 'loaded');
|
|
|
|
Element.removeClassName(id, 'loading');
|
|
|
|
}
|
|
|
|
|
|
|
|
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 = $('project_name').getValue(); // 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() {
|
|
|
|
var f = function() {
|
|
|
|
if(!projectIdentifierLocked) {
|
|
|
|
$('project_identifier').setValue(generateProjectIdentifier());
|
|
|
|
}
|
|
|
|
};
|
|
|
|
Event.observe('project_name', 'keyup', f);
|
|
|
|
}
|
|
|
|
|
|
|
|
function observeProjectIdentifier() {
|
|
|
|
var f = function() {
|
|
|
|
if($('project_identifier').getValue() !== '' && $('project_identifier').getValue() != generateProjectIdentifier()) {
|
|
|
|
projectIdentifierLocked = true;
|
|
|
|
} else {
|
|
|
|
projectIdentifierLocked = false;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
Event.observe('project_identifier', 'keyup', f);
|
|
|
|
}
|
|
|
|
|
|
|
|
function observeParentIssueField(url) {
|
|
|
|
new Ajax.Autocompleter('issue_parent_id',
|
|
|
|
'parent_issue_candidates',
|
|
|
|
url,
|
|
|
|
{ minChars: 1,
|
|
|
|
frequency: 0.5,
|
|
|
|
paramName: 'q',
|
|
|
|
updateElement: function(value) {
|
|
|
|
document.getElementById('issue_parent_id').value = value.id;
|
|
|
|
},
|
|
|
|
parameters: 'scope=all'
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function observeWorkPackageParentField(url) {
|
|
|
|
new Ajax.Autocompleter('work_package_parent_id',
|
|
|
|
'parent_issue_candidates',
|
|
|
|
url,
|
|
|
|
{ minChars: 1,
|
|
|
|
frequency: 0.5,
|
|
|
|
paramName: 'q',
|
|
|
|
updateElement: function(value) {
|
|
|
|
document.getElementById('work_package_parent_id').value = value.id;
|
|
|
|
},
|
|
|
|
parameters: 'scope=all'
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function observeRelatedIssueField(url) {
|
|
|
|
new Ajax.Autocompleter('relation_to_id',
|
|
|
|
'related_issue_candidates',
|
|
|
|
url,
|
|
|
|
{ minChars: 1,
|
|
|
|
frequency: 0.5,
|
|
|
|
paramName: 'q',
|
|
|
|
updateElement: function(value) {
|
|
|
|
document.getElementById('relation_to_id').value = value.id;
|
|
|
|
},
|
|
|
|
parameters: 'scope=all'
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// Hides types and issues custom fields on the new project form when
|
|
|
|
// work_package_tracking module is disabled.
|
|
|
|
function observeProjectModules() {
|
|
|
|
var f = function() {
|
|
|
|
if (jQuery('#project_enabled_module_names_work_package_tracking').attr('checked')) {
|
|
|
|
jQuery('#project_types, #project_issue_custom_fields').show();
|
|
|
|
} else {
|
|
|
|
jQuery('#project_types, #project_issue_custom_fields').hide();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
jQuery(window).load(f);
|
|
|
|
jQuery('#project_enabled_module_names_work_package_tracking').change(f);
|
|
|
|
}
|
|
|
|
|
|
|
|
var WarnLeavingUnsaved = function(message) {
|
|
|
|
this.message = message;
|
|
|
|
var changedForms = false;
|
|
|
|
var isEmbed = window != window.parent;
|
|
|
|
var obj = this;
|
|
|
|
|
|
|
|
this.unload = function() {
|
|
|
|
if(changedForms && !isEmbed) {
|
|
|
|
return message;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
this.getChanged = function() {
|
|
|
|
return changedForms;
|
|
|
|
};
|
|
|
|
|
|
|
|
this.setChanged = function() {
|
|
|
|
changedForms = true;
|
|
|
|
};
|
|
|
|
|
|
|
|
this.setUnchanged = function() {
|
|
|
|
changedForms = false;
|
|
|
|
};
|
|
|
|
|
|
|
|
this.init = function() {
|
|
|
|
jQuery(document).on('change', 'textarea', function() {
|
|
|
|
obj.setChanged();
|
|
|
|
});
|
|
|
|
|
|
|
|
jQuery(document).on('submit', 'form', function() {
|
|
|
|
obj.setUnchanged();
|
|
|
|
});
|
|
|
|
|
|
|
|
jQuery(window).bind('beforeunload', this.unload);
|
|
|
|
};
|
|
|
|
|
|
|
|
this.init();
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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
|
|
|
|
*/
|
|
|
|
document.observe("dom:loaded", function() {
|
|
|
|
Ajax.Responders.register({
|
|
|
|
onCreate: function(request){
|
|
|
|
var csrf_meta_tag = $$('meta[name=csrf-token]')[0];
|
|
|
|
|
|
|
|
if (!request.options.requestHeaders) {
|
|
|
|
request.options.requestHeaders = {};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (csrf_meta_tag) {
|
|
|
|
var header = 'X-CSRF-Token',
|
|
|
|
token = csrf_meta_tag.readAttribute('content');
|
|
|
|
|
|
|
|
request.options.requestHeaders[header] = token;
|
|
|
|
}
|
|
|
|
|
|
|
|
request.options.requestHeaders['X-Authentication-Scheme'] = "Session";
|
|
|
|
|
|
|
|
if ($('ajax-indicator') && Ajax.activeRequestCount > 0) {
|
|
|
|
Element.show('ajax-indicator');
|
|
|
|
}
|
|
|
|
},
|
|
|
|
onComplete: function(request, result){
|
|
|
|
if ($('ajax-indicator') && Ajax.activeRequestCount === 0) {
|
|
|
|
Element.hide('ajax-indicator');
|
|
|
|
}
|
|
|
|
addClickEventToAllErrorMessages();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
function hideOnLoad() {
|
|
|
|
jQuery('.hol').hide();
|
|
|
|
}
|
|
|
|
jQuery(hideOnLoad);
|
|
|
|
|
|
|
|
function addClickEventToAllErrorMessages() {
|
|
|
|
$$('a.afocus').each(function(a) {
|
|
|
|
$(a).observe('click', function(event) {
|
|
|
|
var field;
|
|
|
|
field = $($(a).readAttribute('href').substr(1));
|
|
|
|
if (field === null) {
|
|
|
|
// Cut off '_id' (necessary for select boxes)
|
|
|
|
field = $($(a).readAttribute('href').substr(1).concat('_id'));
|
|
|
|
}
|
|
|
|
if (field) {
|
|
|
|
field.down('input, textarea, select').focus();
|
|
|
|
}
|
|
|
|
Event.stop(event);
|
|
|
|
return false;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function toggleEmailDecoratorFields() {
|
|
|
|
lang = jQuery("#emails_decorators_switch").val();
|
|
|
|
jQuery(".emails_decorators").hide();
|
|
|
|
jQuery("#emails_decorators_" + lang).show();
|
|
|
|
}
|
|
|
|
|
|
|
|
$(document).observe('dom:loaded', function() {
|
|
|
|
// 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();
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 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($) {
|
|
|
|
document.ajaxActive = false;
|
|
|
|
$(document).ajaxSend(function (event, request) {
|
|
|
|
document.ajaxActive = true;
|
|
|
|
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
|
|
|
|
$(document).ajaxStop(function () {
|
|
|
|
document.ajaxActive = false;
|
|
|
|
if ($('#ajax-indicator')) {
|
|
|
|
$('#ajax-indicator').hide();
|
|
|
|
}
|
|
|
|
addClickEventToAllErrorMessages();
|
|
|
|
});
|
|
|
|
|
|
|
|
$.extend($.fn.select2.defaults, {
|
|
|
|
formatNoMatches: function () {
|
|
|
|
return I18n.t("js.select2.no_matches");
|
|
|
|
},
|
|
|
|
formatInputTooShort: function (input, min) {
|
|
|
|
return I18n.t("js.select2.input_too_short", {count: min - input.length});
|
|
|
|
},
|
|
|
|
formatSelectionTooBig: function (limit) {
|
|
|
|
return I18n.t("js.select2.selection_too_big", {limit: limit});
|
|
|
|
},
|
|
|
|
formatLoadMore: function (pageNumber) {
|
|
|
|
return I18n.t("js.select2.load_more");
|
|
|
|
},
|
|
|
|
formatSearching: function () {
|
|
|
|
return I18n.t("js.select2.searching");
|
|
|
|
},
|
|
|
|
containerCssClass: 'form--select2',
|
|
|
|
adaptContainerCssClass: function(clazz) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
$('#project-search-container .select2-select').each(function (ix, select) {
|
|
|
|
var PROJECT_JUMP_BOX_PAGE_SIZE = 50;
|
|
|
|
|
|
|
|
select = $(select);
|
|
|
|
var select2,
|
|
|
|
menu = select.parents('li.drop-down'),
|
|
|
|
that = this;
|
|
|
|
|
|
|
|
select.select2({
|
|
|
|
formatResult : OpenProject.Helpers.Search.formatter,
|
|
|
|
matcher : OpenProject.Helpers.Search.matcher,
|
|
|
|
query : OpenProject.Helpers.Search.projectQueryWithHierarchy(
|
|
|
|
jQuery.proxy(openProject, 'fetchProjects'),
|
|
|
|
PROJECT_JUMP_BOX_PAGE_SIZE),
|
|
|
|
dropdownCssClass : "project-search-results",
|
|
|
|
containerCssClass : "select2-select",
|
|
|
|
}).
|
|
|
|
on('change', function (e) {
|
|
|
|
// this handles expected 'new-tab behaviour'
|
|
|
|
if (e.val) {
|
|
|
|
// jQuery sets metaKey to true when pressing ctrl
|
|
|
|
if (e.metaKey) {
|
|
|
|
window.open(select2.data().project.url);
|
|
|
|
} else {
|
|
|
|
window.location = select2.data().project.url;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}).
|
|
|
|
on('close', function () {
|
|
|
|
if (menu.is('.open')) {
|
|
|
|
menu.trigger("closeMenu");
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
select2 = select.data('select2');
|
|
|
|
|
|
|
|
// Adding an event handler to change select2's default behavior concerning
|
|
|
|
// TAB and ESC
|
|
|
|
select2.search.keydown(function (e) {
|
|
|
|
switch (e.which) {
|
|
|
|
case 9: // TAB
|
|
|
|
closestVisible = select2.container.children(".select2-choice").closest(":visible");
|
|
|
|
if (e.shiftKey) {
|
|
|
|
closestVisible.previousElementInDom(":input:visible, a:visible").focus();
|
|
|
|
} else {
|
|
|
|
closestVisible.nextElementInDom(":input:visible, a:visible").focus();
|
|
|
|
}
|
|
|
|
e.stopImmediatePropagation();
|
|
|
|
e.preventDefault();
|
|
|
|
return false;
|
|
|
|
case 27: // ESC
|
|
|
|
e.stopImmediatePropagation();
|
|
|
|
e.preventDefault();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
// Moving the newly attached handler to the beginning of the handler chain
|
|
|
|
select2.search.data('events').keydown.unshift(select2.search.data('events').keydown.pop());
|
|
|
|
|
|
|
|
menu.bind("closed", function () {
|
|
|
|
// Close select2 result list, when menu is closed
|
|
|
|
select2.close();
|
|
|
|
});
|
|
|
|
|
|
|
|
menu.bind("opened", function () {
|
|
|
|
// Open select2 element, when menu is opened
|
|
|
|
select2.open();
|
|
|
|
|
|
|
|
setTimeout(function () {
|
|
|
|
$("#select2-drop-mask").hide();
|
|
|
|
}, 50);
|
|
|
|
|
|
|
|
// Include input in tab cycle by attaching keydown handlers to previous
|
|
|
|
// and next interactive DOM element.
|
|
|
|
select2.container.previousElementInDom(":input:visible, a:visible").keydown(function (e) {
|
|
|
|
if (!e.shiftKey && e.which === 9) {
|
|
|
|
select2.search.focus();
|
|
|
|
e.preventDefault();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
select2.container.nextElementInDom(":input:visible:not(.select2-input), a:visible:not(.select2-input)").keydown(function (e) {
|
|
|
|
if (e.shiftKey && e.which === 9 && select2.search.is(":visible")) {
|
|
|
|
select2.search.focus();
|
|
|
|
e.preventDefault();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
// file table thumbnails
|
|
|
|
$("table a.has-thumb").hover(function() {
|
|
|
|
$(this).removeAttr("title").toggleClass("active");
|
|
|
|
|
|
|
|
// grab the image dimensions to position it properly
|
|
|
|
var thumbImg = $(this).find("img");
|
|
|
|
var thumbImgLeft = -(thumbImg.outerWidth() );
|
|
|
|
var thumbImgTop = -(thumbImg.height() / 2 );
|
|
|
|
thumbImg.css({top: thumbImgTop, left: thumbImgLeft}).show();
|
|
|
|
|
|
|
|
}, function() {
|
|
|
|
$(this).toggleClass("active").find("img").hide();
|
|
|
|
});
|
|
|
|
|
|
|
|
// show/hide the files table
|
|
|
|
$(".attachments h4").click(function() {
|
|
|
|
$(this).toggleClass("closed").next().slideToggle(animationRate);
|
|
|
|
});
|
|
|
|
|
|
|
|
$(window).resize(function() {
|
|
|
|
// wait 200 milliseconds for no further resize event
|
|
|
|
// then readjust breadcrumb
|
|
|
|
|
|
|
|
if(this.resizeTO) clearTimeout(this.resizeTO);
|
|
|
|
this.resizeTO = setTimeout(function() {
|
|
|
|
$(this).trigger('resizeEnd');
|
|
|
|
}, 200);
|
|
|
|
});
|
|
|
|
|
|
|
|
$.fn.mySlide = function(callback) {
|
|
|
|
this.slideToggle(animationRate,callback);
|
|
|
|
|
|
|
|
return this;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Do not close the login window when using it
|
|
|
|
$('#nav-login-content').click(function(event){
|
|
|
|
event.stopPropagation();
|
|
|
|
});
|
|
|
|
|
|
|
|
jQuery('table.cal div.issue.tooltip').each(function(){
|
|
|
|
var div = $(this);
|
|
|
|
div.find('a').first().focus(function(){
|
|
|
|
div.addClass('hover');
|
|
|
|
});
|
|
|
|
div.find('a').first().blur(function(){
|
|
|
|
div.removeClass('hover');
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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.size() === 0);
|
|
|
|
|
|
|
|
if (default_language_select_active.size() === 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) {
|
|
|
|
$('#setting_default_language select').attr('disabled', active)
|
|
|
|
.closest('form')
|
|
|
|
.find('input:submit')
|
|
|
|
.attr('disabled', active);
|
|
|
|
};
|
|
|
|
|
|
|
|
init_language_selection_handling = function () {
|
|
|
|
$('#setting_available_languages input:not([checked="checked"])').each(function (index, input) {
|
|
|
|
update_default_language_options($(input));
|
|
|
|
});
|
|
|
|
$('#setting_available_languages input').click(function () {
|
|
|
|
update_default_language_options($(this));
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
return {
|
|
|
|
init_language_selection_handling: init_language_selection_handling
|
|
|
|
};
|
|
|
|
}(jQuery));
|
|
|
|
|
|
|
|
var I18nForms = (function ($) {
|
|
|
|
var event_handler,
|
|
|
|
init,
|
|
|
|
id_memo = {},
|
|
|
|
memorize_ids,
|
|
|
|
submit_preparer;
|
|
|
|
|
|
|
|
event_handler = (function() {
|
|
|
|
var active_locale_selectors,
|
|
|
|
add_locale_fields,
|
|
|
|
destroy_locale,
|
|
|
|
init,
|
|
|
|
observe_add_locale_link,
|
|
|
|
observe_destroy_locale_links,
|
|
|
|
select_first_untaken_option,
|
|
|
|
taken_options,
|
|
|
|
update_add_link_status,
|
|
|
|
update_destroy_link_status,
|
|
|
|
update_interaction_elements,
|
|
|
|
update_locale_availability;
|
|
|
|
|
|
|
|
active_locale_selectors = function (localizedField) {
|
|
|
|
return localizedField.find('.locale_selector');
|
|
|
|
};
|
|
|
|
|
|
|
|
add_locale_fields = function (localizedField) {
|
|
|
|
var backup = localizedField.find('.translation').first(),
|
|
|
|
add_link = localizedField.find('.add_locale'),
|
|
|
|
new_items = backup.clone(),
|
|
|
|
new_language_selector = new_items.find('.locale_selector'),
|
|
|
|
other_language_selectors = active_locale_selectors(localizedField);
|
|
|
|
|
|
|
|
new_items.find('input, textarea').val("");
|
|
|
|
new_items.insertBefore(add_link);
|
|
|
|
|
|
|
|
select_first_untaken_option(new_language_selector, other_language_selectors);
|
|
|
|
update_interaction_elements(localizedField);
|
|
|
|
|
|
|
|
observe_destroy_locale_links(new_items.find('.destroy_locale'));
|
|
|
|
};
|
|
|
|
|
|
|
|
destroy_locale = function (element) {
|
|
|
|
var localizedField = element.closest('.form--field');
|
|
|
|
|
|
|
|
element.closest('.translation').remove();
|
|
|
|
|
|
|
|
update_interaction_elements(localizedField);
|
|
|
|
};
|
|
|
|
|
|
|
|
observe_add_locale_link = function (selector) {
|
|
|
|
$(selector).click(function (event) {
|
|
|
|
event.preventDefault();
|
|
|
|
add_locale_fields($(this).closest('.form--field'));
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
observe_destroy_locale_links = function (selector) {
|
|
|
|
$(selector).click(function (event) {
|
|
|
|
event.preventDefault();
|
|
|
|
destroy_locale($(this));
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
select_first_untaken_option = function (select, others) {
|
|
|
|
var taken,
|
|
|
|
available;
|
|
|
|
|
|
|
|
taken = taken_options(others);
|
|
|
|
|
|
|
|
available = select.find('option').map(function (index, element) {
|
|
|
|
element = $(element);
|
|
|
|
|
|
|
|
if (taken.indexOf(element.val()) < 0) {
|
|
|
|
return element.val();
|
|
|
|
}
|
|
|
|
}).get();
|
|
|
|
|
|
|
|
select.val(available.pop());
|
|
|
|
};
|
|
|
|
|
|
|
|
update_add_link_status = function (localizedField) {
|
|
|
|
var indicator_selector = active_locale_selectors(localizedField),
|
|
|
|
taken = taken_options(indicator_selector),
|
|
|
|
all_options,
|
|
|
|
available,
|
|
|
|
add_link = localizedField.find('.add_locale');
|
|
|
|
|
|
|
|
available = indicator_selector.first().find('option').map(function (index, element) {
|
|
|
|
element = $(element);
|
|
|
|
|
|
|
|
if (taken.indexOf(element.val()) < 0) {
|
|
|
|
return element.val();
|
|
|
|
}
|
|
|
|
}).get();
|
|
|
|
|
|
|
|
add_link.toggle(available.size() > 0);
|
|
|
|
};
|
|
|
|
|
|
|
|
update_destroy_link_status = function (localizedField) {
|
|
|
|
var active_selectors = active_locale_selectors(localizedField);
|
|
|
|
|
|
|
|
localizedField.find('.destroy_locale').toggle(active_selectors.size() > 1);
|
|
|
|
};
|
|
|
|
|
|
|
|
update_interaction_elements = function (localizedField) {
|
|
|
|
update_locale_availability(localizedField);
|
|
|
|
update_add_link_status(localizedField);
|
|
|
|
update_destroy_link_status(localizedField);
|
|
|
|
};
|
|
|
|
|
|
|
|
update_locale_availability = function (localizedField) {
|
|
|
|
var active_selectors = active_locale_selectors(localizedField),
|
|
|
|
active_locales = taken_options(active_selectors);
|
|
|
|
|
|
|
|
active_selectors.each(function (index, element) {
|
|
|
|
var selector = $(element),
|
|
|
|
selected_value = selector.val();
|
|
|
|
|
|
|
|
selector.find('option').each(function (index, element) {
|
|
|
|
var option = $(element);
|
|
|
|
|
|
|
|
option.attr('disabled', (active_locales.indexOf(option.val()) >= 0 && option.val() !== selected_value));
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
taken_options = function (select_collection) {
|
|
|
|
return select_collection.map(function (index, element) {
|
|
|
|
element = $(element);
|
|
|
|
|
|
|
|
return element.val();
|
|
|
|
}).get();
|
|
|
|
};
|
|
|
|
|
|
|
|
init = function () {
|
|
|
|
observe_add_locale_link($('.add_locale'));
|
|
|
|
observe_destroy_locale_links($('.destroy_locale'));
|
|
|
|
|
|
|
|
$('form .translation').closest('.form--field').each(function (i, element) {
|
|
|
|
element = $(element);
|
|
|
|
update_interaction_elements(element);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
return {
|
|
|
|
init : init
|
|
|
|
};
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
|
|
|
memorize_ids = function (form) {
|
|
|
|
var translations = $('.translation');
|
|
|
|
|
|
|
|
translations.each(function (i, element) {
|
|
|
|
var id,
|
|
|
|
locale;
|
|
|
|
element = $(element);
|
|
|
|
|
|
|
|
id = element.find(".translation_id").val();
|
|
|
|
locale = element.find(".locale_selector").val();
|
|
|
|
|
|
|
|
if (locale !== "" && id !== "" && id_memo[locale] === undefined) {
|
|
|
|
id_memo[locale] = id;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
submit_preparer = (function() {
|
|
|
|
var add_destroy_elements,
|
|
|
|
add_empty_values_for_missing_attributes,
|
|
|
|
collect_elements_by_locale,
|
|
|
|
collect_translation_classes,
|
|
|
|
element_numerator,
|
|
|
|
prepare,
|
|
|
|
unify_translations_across_attribute;
|
|
|
|
|
|
|
|
add_destroy_elements = function(locale) {
|
|
|
|
var translation,
|
|
|
|
destroy_element,
|
|
|
|
id_element,
|
|
|
|
destroy_id_elements;
|
|
|
|
|
|
|
|
translation = $('.translation').first();
|
|
|
|
destroy_id_elements = translation.find('.destroy_flag, .translation_id').clone();
|
|
|
|
destroy_element = translation.filter('.destroy_flag');
|
|
|
|
id_element = translation.filter('.translation_id');
|
|
|
|
|
|
|
|
translation.after(destroy_id_elements);
|
|
|
|
|
|
|
|
destroy_id_elements.filter('.destroy_flag').attr('disabled', false);
|
|
|
|
destroy_id_elements.filter('.translation_id').attr('value', id_memo[locale]);
|
|
|
|
|
|
|
|
element_numerator.set_next_number_in_name(destroy_id_elements);
|
|
|
|
};
|
|
|
|
|
|
|
|
add_empty_values_for_missing_attributes = function (locale, translation_classes) {
|
|
|
|
var new_translations = $('');
|
|
|
|
|
|
|
|
$.each(translation_classes, function(i, translated_attribute) {
|
|
|
|
var translations = $('.' + translated_attribute),
|
|
|
|
locale_selectors = translations.find('.locale_selector'),
|
|
|
|
included,
|
|
|
|
new_translation;
|
|
|
|
|
|
|
|
included = locale_selectors.map(function(i, element) {
|
|
|
|
return $(element).val();
|
|
|
|
}).get().indexOf(locale) >= 0;
|
|
|
|
|
|
|
|
if (!included) {
|
|
|
|
new_translation = translations.first().clone();
|
|
|
|
new_translation.hide();
|
|
|
|
new_translation.find('.destroy_flag').val('1')
|
|
|
|
.attr('disabled', false);
|
|
|
|
new_translation.find('input, textarea').val('');
|
|
|
|
new_translation.find('.locale_selector').val(locale);
|
|
|
|
new_translation.insertAfter(translations.first());
|
|
|
|
|
|
|
|
new_translations = new_translations.add(new_translation);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return new_translations;
|
|
|
|
};
|
|
|
|
|
|
|
|
collect_elements_by_locale = function(translations) {
|
|
|
|
var locales = {};
|
|
|
|
|
|
|
|
translations.each(function (i, element) {
|
|
|
|
var locale;
|
|
|
|
|
|
|
|
element = $(element);
|
|
|
|
locale = element.find('.locale_selector').val();
|
|
|
|
|
|
|
|
if (locales[locale] === undefined) {
|
|
|
|
locales[locale] = element;
|
|
|
|
} else {
|
|
|
|
locales[locale] = locales[locale].add(element);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return locales;
|
|
|
|
};
|
|
|
|
|
|
|
|
collect_translation_classes = function (translations) {
|
|
|
|
var translation_classes = [];
|
|
|
|
|
|
|
|
translations.each(function (i, element) {
|
|
|
|
var translation_class;
|
|
|
|
|
|
|
|
element = $(element);
|
|
|
|
|
|
|
|
translation_class = /\w+_translation/.exec(element.attr('class'))[0];
|
|
|
|
|
|
|
|
if (translation_classes.indexOf(translation_class) < 0) {
|
|
|
|
translation_classes.push(translation_class);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return translation_classes;
|
|
|
|
};
|
|
|
|
|
|
|
|
element_numerator = (function() {
|
|
|
|
var init,
|
|
|
|
num,
|
|
|
|
number_matcher = /([\[_])(\d+)([\]\[_]{1,2}\w+\]?$)/,
|
|
|
|
replace_number_in_name,
|
|
|
|
set_next_number_in_name;
|
|
|
|
|
|
|
|
init = function() {
|
|
|
|
num = 0;
|
|
|
|
};
|
|
|
|
|
|
|
|
set_next_number_in_name = function(elements) {
|
|
|
|
replace_number_in_name(elements, num);
|
|
|
|
num += 1;
|
|
|
|
};
|
|
|
|
|
|
|
|
replace_number_in_name = function (element, rep_number) {
|
|
|
|
element.each(function (index, e) {
|
|
|
|
e = $(e);
|
|
|
|
e.attr('name', e.attr('name').replace(number_matcher, "$1" + rep_number + "$3"));
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
return {
|
|
|
|
init : init,
|
|
|
|
set_next_number_in_name : set_next_number_in_name
|
|
|
|
};
|
|
|
|
})();
|
|
|
|
|
|
|
|
prepare = function (form) {
|
|
|
|
var locales,
|
|
|
|
translation_classes = [],
|
|
|
|
translations = form.find('.translation');
|
|
|
|
|
|
|
|
element_numerator.init();
|
|
|
|
|
|
|
|
translation_classes = collect_translation_classes(translations);
|
|
|
|
locales = collect_elements_by_locale(translations);
|
|
|
|
|
|
|
|
$.each(locales, function(locale, elements) {
|
|
|
|
empty_value_elements = add_empty_values_for_missing_attributes(locale, translation_classes);
|
|
|
|
unify_translations_across_attribute(locale, elements.add(empty_value_elements), translation_classes);
|
|
|
|
});
|
|
|
|
|
|
|
|
$.each(id_memo, function(locale, id) {
|
|
|
|
if (!locales.hasOwnProperty(locale)) {
|
|
|
|
add_destroy_elements(locale);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
unify_translations_across_attribute = function (locale, elements, translation_classes) {
|
|
|
|
var current_id,
|
|
|
|
i,
|
|
|
|
to_destroy,
|
|
|
|
to_keep;
|
|
|
|
|
|
|
|
element_numerator.set_next_number_in_name(elements.find('select, textarea, input'));
|
|
|
|
|
|
|
|
current_id = id_memo[locale] !== undefined ? id_memo[locale] : '';
|
|
|
|
elements.find('.translation_id').val(current_id);
|
|
|
|
|
|
|
|
to_destroy = elements.filter(':hidden');
|
|
|
|
to_keep = elements.filter(':visible');
|
|
|
|
|
|
|
|
if (to_keep.size() > 0) {
|
|
|
|
to_destroy.find('.destroy_flag').attr('disabled', true);
|
|
|
|
for (i = 0; i < translation_classes.size(); i += 1) {
|
|
|
|
if (to_keep.filter("." + translation_classes[i]).size() === 0) {
|
|
|
|
to_destroy.filter("." + translation_classes[i]).find('input[type=text], textarea').val('');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
return {
|
|
|
|
prepare : prepare
|
|
|
|
};
|
|
|
|
})();
|
|
|
|
|
|
|
|
init = function () {
|
|
|
|
var translatedField = $('form .translation').closest('.form--field');
|
|
|
|
|
|
|
|
if (translatedField.size() > 0) {
|
|
|
|
memorize_ids();
|
|
|
|
event_handler.init();
|
|
|
|
|
|
|
|
translatedField.closest('form').submit(function () {
|
|
|
|
submit_preparer.prepare($(this));
|
|
|
|
// allow default behaviour
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
return {
|
|
|
|
init : init
|
|
|
|
};
|
|
|
|
}(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();
|
|
|
|
};
|
|
|
|
|
|
|
|
var setupServerResponse = function() {
|
|
|
|
I18nForms.init();
|
|
|
|
initMainMenuExpandStatus();
|
|
|
|
focusFirstErroneousField();
|
|
|
|
activateFlashNotice();
|
|
|
|
activateFlashError();
|
|
|
|
|
|
|
|
jQuery(document).ajaxComplete(activateFlashNotice);
|
|
|
|
jQuery(document).ajaxComplete(activateFlashError);
|
|
|
|
};
|
|
|
|
|
|
|
|
jQuery(document).ready(setupServerResponse);
|
|
|
|
|
|
|
|
var SubmitConfirm = (function($) {
|
|
|
|
var init;
|
|
|
|
|
|
|
|
init = function(element, question) {
|
|
|
|
element.submit(function() {
|
|
|
|
if(!confirm(question)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
return {
|
|
|
|
init: init
|
|
|
|
};
|
|
|
|
})(jQuery);
|