You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@allura.apache.org by he...@apache.org on 2015/07/02 17:09:16 UTC

[01/23] allura git commit: [#7884] ticket:799 Add id to features section, so that we can refer it in url easily

Repository: allura
Updated Branches:
  refs/heads/hs/7894 342b8c063 -> 664bd19f4 (forced update)


[#7884] ticket:799 Add id to features section, so that we can refer it in url easily


Project: http://git-wip-us.apache.org/repos/asf/allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/allura/commit/eabccabc
Tree: http://git-wip-us.apache.org/repos/asf/allura/tree/eabccabc
Diff: http://git-wip-us.apache.org/repos/asf/allura/diff/eabccabc

Branch: refs/heads/hs/7894
Commit: eabccabc4b0e51cb351e2de7dac822ed8ecd36ba
Parents: 3b97b25
Author: Igor Bondarenko <je...@gmail.com>
Authored: Fri Jun 12 13:49:59 2015 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Mon Jun 29 18:04:51 2015 +0000

----------------------------------------------------------------------
 .../allura/ext/admin/templates/admin_widgets/metadata_admin.html   | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/allura/blob/eabccabc/Allura/allura/ext/admin/templates/admin_widgets/metadata_admin.html
----------------------------------------------------------------------
diff --git a/Allura/allura/ext/admin/templates/admin_widgets/metadata_admin.html b/Allura/allura/ext/admin/templates/admin_widgets/metadata_admin.html
index a9f9758..bebef0c 100644
--- a/Allura/allura/ext/admin/templates/admin_widgets/metadata_admin.html
+++ b/Allura/allura/ext/admin/templates/admin_widgets/metadata_admin.html
@@ -49,7 +49,7 @@
 
     <div style="clear:both">&nbsp;</div>
 
-    <fieldset class="preferences">
+    <fieldset class="preferences" id="features">
       <legend>{{ widget.display_label(widget.fields.features) }}</legend>
       {{ widget.display_field(widget.fields.features) }}
     </fieldset>


[07/23] allura git commit: [#7884] ticket:805 Improve SortableRepeatedField widget and features UI

Posted by he...@apache.org.
[#7884] ticket:805 Improve SortableRepeatedField widget and features UI

- change styles to fit with the rest of the page
- cursor: move for the "move" icon
- hide "add" button and help messages
- add new field, when user focuses last one


Project: http://git-wip-us.apache.org/repos/asf/allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/allura/commit/651047d9
Tree: http://git-wip-us.apache.org/repos/asf/allura/tree/651047d9
Diff: http://git-wip-us.apache.org/repos/asf/allura/diff/651047d9

Branch: refs/heads/hs/7894
Commit: 651047d9ea42e195d4d6c9c0c2f052846628eb48
Parents: eabccab
Author: Igor Bondarenko <je...@gmail.com>
Authored: Wed Jun 17 15:40:48 2015 +0300
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Mon Jun 29 18:04:52 2015 +0000

----------------------------------------------------------------------
 .../ext/admin/templates/admin_widgets/features_field.html |  2 +-
 .../ext/admin/templates/admin_widgets/metadata_admin.html |  7 ++++---
 Allura/allura/ext/admin/widgets.py                        | 10 +++-------
 Allura/allura/lib/widgets/form_fields.py                  |  5 +++++
 .../lib/widgets/resources/js/sortable_repeated_field.js   | 10 ++++++++++
 .../allura/templates/widgets/sortable_repeated_field.html | 10 +++++++---
 6 files changed, 30 insertions(+), 14 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/allura/blob/651047d9/Allura/allura/ext/admin/templates/admin_widgets/features_field.html
----------------------------------------------------------------------
diff --git a/Allura/allura/ext/admin/templates/admin_widgets/features_field.html b/Allura/allura/ext/admin/templates/admin_widgets/features_field.html
index 5d70528..413158a 100644
--- a/Allura/allura/ext/admin/templates/admin_widgets/features_field.html
+++ b/Allura/allura/ext/admin/templates/admin_widgets/features_field.html
@@ -21,7 +21,7 @@
   {% set f = widget.fields[0] %}
   {% set ctx = widget.context_for(f) %}
   <div data-name="{{f.name}}">
-    <span class="ui-icon ui-icon-arrowthick-2-n-s" style="display:inline-block;"></span>
+    <span class="ui-icon ui-icon-arrowthick-2-n-s" style="display:inline-block;cursor:move;"></span>
     {{f.display(**ctx)}}
   </div>
 </div>

http://git-wip-us.apache.org/repos/asf/allura/blob/651047d9/Allura/allura/ext/admin/templates/admin_widgets/metadata_admin.html
----------------------------------------------------------------------
diff --git a/Allura/allura/ext/admin/templates/admin_widgets/metadata_admin.html b/Allura/allura/ext/admin/templates/admin_widgets/metadata_admin.html
index bebef0c..b313dfc 100644
--- a/Allura/allura/ext/admin/templates/admin_widgets/metadata_admin.html
+++ b/Allura/allura/ext/admin/templates/admin_widgets/metadata_admin.html
@@ -49,10 +49,11 @@
 
     <div style="clear:both">&nbsp;</div>
 
-    <fieldset class="preferences" id="features">
-      <legend>{{ widget.display_label(widget.fields.features) }}</legend>
+    <div id="features">
+      {{ widget.display_label(widget.fields.features) }}
       {{ widget.display_field(widget.fields.features) }}
-    </fieldset>
+    </div>
+    <br>
 
     {% if tg.config.get('support_tool_choices') %}
     Preferred Support Page (for users of your project):<br>

http://git-wip-us.apache.org/repos/asf/allura/blob/651047d9/Allura/allura/ext/admin/widgets.py
----------------------------------------------------------------------
diff --git a/Allura/allura/ext/admin/widgets.py b/Allura/allura/ext/admin/widgets.py
index 9e6e39d..0d5cae9 100644
--- a/Allura/allura/ext/admin/widgets.py
+++ b/Allura/allura/ext/admin/widgets.py
@@ -193,14 +193,10 @@ class MetadataAdmin(ff.AdminForm):
         # is not just ew.TextField
         features = ffw.SortableRepeatedField(
             label='Features',
-            empty_msg='No features yet',
-            nonempty_msg='Drag and drop features to reorder. '
-                         'Leave empty to delete a feature.',
+            show_msg=False,
+            show_button=False,
             append_to='bottom',
-            button=ew.InputField(
-                css_class='add',
-                field_type='button',
-                value='Add feature'),
+            extra_field_on_focus_name='feature',
             field=FeaturesField())
         icon = ew.FileField(label='Icon')
         external_homepage = ew.InputField(field_type="text", label='Homepage',

http://git-wip-us.apache.org/repos/asf/allura/blob/651047d9/Allura/allura/lib/widgets/form_fields.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/widgets/form_fields.py b/Allura/allura/lib/widgets/form_fields.py
index be3fd1a..77665f9 100644
--- a/Allura/allura/lib/widgets/form_fields.py
+++ b/Allura/allura/lib/widgets/form_fields.py
@@ -413,6 +413,7 @@ class SortableRepeatedMixin(JQueryMixin):
         'stub_cls',
         'msg_cls',
         'append_to',
+        'extra_field_on_focus_name',
     ]
     defaults = dict(
         container_cls='sortable-repeated-field',
@@ -423,7 +424,11 @@ class SortableRepeatedMixin(JQueryMixin):
         append_to='top',
         empty_msg='No fields have been defined',
         nonempty_msg='Drag and drop the fields to reorder',
+        show_msg=True,
+        show_button=True,
+        extra_field_on_focus_name=None,
         repetitions=0)
+
     button = ew.InputField(
         css_class='add', field_type='button', value='New Field')
 

http://git-wip-us.apache.org/repos/asf/allura/blob/651047d9/Allura/allura/lib/widgets/resources/js/sortable_repeated_field.js
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/widgets/resources/js/sortable_repeated_field.js b/Allura/allura/lib/widgets/resources/js/sortable_repeated_field.js
index ca78493..5371299 100644
--- a/Allura/allura/lib/widgets/resources/js/sortable_repeated_field.js
+++ b/Allura/allura/lib/widgets/resources/js/sortable_repeated_field.js
@@ -26,6 +26,7 @@
         stub_cls:'sortable-field-stub',
         msg_cls:'sortable-field-message',
         append_to:'top',  // append new field to top by default. Also supports 'bottom'
+        extra_field_on_focus_name:null,
     };
 
     $.fn.SortableRepeatedField = function(options) {
@@ -85,6 +86,11 @@
                 self.data.$delete_buttons.one('click', _deleteField);
                 self.data.$flist.sortable({stop:_renumberFields});
                 self.data.$stub.hide();
+                if (self.opts.extra_field_on_focus_name) {
+                  self.data.$flist.find('.' + self.opts.field_cls)
+                      .find('input[name$=' + self.opts.extra_field_on_focus_name + ']')
+                      .off('focus').last().on('focus', _addExtraField);
+                }
                 _manageMessages();
             },
             fld_name: function() {
@@ -159,5 +165,9 @@
                     });
             });
         }
+        function _addExtraField() {
+          $(this).off('focus');
+          _addField();
+        }
     };
 }(jQuery));

http://git-wip-us.apache.org/repos/asf/allura/blob/651047d9/Allura/allura/templates/widgets/sortable_repeated_field.html
----------------------------------------------------------------------
diff --git a/Allura/allura/templates/widgets/sortable_repeated_field.html b/Allura/allura/templates/widgets/sortable_repeated_field.html
index dc41694..538d14f 100644
--- a/Allura/allura/templates/widgets/sortable_repeated_field.html
+++ b/Allura/allura/templates/widgets/sortable_repeated_field.html
@@ -20,18 +20,22 @@
      xmlns:py="http://genshi.edgewall.org/"
      class="$container_cls"
      py:attrs="{'data-name':name}">
-  <div><p
+   <div><py:if test="show_msg"><p
           class="$msg_cls"
           data-empty-message="$empty_msg"
           data-nonempty-message="$nonempty_msg"
-          /></div>
-  ${widget.button.display()}
+          /></py:if></div>
+  <py:if test="show_button">${widget.button.display()}</py:if>
   <br style="clear:both"/>
   <div class="$flist_cls">
     <py:for each="i in xrange(repetitions)"
             py:with="ctx=widget.context_for(i)">
       ${widget.field.display(css_class=field_cls, **ctx)}
     </py:for>
+    <py:if test="extra_field_on_focus_name"
+           py:with="ctx=widget.context_for(repetitions)">
+      ${widget.field.display(css_class=field_cls, **ctx)}
+    </py:if>
     ${widget.field.display(name=name+'#', css_class=stub_cls)}
   </div>
 </div>


[03/23] allura git commit: [#7884] ticket:799 Add icon for drag & drop

Posted by he...@apache.org.
[#7884] ticket:799 Add icon for drag & drop


Project: http://git-wip-us.apache.org/repos/asf/allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/allura/commit/d91dd1d3
Tree: http://git-wip-us.apache.org/repos/asf/allura/tree/d91dd1d3
Diff: http://git-wip-us.apache.org/repos/asf/allura/diff/d91dd1d3

Branch: refs/heads/hs/7894
Commit: d91dd1d361b4fc004a593beb2ff2c8168048e392
Parents: 3bbf0f6
Author: Igor Bondarenko <je...@gmail.com>
Authored: Fri Jun 12 10:12:58 2015 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Mon Jun 29 18:04:51 2015 +0000

----------------------------------------------------------------------
 .../templates/admin_widgets/features_field.html    | 17 ++++++-----------
 Allura/allura/ext/admin/widgets.py                 |  5 ++++-
 2 files changed, 10 insertions(+), 12 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/allura/blob/d91dd1d3/Allura/allura/ext/admin/templates/admin_widgets/features_field.html
----------------------------------------------------------------------
diff --git a/Allura/allura/ext/admin/templates/admin_widgets/features_field.html b/Allura/allura/ext/admin/templates/admin_widgets/features_field.html
index c5ef066..5d70528 100644
--- a/Allura/allura/ext/admin/templates/admin_widgets/features_field.html
+++ b/Allura/allura/ext/admin/templates/admin_widgets/features_field.html
@@ -18,15 +18,10 @@
 -#}
 
 <div class="{{css_class}}">
-  {% for f in widget.fields %}
-    {% set ctx=widget.context_for(f) %}
-    <div data-name="{{f.name}}">
-      {% if f.show_label %}
-        <div class="grid-4"><label>{{f.label}}:</label></div>
-        <div class="grid-14">{{f.display(**ctx)}}</div>
-      {% else %}
-        <div>{{f.display(**ctx)}}</div>
-      {% endif %}
-    </div>
-  {% endfor %}
+  {% set f = widget.fields[0] %}
+  {% set ctx = widget.context_for(f) %}
+  <div data-name="{{f.name}}">
+    <span class="ui-icon ui-icon-arrowthick-2-n-s" style="display:inline-block;"></span>
+    {{f.display(**ctx)}}
+  </div>
 </div>

http://git-wip-us.apache.org/repos/asf/allura/blob/d91dd1d3/Allura/allura/ext/admin/widgets.py
----------------------------------------------------------------------
diff --git a/Allura/allura/ext/admin/widgets.py b/Allura/allura/ext/admin/widgets.py
index 4ea1fcd..572db5d 100644
--- a/Allura/allura/ext/admin/widgets.py
+++ b/Allura/allura/ext/admin/widgets.py
@@ -157,7 +157,10 @@ class ScreenshotAdmin(ff.ForgeForm):
 
 class FeaturesField(ew.CompoundField):
     template = 'jinja:allura.ext.admin:templates/admin_widgets/features_field.html'
-    fields = [ew.TextField(name='feature', show_label=False)]
+    fields = [ew.TextField(name='feature', attrs={'style': 'width:89%'})]
+
+    def resources(self):
+        yield ew.CSSLink('allura/css/smoothness/jquery-ui-1.8.4.custom.css')
 
 
 class MetadataAdmin(ff.AdminForm):


[22/23] allura git commit: [#7894] Move skip_mod_date into Allura

Posted by he...@apache.org.
[#7894] Move skip_mod_date into Allura


Project: http://git-wip-us.apache.org/repos/asf/allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/allura/commit/6e8e6f33
Tree: http://git-wip-us.apache.org/repos/asf/allura/tree/6e8e6f33
Diff: http://git-wip-us.apache.org/repos/asf/allura/diff/6e8e6f33

Branch: refs/heads/hs/7894
Commit: 6e8e6f33f5501290cbea052e99044a15a54beeeb
Parents: 23bcdc9
Author: Heith Seewald <hs...@slashdotmedia.com>
Authored: Mon Jun 29 14:36:55 2015 -0400
Committer: Heith Seewald <hs...@slashdotmedia.com>
Committed: Thu Jul 2 11:08:58 2015 -0400

----------------------------------------------------------------------
 Allura/allura/lib/utils.py        | 12 ++++++++++++
 Allura/allura/tests/test_utils.py |  8 ++++++++
 2 files changed, 20 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/allura/blob/6e8e6f33/Allura/allura/lib/utils.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/utils.py b/Allura/allura/lib/utils.py
index 143868e..f812e2e 100644
--- a/Allura/allura/lib/utils.py
+++ b/Allura/allura/lib/utils.py
@@ -14,6 +14,7 @@
 #       KIND, either express or implied.  See the License for the
 #       specific language governing permissions and limitations
 #       under the License.
+from contextlib import contextmanager
 
 import time
 import string
@@ -21,6 +22,7 @@ import hashlib
 import binascii
 import logging.handlers
 import codecs
+from ming.odm import session
 import os.path
 import datetime
 import random
@@ -614,3 +616,13 @@ def clean_phone_number(number):
 def phone_number_hash(number):
     number = clean_phone_number(number)
     return hashlib.sha1(number).hexdigest()
+
+
+@contextmanager
+def skip_mod_date(model_cls):
+    skip_mod_date = getattr(session(model_cls)._get(), 'skip_mod_date', False)
+    session(model_cls)._get().skip_mod_date = True
+    try:
+        yield
+    finally:
+        session(model_cls)._get().skip_mod_date = skip_mod_date
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/allura/blob/6e8e6f33/Allura/allura/tests/test_utils.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/test_utils.py b/Allura/allura/tests/test_utils.py
index 5043e3d..39d61b3 100644
--- a/Allura/allura/tests/test_utils.py
+++ b/Allura/allura/tests/test_utils.py
@@ -21,6 +21,8 @@ import json
 import time
 import unittest
 import datetime as dt
+from ming.odm import session
+import model as M
 from os import path
 
 from webob import Request
@@ -323,3 +325,9 @@ def test_phone_number_hash():
     hash = utils.phone_number_hash
     assert_equal(hash('1234567890'), hash('+123 456:7890'))
     assert_not_equal(hash('1234567890'), hash('1234567891'))
+
+
+def test_skip_mod_date():
+    with utils.skip_mod_date(M.Artifact):
+        assert getattr(session(M.Artifact)._get(), 'skip_mod_date', None) == True
+    assert getattr(session(M.Artifact)._get(), 'skip_mod_date', None) == False
\ No newline at end of file


[08/23] allura git commit: [#7885] Added tooltip config to master jinja template

Posted by he...@apache.org.
[#7885] Added tooltip config to master jinja template


Project: http://git-wip-us.apache.org/repos/asf/allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/allura/commit/85eec26d
Tree: http://git-wip-us.apache.org/repos/asf/allura/tree/85eec26d
Diff: http://git-wip-us.apache.org/repos/asf/allura/diff/85eec26d

Branch: refs/heads/hs/7894
Commit: 85eec26d63d6cb1a026f6d666d5ced0d77b5129b
Parents: 1bac728
Author: Heith Seewald <hs...@slashdotmedia.com>
Authored: Mon Jun 8 15:43:53 2015 -0400
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Mon Jun 29 18:25:47 2015 +0000

----------------------------------------------------------------------
 .../allura/templates/jinja_master/master.html   | 227 ++++++++++---------
 1 file changed, 125 insertions(+), 102 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/allura/blob/85eec26d/Allura/allura/templates/jinja_master/master.html
----------------------------------------------------------------------
diff --git a/Allura/allura/templates/jinja_master/master.html b/Allura/allura/templates/jinja_master/master.html
index cc6b548..062d7de 100644
--- a/Allura/allura/templates/jinja_master/master.html
+++ b/Allura/allura/templates/jinja_master/master.html
@@ -20,15 +20,17 @@
 <!-- Server: {{g.server_name}} -->
 {% import 'allura:templates/jinja_master/lib.html' as lib with context %}
 {% if g.theme.jinja_macros %}
-  {% import g.theme.jinja_macros as theme_macros with context %}
+    {% import g.theme.jinja_macros as theme_macros with context %}
 {% endif %}
 {% do g.register_forge_js('js/jquery-base.js', location='head_js') %}
 {% do g.register_forge_js('js/jquery.notify.js') %}
+{% do g.register_forge_js('js/jquery.tooltipster.js') %}
 {% do g.register_forge_js('js/modernizr.js') %}
 {% do g.register_forge_js('js/sylvester.js') %}
 {% do g.register_forge_js('js/pb.transformie.min.js') %}
 {% do g.register_forge_js('js/allura-base.js') %}
 {% do g.register_forge_css('css/forge/hilite.css') %}
+{% do g.register_forge_css('css/forge/tooltipster.css') %}
 {% do g.register_css('/nf/tool_icon_css?' + g.build_key, compress=False) %}
 {% do g.theme.require() %}
 {% do g.resource_manager.register_widgets(c) %}
@@ -37,130 +39,151 @@
 <!--[if IE 7 ]>    <html lang="en" class="no-js ie7"> <![endif]-->
 <!--[if IE 8 ]>    <html lang="en" class="no-js ie8"> <![endif]-->
 <!--[if IE 9 ]>    <html lang="en" class="no-js ie9"> <![endif]-->
-<!--[if (gt IE 9)|!(IE)]>--> <html lang="en" class="no-js"> <!--<![endif]-->
-  <head>
+<!--[if (gt IE 9)|!(IE)]>-->
+<html lang="en" class="no-js"> <!--<![endif]-->
+<head>
     <meta content="text/html; charset=UTF-8" http-equiv="content-type"/>
     <title>{% block title %}Your title goes here{% endblock %}</title>
-    {{theme_macros.extra_header(g.theme_href(''))}}
+    {{ theme_macros.extra_header(g.theme_href('')) }}
     <script type="text/javascript">
-            /*jslint onevar: false, nomen: false, evil: true, css: true, plusplus: false, white: false, forin: true, on: true, immed: false */
-            /*global confirm, alert, unescape, window, jQuery, $, net, COMSCORE */
+        /*jslint onevar: false, nomen: false, evil: true, css: true, plusplus: false, white: false, forin: true, on: true, immed: false */
+        /*global confirm, alert, unescape, window, jQuery, $, net, COMSCORE */
     </script>
     {% for blob in g.resource_manager.emit('head_css') %}
-      {{ blob }}
+        {{ blob }}
     {% endfor %}
     {% for blob in g.resource_manager.emit('head_js') %}
-      {{ blob }}
+        {{ blob }}
     {% endfor %}
 
     {% if c.project and c.project.neighborhood.css %}
-      <style type="text/css">
-        {{c.project.neighborhood.get_custom_css()|safe}}
-      </style>
+        <style type="text/css">
+            {{c.project.neighborhood.get_custom_css()|safe}}
+        </style>
     {% elif neighborhood and neighborhood.css %}
-      <style type="text/css">
-        {{neighborhood.get_custom_css()}}
-      </style>
+        <style type="text/css">
+            {{neighborhood.get_custom_css()}}
+        </style>
     {% endif %}
     {% block extra_css %}{% endblock %}
-      <style>.{{ g.antispam.honey_class }} { display:none }</style>
+    <style>.{{ g.antispam.honey_class }} {
+        display: none
+    }</style>
 
     {% block head %}
     {% endblock %}
-    {% if g.production_mode %}{{g.analytics.display()}}{% endif %}
-  </head>
+    {% if g.production_mode %}{{ g.analytics.display() }}{% endif %}
+</head>
 
-  <body{% block body_attrs %}{% endblock %} id="forge">
-    <h2 class="hidden">
-        <span style="color:red">Error:</span> CSS did not load.<br>
-        This may happen on the first request due to CSS mimetype issues.
-        Try clearing your browser cache and refreshing.
-        <hr>
-    </h2>
-    {% block body_top_js %}
+<body{% block body_attrs %}{% endblock %} id="forge">
+<h2 class="hidden">
+    <span style="color:red">Error:</span> CSS did not load.<br>
+    This may happen on the first request due to CSS mimetype issues.
+    Try clearing your browser cache and refreshing.
+    <hr>
+</h2>
+{% block body_top_js %}
     {% for blob in g.resource_manager.emit('body_top_js') %}
-      {{ blob }}
+        {{ blob }}
     {% endfor %}
-    {% endblock %}
-    {{theme_macros.header(g.login_url, '/auth/logout')}}
-    {{theme_macros.site_notification()}}
-    {% set flash = tg.flash_obj.render('flash', use_js=False) %}
-    <section id="page-body" class="{{g.document_class(neighborhood)}}">
-	  <div id="nav_menu_holder">
-            {% block nav_menu %}
+{% endblock %}
+{{ theme_macros.header(g.login_url, '/auth/logout') }}
+{{ theme_macros.site_notification() }}
+{% set flash = tg.flash_obj.render('flash', use_js=False) %}
+<section id="page-body" class="{{ g.document_class(neighborhood) }}">
+    <div id="nav_menu_holder">
+        {% block nav_menu %}
             {% include g.theme.nav_menu %}
-            {% endblock %}
-      </div>
-      <div id="top_nav" class="">
+        {% endblock %}
+    </div>
+    <div id="top_nav" class="">
         {% block top_nav %}
-        {% include g.theme.top_nav %}
+            {% include g.theme.top_nav %}
+        {% endblock %}
+    </div>
+    <div id="content_base">
+        {% block content_base %}
+            {% if not hide_left_bar %}
+                {% block sidebar_menu %}
+                    {% include g.theme.sidebar_menu %}
+                {% endblock %}
+                {% set outer_width = 20 %}
+            {% else %}
+                {% set outer_width = 24 %}
+            {% endif %}
+            {% if show_right_bar %}
+                {% set inner_width = outer_width - 8 %}
+            {% else %}
+                {% set inner_width = outer_width %}
+            {% endif %}
+            <div class="grid-{{ outer_width }} pad">
+                <h2 class="dark{% block header_classes %} title{% endblock %}">{% block header %}{% endblock %}
+                    <!-- actions -->
+                    <small>
+                        {% block actions %}{% endblock %}
+                    </small>
+                    <!-- /actions -->
+                </h2>
+                {% block edit_box %}{% endblock %}
+                <div{% if show_right_bar %}
+                    class="{% block inner_grid %}grid-{{ inner_width }}"{% endblock %}{% endif %}>
+                    {% block before_content %}{% endblock %}
+                    {% block content %}{% endblock %}
+                </div>
+                {% if show_right_bar %}
+                    <div id="sidebar-right" class="grid-6 fright">
+                        {% block right_content %}{% endblock %}
+                    </div>
+                {% endif %}
+                {% block after_content %}{% endblock %}
+            </div>
         {% endblock %}
-      </div>
-      <div id="content_base">
-      {% block content_base %}
-			  {% if not hide_left_bar %}
-			    {% block sidebar_menu %}
-          {% include g.theme.sidebar_menu %}
-          {% endblock %}
-          {% set outer_width = 20 %}
-			  {% else %}
-          {% set outer_width = 24 %}
-        {% endif %}
-			  {% if show_right_bar %}
-          {% set inner_width = outer_width - 8 %}
-			  {% else %}
-          {% set inner_width = outer_width %}
-        {% endif %}
-        <div class="grid-{{outer_width}} pad">
-          <h2 class="dark{% block header_classes %} title{% endblock %}">{% block header %}{% endblock %}
-            <!-- actions -->
-            <small>
-            {% block actions %}{% endblock %}
-            </small>
-            <!-- /actions -->
-          </h2>
-		{% block edit_box %}{% endblock %}
-          <div{% if show_right_bar %} class="{% block inner_grid %}grid-{{inner_width}}"{% endblock %}{% endif %}>
-            {% block before_content %}{% endblock %}
-            {% block content %}{% endblock %}
-          </div>
-			{% if show_right_bar %}
-          <div id="sidebar-right" class="grid-6 fright">
-            {% block right_content %}{% endblock %}
-          </div>
-          {% endif %}
-          {% block after_content %}{% endblock %}
-        </div>
-      {% endblock %}
-      </div>
-    </section>
-    {{theme_macros.footer(g.year(), g.theme_href(''))}}
-    <div id="messages">
-        {% for n in h.pop_user_notifications() %}
-          <section class="message {{ n.subject or 'info' }}">
+    </div>
+</section>
+{{ theme_macros.footer(g.year(), g.theme_href('')) }}
+<div id="messages">
+    {% for n in h.pop_user_notifications() %}
+        <section class="message {{ n.subject or 'info' }}">
             <header>Notification:</header>
             <div class="content">{{ n.text }}</div>
-          </section>
-        {% endfor %}
-    </div>
-    {% if c.show_login_overlay %}
-        {{theme_macros.login_overlay()}}
-    {% endif %}
-    {% for blob in g.resource_manager.emit('body_js') %}
-      {{ blob }}
+        </section>
     {% endfor %}
-    {% for blob in g.resource_manager.emit('body_js_tail') %}
-      {{ blob }}
-    {% endfor %}
-    {% block extra_js %}{% endblock %}
-    {% if neighborhood %}
-      {{ neighborhood.site_specific_html | safe }}
-    {% elif c.project.neighborhood %}
-      {{ c.project.neighborhood.site_specific_html | safe }}
-    {% endif %}
-    {{theme_macros.custom_js()}}
-    {% if flash %}
-    <script type="text/javascript">{{flash | safe}}</script>
-    {% endif %}
-  </body>
+</div>
+{% if c.show_login_overlay %}
+    {{ theme_macros.login_overlay() }}
+{% endif %}
+{% for blob in g.resource_manager.emit('body_js') %}
+    {{ blob }}
+{% endfor %}
+{% for blob in g.resource_manager.emit('body_js_tail') %}
+    {{ blob }}
+{% endfor %}
+{% block extra_js %}{% endblock %}
+{% if neighborhood %}
+    {{ neighborhood.site_specific_html | safe }}
+{% elif c.project.neighborhood %}
+    {{ c.project.neighborhood.site_specific_html | safe }}
+{% endif %}
+{{ theme_macros.custom_js() }}
+{% if flash %}
+    <script type="text/javascript">{{ flash | safe }}</script>
+{% endif %}
+<script>
+    $(document).ready(function () {
+        $('.tooltip').tooltipster({
+            animation: 'fade',
+            delay: 200,
+            theme: 'tooltipster-light',
+            trigger: 'hover',
+            onlyOne : false,
+            icon: '?',
+            iconTheme: 'tooltipster-icon',
+            iconDesktop: true,
+            position: 'right',
+            maxWidth: 350
+    });
+    })
+
+</script>
+</body>
 </html>


[23/23] allura git commit: [#7894] Fix issue where can_merge updated mod_date

Posted by he...@apache.org.
[#7894] Fix issue where can_merge updated mod_date


Project: http://git-wip-us.apache.org/repos/asf/allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/allura/commit/664bd19f
Tree: http://git-wip-us.apache.org/repos/asf/allura/tree/664bd19f
Diff: http://git-wip-us.apache.org/repos/asf/allura/diff/664bd19f

Branch: refs/heads/hs/7894
Commit: 664bd19f481cf11e34ddb667b509115e3504ea5e
Parents: 6e8e6f3
Author: Heith Seewald <hs...@slashdotmedia.com>
Authored: Mon Jun 29 16:07:56 2015 -0400
Committer: Heith Seewald <hs...@slashdotmedia.com>
Committed: Thu Jul 2 11:08:58 2015 -0400

----------------------------------------------------------------------
 Allura/allura/lib/utils.py             | 16 ++++++++++++++++
 Allura/allura/model/repository.py      |  8 ++++----
 Allura/allura/tasks/repo_tasks.py      |  1 +
 Allura/allura/tests/model/test_repo.py |  3 ++-
 4 files changed, 23 insertions(+), 5 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/allura/blob/664bd19f/Allura/allura/lib/utils.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/utils.py b/Allura/allura/lib/utils.py
index f812e2e..b00d7c1 100644
--- a/Allura/allura/lib/utils.py
+++ b/Allura/allura/lib/utils.py
@@ -620,6 +620,22 @@ def phone_number_hash(number):
 
 @contextmanager
 def skip_mod_date(model_cls):
+    """ Avoids updating 'mod_date'
+
+    Useful for saving cache on a model and things like that.
+
+    .. note:: This only works when the changes made to the model are flushed.
+
+    :Example:
+
+    from allura import model as M
+    key = self.can_merge_cache_key()
+    with utils.skip_mod_date(M.MergeRequest):
+        self.can_merge_cache[key] = val
+        session(self).flush(self)
+
+    :param model_cls: The model *class* being updated.
+    """
     skip_mod_date = getattr(session(model_cls)._get(), 'skip_mod_date', False)
     session(model_cls)._get().skip_mod_date = True
     try:

http://git-wip-us.apache.org/repos/asf/allura/blob/664bd19f/Allura/allura/model/repository.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/repository.py b/Allura/allura/model/repository.py
index 3f4799a..b3c60b7 100644
--- a/Allura/allura/model/repository.py
+++ b/Allura/allura/model/repository.py
@@ -832,9 +832,6 @@ class MergeRequest(VersionedArtifact, ActivityObject):
             return False
         if self.app.config.options.get('merge_disabled'):
             return False
-
-        _session = artifact_orm_session._get()
-        _session.skip_mod_date = True
         return True
 
     def can_merge_cache_key(self):
@@ -853,8 +850,11 @@ class MergeRequest(VersionedArtifact, ActivityObject):
         return self.can_merge_cache.get(key)
 
     def set_can_merge_cache(self, val):
+        from allura import model as M
         key = self.can_merge_cache_key()
-        self.can_merge_cache[key] = val
+        with utils.skip_mod_date(M.MergeRequest):
+            self.can_merge_cache[key] = val
+            session(self).flush(self)
 
     def can_merge(self):
         """

http://git-wip-us.apache.org/repos/asf/allura/blob/664bd19f/Allura/allura/tasks/repo_tasks.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tasks/repo_tasks.py b/Allura/allura/tasks/repo_tasks.py
index e44ea94..9ea9fd0 100644
--- a/Allura/allura/tasks/repo_tasks.py
+++ b/Allura/allura/tasks/repo_tasks.py
@@ -24,6 +24,7 @@ from ming.odm import session
 
 from allura.lib.decorators import task
 from allura.lib.repository import RepositoryApp
+from allura.lib.utils import skip_mod_date
 
 
 @task

http://git-wip-us.apache.org/repos/asf/allura/blob/664bd19f/Allura/allura/tests/model/test_repo.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/model/test_repo.py b/Allura/allura/tests/model/test_repo.py
index f71ad71..1f6ec3c 100644
--- a/Allura/allura/tests/model/test_repo.py
+++ b/Allura/allura/tests/model/test_repo.py
@@ -808,5 +808,6 @@ class TestMergeRequest(object):
         assert_equal(self.mr.can_merge_task_status(), None)
         repo_tasks.can_merge.post(self.mr._id)
         assert_equal(self.mr.can_merge_task_status(), 'ready')
-        M.MonQTask.run_ready()
+        with mock.patch('allura.model.repository.MergeRequest.set_can_merge_cache'):
+            M.MonQTask.run_ready()
         assert_equal(self.mr.can_merge_task_status(), 'complete')


[02/23] allura git commit: [#7884] ticket:799 Add new feature to the bottom

Posted by he...@apache.org.
[#7884] ticket:799 Add new feature to the bottom


Project: http://git-wip-us.apache.org/repos/asf/allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/allura/commit/3b97b254
Tree: http://git-wip-us.apache.org/repos/asf/allura/tree/3b97b254
Diff: http://git-wip-us.apache.org/repos/asf/allura/diff/3b97b254

Branch: refs/heads/hs/7894
Commit: 3b97b254d42a778dea492f4c0196b29a1427ac5d
Parents: d91dd1d
Author: Igor Bondarenko <je...@gmail.com>
Authored: Fri Jun 12 10:26:15 2015 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Mon Jun 29 18:04:51 2015 +0000

----------------------------------------------------------------------
 Allura/allura/ext/admin/widgets.py                            | 1 +
 Allura/allura/lib/widgets/form_fields.py                      | 2 ++
 .../lib/widgets/resources/js/sortable_repeated_field.js       | 7 ++++++-
 3 files changed, 9 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/allura/blob/3b97b254/Allura/allura/ext/admin/widgets.py
----------------------------------------------------------------------
diff --git a/Allura/allura/ext/admin/widgets.py b/Allura/allura/ext/admin/widgets.py
index 572db5d..9e6e39d 100644
--- a/Allura/allura/ext/admin/widgets.py
+++ b/Allura/allura/ext/admin/widgets.py
@@ -196,6 +196,7 @@ class MetadataAdmin(ff.AdminForm):
             empty_msg='No features yet',
             nonempty_msg='Drag and drop features to reorder. '
                          'Leave empty to delete a feature.',
+            append_to='bottom',
             button=ew.InputField(
                 css_class='add',
                 field_type='button',

http://git-wip-us.apache.org/repos/asf/allura/blob/3b97b254/Allura/allura/lib/widgets/form_fields.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/widgets/form_fields.py b/Allura/allura/lib/widgets/form_fields.py
index 7d94e47..be3fd1a 100644
--- a/Allura/allura/lib/widgets/form_fields.py
+++ b/Allura/allura/lib/widgets/form_fields.py
@@ -412,6 +412,7 @@ class SortableRepeatedMixin(JQueryMixin):
         'flist_cls',
         'stub_cls',
         'msg_cls',
+        'append_to',
     ]
     defaults = dict(
         container_cls='sortable-repeated-field',
@@ -419,6 +420,7 @@ class SortableRepeatedMixin(JQueryMixin):
         flist_cls='sortable-field-list',
         stub_cls='sortable-field-stub',
         msg_cls='sortable-field-message',
+        append_to='top',
         empty_msg='No fields have been defined',
         nonempty_msg='Drag and drop the fields to reorder',
         repetitions=0)

http://git-wip-us.apache.org/repos/asf/allura/blob/3b97b254/Allura/allura/lib/widgets/resources/js/sortable_repeated_field.js
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/widgets/resources/js/sortable_repeated_field.js b/Allura/allura/lib/widgets/resources/js/sortable_repeated_field.js
index 71006ad..ca78493 100644
--- a/Allura/allura/lib/widgets/resources/js/sortable_repeated_field.js
+++ b/Allura/allura/lib/widgets/resources/js/sortable_repeated_field.js
@@ -25,6 +25,7 @@
         flist_cls:'sortable-field-list',
         stub_cls:'sortable-field-stub',
         msg_cls:'sortable-field-message',
+        append_to:'top',  // append new field to top by default. Also supports 'bottom'
     };
 
     $.fn.SortableRepeatedField = function(options) {
@@ -116,7 +117,11 @@
                         .replace(tpl_name, self.fld_name() + '-0'));
                 });
             $new_field.find('.hasDatepicker').removeClass('hasDatepicker');
-            self.data.$flist.prepend($new_field);
+            if (self.opts.append_to == 'top') {
+                self.data.$flist.prepend($new_field);
+            } else {
+                self.data.$flist.append($new_field);
+            }
             _renumberFields();
             _manageMessages();
             // Trigger event reattachment, etc.


[13/23] allura git commit: [#7885] Added support for the jquery tooltip plugin "Tooltipster"

Posted by he...@apache.org.
[#7885] Added support for the jquery tooltip plugin "Tooltipster"


Project: http://git-wip-us.apache.org/repos/asf/allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/allura/commit/1fed9742
Tree: http://git-wip-us.apache.org/repos/asf/allura/tree/1fed9742
Diff: http://git-wip-us.apache.org/repos/asf/allura/diff/1fed9742

Branch: refs/heads/hs/7894
Commit: 1fed97427ecd480d1914287b00a53b372c093297
Parents: 0edad6e
Author: Heith Seewald <hs...@slashdotmedia.com>
Authored: Tue Jun 9 16:26:12 2015 -0400
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Mon Jun 29 18:25:54 2015 +0000

----------------------------------------------------------------------
 .../allura/public/nf/css/forge/tooltipster.css  | 41 ++++++++++++++------
 .../allura/templates/jinja_master/master.html   | 29 +++++++++++---
 2 files changed, 53 insertions(+), 17 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/allura/blob/1fed9742/Allura/allura/public/nf/css/forge/tooltipster.css
----------------------------------------------------------------------
diff --git a/Allura/allura/public/nf/css/forge/tooltipster.css b/Allura/allura/public/nf/css/forge/tooltipster.css
index db56bc3..a276c96 100755
--- a/Allura/allura/public/nf/css/forge/tooltipster.css
+++ b/Allura/allura/public/nf/css/forge/tooltipster.css
@@ -21,25 +21,32 @@
 }
 
 /* If you're using the icon option, use this next selector to style them */
+
+.tooltipster-div {
+    position: relative;
+    height: 100%;
+}
+
 .tooltipster-icon {
     cursor: help;
     background: #00AFFE;
-    font-size: medium;
-    font-weight: 600;
+    font-size: small;
+    font-weight: bold;
+    height: 1.3em;
     color: white;
-    border: 1px solid #067DB8;
-    border-radius: 15%;
+    border: 0.1em solid #067DB8;
+    border-radius: 18.5%;
     position: absolute;
-    padding: 0 4px;
-    left: 41.3%;
-    margin-top: -27.5px;
-    margin-bottom: 141px;
+    display: inline-block;
+    margin-top: 0.3em;
+    top: 2.7%;
+    right: 2%;
+    z-index: 9999999;
+    padding: 0 0.3em;
 }
 
-/* If you're using the icon option, use this next selector to style them */
 .tooltipster-icon:hover {
     background: #067DB8;
-    border: 1px solid #00AFFE;
 }
 
 /* This is the base styling required to make all Tooltipsters work */
@@ -313,4 +320,16 @@
     font-style: italic;
     line-height: 16px;
     padding: 8px 10px;
-}
\ No newline at end of file
+}
+
+textarea.tooltip {
+    position: relative;
+    display: block;
+    margin: 0px auto;
+}
+
+/*input.tooltip {*/
+    /*position: relative;*/
+    /*display: inline-block;*/
+    /*/!*margin: auto;*!/*/
+/*}*/
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/allura/blob/1fed9742/Allura/allura/templates/jinja_master/master.html
----------------------------------------------------------------------
diff --git a/Allura/allura/templates/jinja_master/master.html b/Allura/allura/templates/jinja_master/master.html
index 062d7de..087bb36 100644
--- a/Allura/allura/templates/jinja_master/master.html
+++ b/Allura/allura/templates/jinja_master/master.html
@@ -170,17 +170,34 @@
 {% endif %}
 <script>
     $(document).ready(function () {
-        $('.tooltip').tooltipster({
+        var toolTip = $(".tooltip");
+
+        var tooltipDiv = toolTip.wrap( "<div class='tooltipster-div'></div>" );
+        console.log('tooltipDiv', tooltipDiv);
+        var input_field = $('.tooltipster-div').find('input');
+
+
+
+        var input_width = input_field.outerWidth();
+        var _innerWidth = input_width + (input_width * 0.05);
+
+        if (_innerWidth < input_width){
+                     $('.tooltipster-div').width(_innerWidth);
+
+        }
+
+
+        toolTip.tooltipster({
             animation: 'fade',
             delay: 200,
             theme: 'tooltipster-light',
             trigger: 'hover',
-            onlyOne : false,
-            icon: '?',
-            iconTheme: 'tooltipster-icon',
-            iconDesktop: true,
+{#            icon: '?',#}
+{#            touchDevices: true,#}
+{#            iconDesktop: true,#}
             position: 'right',
-            maxWidth: 350
+            iconCloning: false,
+            maxWidth: 300
     });
     })
 


[18/23] allura git commit: [#7906] in login post, pass a _session_id value in both POST and cookies, so it gets past CSRF checks

Posted by he...@apache.org.
[#7906] in login post, pass a _session_id value in both POST and cookies, so it gets past CSRF checks


Project: http://git-wip-us.apache.org/repos/asf/allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/allura/commit/276671bf
Tree: http://git-wip-us.apache.org/repos/asf/allura/tree/276671bf
Diff: http://git-wip-us.apache.org/repos/asf/allura/diff/276671bf

Branch: refs/heads/hs/7894
Commit: 276671bf22bc4c7a0c0f0ed3bd270a56642b63b5
Parents: 661c8d0
Author: Dave Brondsema <db...@slashdotmedia.com>
Authored: Mon Jun 29 18:01:36 2015 +0000
Committer: Heith Seewald <hs...@slashdotmedia.com>
Committed: Mon Jun 29 20:50:21 2015 +0000

----------------------------------------------------------------------
 scripts/ApacheAccessHandler.py | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/allura/blob/276671bf/scripts/ApacheAccessHandler.py
----------------------------------------------------------------------
diff --git a/scripts/ApacheAccessHandler.py b/scripts/ApacheAccessHandler.py
index 1af3714..1ee9ebc 100644
--- a/scripts/ApacheAccessHandler.py
+++ b/scripts/ApacheAccessHandler.py
@@ -115,7 +115,11 @@ def check_authentication(req):
     r = requests.post(auth_url, allow_redirects=False, data={
         'username': username,
         'password': password,
-        'return_to': '/login_successful'})
+        'return_to': '/login_successful',
+        '_session_id': 'this-is-our-session',
+    }, cookies={
+        '_session_id': 'this-is-our-session',
+    })
     return r.status_code == 302 and r.headers['location'].endswith('/login_successful')
 
 


[12/23] allura git commit: [#7885] Remove the tooltip help icon from tooltipster

Posted by he...@apache.org.
[#7885] Remove the tooltip help icon from tooltipster


Project: http://git-wip-us.apache.org/repos/asf/allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/allura/commit/3cda474e
Tree: http://git-wip-us.apache.org/repos/asf/allura/tree/3cda474e
Diff: http://git-wip-us.apache.org/repos/asf/allura/diff/3cda474e

Branch: refs/heads/hs/7894
Commit: 3cda474e5ad2b60fa167f61874bff4480aee4a24
Parents: 1fed974
Author: Heith Seewald <hs...@slashdotmedia.com>
Authored: Wed Jun 10 12:07:21 2015 -0400
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Mon Jun 29 18:25:54 2015 +0000

----------------------------------------------------------------------
 Allura/allura/nf/allura/css/allura.css          |  2 +-
 .../allura/public/nf/css/forge/tooltipster.css  | 12 ----------
 .../allura/templates/jinja_master/master.html   | 25 ++------------------
 3 files changed, 3 insertions(+), 36 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/allura/blob/3cda474e/Allura/allura/nf/allura/css/allura.css
----------------------------------------------------------------------
diff --git a/Allura/allura/nf/allura/css/allura.css b/Allura/allura/nf/allura/css/allura.css
index 18dbd62..17ee342 100644
--- a/Allura/allura/nf/allura/css/allura.css
+++ b/Allura/allura/nf/allura/css/allura.css
@@ -86,4 +86,4 @@ tr.rev div.markdown_content p {
 
 #phone_verification_overlay iframe {
     height: 320px;
-}
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/allura/blob/3cda474e/Allura/allura/public/nf/css/forge/tooltipster.css
----------------------------------------------------------------------
diff --git a/Allura/allura/public/nf/css/forge/tooltipster.css b/Allura/allura/public/nf/css/forge/tooltipster.css
index a276c96..0c9dd4a 100755
--- a/Allura/allura/public/nf/css/forge/tooltipster.css
+++ b/Allura/allura/public/nf/css/forge/tooltipster.css
@@ -321,15 +321,3 @@
     line-height: 16px;
     padding: 8px 10px;
 }
-
-textarea.tooltip {
-    position: relative;
-    display: block;
-    margin: 0px auto;
-}
-
-/*input.tooltip {*/
-    /*position: relative;*/
-    /*display: inline-block;*/
-    /*/!*margin: auto;*!/*/
-/*}*/
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/allura/blob/3cda474e/Allura/allura/templates/jinja_master/master.html
----------------------------------------------------------------------
diff --git a/Allura/allura/templates/jinja_master/master.html b/Allura/allura/templates/jinja_master/master.html
index 087bb36..74b09bc 100644
--- a/Allura/allura/templates/jinja_master/master.html
+++ b/Allura/allura/templates/jinja_master/master.html
@@ -170,37 +170,16 @@
 {% endif %}
 <script>
     $(document).ready(function () {
-        var toolTip = $(".tooltip");
-
-        var tooltipDiv = toolTip.wrap( "<div class='tooltipster-div'></div>" );
-        console.log('tooltipDiv', tooltipDiv);
-        var input_field = $('.tooltipster-div').find('input');
-
-
-
-        var input_width = input_field.outerWidth();
-        var _innerWidth = input_width + (input_width * 0.05);
-
-        if (_innerWidth < input_width){
-                     $('.tooltipster-div').width(_innerWidth);
-
-        }
-
-
-        toolTip.tooltipster({
+        $(".tooltip").tooltipster({
             animation: 'fade',
             delay: 200,
             theme: 'tooltipster-light',
             trigger: 'hover',
-{#            icon: '?',#}
-{#            touchDevices: true,#}
-{#            iconDesktop: true,#}
             position: 'right',
             iconCloning: false,
             maxWidth: 300
-    });
+        });
     })
-
 </script>
 </body>
 </html>


[20/23] allura git commit: Mock the __ming__ (Mock doesn't do this automatically) so that if we try to cache the tags/branches, it doesn't error out

Posted by he...@apache.org.
Mock the __ming__ (Mock doesn't do this automatically) so that if we try to cache the tags/branches, it doesn't error out


Project: http://git-wip-us.apache.org/repos/asf/allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/allura/commit/0fd24ba4
Tree: http://git-wip-us.apache.org/repos/asf/allura/tree/0fd24ba4
Diff: http://git-wip-us.apache.org/repos/asf/allura/diff/0fd24ba4

Branch: refs/heads/hs/7894
Commit: 0fd24ba4cc7f1d4d49a774cdabf3fe650478737e
Parents: 14edf90
Author: Dave Brondsema <db...@slashdotmedia.com>
Authored: Thu Jul 2 14:38:33 2015 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Jul 2 14:38:33 2015 +0000

----------------------------------------------------------------------
 ForgeGit/forgegit/tests/model/test_repository.py | 2 ++
 1 file changed, 2 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/allura/blob/0fd24ba4/ForgeGit/forgegit/tests/model/test_repository.py
----------------------------------------------------------------------
diff --git a/ForgeGit/forgegit/tests/model/test_repository.py b/ForgeGit/forgegit/tests/model/test_repository.py
index c28daf1..4cfd2ef 100644
--- a/ForgeGit/forgegit/tests/model/test_repository.py
+++ b/ForgeGit/forgegit/tests/model/test_repository.py
@@ -746,6 +746,7 @@ class TestGitImplementation(unittest.TestCase):
         repo_dir = pkg_resources.resource_filename(
             'forgegit', 'tests/data/testgit.git')
         repo = mock.Mock(full_fs_path=repo_dir)
+        repo.__ming__ = mock.Mock()
         repo.cached_branches = []
         impl = GM.git_repo.GitImplementation(repo)
         self.assertEqual(impl.branches, [
@@ -759,6 +760,7 @@ class TestGitImplementation(unittest.TestCase):
         repo_dir = pkg_resources.resource_filename(
             'forgegit', 'tests/data/testgit.git')
         repo = mock.Mock(full_fs_path=repo_dir)
+        repo.__ming__ = mock.Mock()
         repo.cached_tags = []
         impl = GM.git_repo.GitImplementation(repo)
         self.assertEqual(impl.tags, [


[19/23] allura git commit: Lower default repo_refs_cache_threshold value more

Posted by he...@apache.org.
Lower default repo_refs_cache_threshold value more


Project: http://git-wip-us.apache.org/repos/asf/allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/allura/commit/14edf90f
Tree: http://git-wip-us.apache.org/repos/asf/allura/tree/14edf90f
Diff: http://git-wip-us.apache.org/repos/asf/allura/diff/14edf90f

Branch: refs/heads/hs/7894
Commit: 14edf90fdee4b2e985af9120016cd720a8b1bcfb
Parents: 276671b
Author: Dave Brondsema <da...@brondsema.net>
Authored: Wed Jul 1 17:15:13 2015 -0400
Committer: Dave Brondsema <da...@brondsema.net>
Committed: Wed Jul 1 17:15:13 2015 -0400

----------------------------------------------------------------------
 Allura/development.ini | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/allura/blob/14edf90f/Allura/development.ini
----------------------------------------------------------------------
diff --git a/Allura/development.ini b/Allura/development.ini
index bb2ccab..c3325ed 100644
--- a/Allura/development.ini
+++ b/Allura/development.ini
@@ -301,7 +301,7 @@ scm.import.retry_sleep_secs = 5
 ; the results in mongo based on a threshold. Set `repo_refs_cache_threshold` (in seconds) and the resulting
 ; lists will be cached and served from cache on subsequent requests until reset by `repo_refresh`.
 ; Set to 0 to cache all references. Remove entirely to cache nothing.
-repo_refs_cache_threshold = .5
+repo_refs_cache_threshold = .01
 
 ; One-click merge is enabled by default, but can be turned off on for each type of repo
 scm.merge.git.disabled = false


[06/23] allura git commit: [#7884] ticket:805 Fix sortable fields in Firefox

Posted by he...@apache.org.
[#7884] ticket:805 Fix sortable fields in Firefox


Project: http://git-wip-us.apache.org/repos/asf/allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/allura/commit/cbd25fb1
Tree: http://git-wip-us.apache.org/repos/asf/allura/tree/cbd25fb1
Diff: http://git-wip-us.apache.org/repos/asf/allura/diff/cbd25fb1

Branch: refs/heads/hs/7894
Commit: cbd25fb150ddb2f3d70b9d75edc15a9133d525a4
Parents: 651047d
Author: Igor Bondarenko <je...@gmail.com>
Authored: Wed Jun 17 16:37:13 2015 +0300
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Mon Jun 29 18:04:52 2015 +0000

----------------------------------------------------------------------
 Allura/allura/templates/widgets/sortable_repeated_field.html | 2 ++
 1 file changed, 2 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/allura/blob/cbd25fb1/Allura/allura/templates/widgets/sortable_repeated_field.html
----------------------------------------------------------------------
diff --git a/Allura/allura/templates/widgets/sortable_repeated_field.html b/Allura/allura/templates/widgets/sortable_repeated_field.html
index 538d14f..7f28091 100644
--- a/Allura/allura/templates/widgets/sortable_repeated_field.html
+++ b/Allura/allura/templates/widgets/sortable_repeated_field.html
@@ -19,7 +19,9 @@
 <div xmlns="http://www.w3.org/1999/xhtml"
      xmlns:py="http://genshi.edgewall.org/"
      class="$container_cls"
+     style="overflow:auto;"
      py:attrs="{'data-name':name}">
+   <!-- We need "overflow:auto" for sortable.js to properly work in Firefox http://stackoverflow.com/a/3393282/4640690 -->
    <div><py:if test="show_msg"><p
           class="$msg_cls"
           data-empty-message="$empty_msg"


[16/23] allura git commit: [#7803] ticket:810 Updated documentation to specify the correct pkill command for stopping taskd

Posted by he...@apache.org.
[#7803] ticket:810 Updated documentation to specify the correct pkill command for stopping taskd


Project: http://git-wip-us.apache.org/repos/asf/allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/allura/commit/446a39d1
Tree: http://git-wip-us.apache.org/repos/asf/allura/tree/446a39d1
Diff: http://git-wip-us.apache.org/repos/asf/allura/diff/446a39d1

Branch: refs/heads/hs/7894
Commit: 446a39d10dbdc8ff19d85befc35981ebced62b89
Parents: 6b7a5fd
Author: Aleksey 'LXj' Alekseyev <go...@gmail.com>
Authored: Sun Jun 28 13:46:15 2015 +0300
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Mon Jun 29 20:26:56 2015 +0000

----------------------------------------------------------------------
 Allura/docs/development/contributing.rst | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/allura/blob/446a39d1/Allura/docs/development/contributing.rst
----------------------------------------------------------------------
diff --git a/Allura/docs/development/contributing.rst b/Allura/docs/development/contributing.rst
index 8a8cfdb..3e6c8d9 100644
--- a/Allura/docs/development/contributing.rst
+++ b/Allura/docs/development/contributing.rst
@@ -175,7 +175,7 @@ foreground::
     # web
     pkill "paster serve" && paster serve --reload ../development.ini
     # taskd
-    pkill "paster taskd" && paster taskd ../development.ini --nocapture
+    pkill "^taskd" && paster taskd ../development.ini --nocapture
 
 Make a request to the web app, and when your line of code is hit, a debug
 session will start on the console where the process is running.


[17/23] allura git commit: [#7803] tweak regex to match taskd procs currently handling a task

Posted by he...@apache.org.
[#7803] tweak regex to match taskd procs currently handling a task


Project: http://git-wip-us.apache.org/repos/asf/allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/allura/commit/661c8d0f
Tree: http://git-wip-us.apache.org/repos/asf/allura/tree/661c8d0f
Diff: http://git-wip-us.apache.org/repos/asf/allura/diff/661c8d0f

Branch: refs/heads/hs/7894
Commit: 661c8d0f2296269e6a9929d3640f161622fd1d78
Parents: 446a39d
Author: Dave Brondsema <db...@slashdotmedia.com>
Authored: Mon Jun 29 20:26:39 2015 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Mon Jun 29 20:27:02 2015 +0000

----------------------------------------------------------------------
 Allura/allura/command/taskd_cleanup.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/allura/blob/661c8d0f/Allura/allura/command/taskd_cleanup.py
----------------------------------------------------------------------
diff --git a/Allura/allura/command/taskd_cleanup.py b/Allura/allura/command/taskd_cleanup.py
index 99caa80..47d09bf 100644
--- a/Allura/allura/command/taskd_cleanup.py
+++ b/Allura/allura/command/taskd_cleanup.py
@@ -128,8 +128,8 @@ class TaskdCleanupCommand(base.Command):
         })
 
     def _taskd_pids(self):
-        # space after "taskd" to ensure no match on taskd_cleanup (ourself)
-        p = subprocess.Popen(['pgrep', '-f', '^taskd '],
+        # space or colon after "taskd" to ensure no match on taskd_cleanup (ourself)
+        p = subprocess.Popen(['pgrep', '-f', '^taskd[ :]'],
                              stdout=subprocess.PIPE,
                              stderr=subprocess.PIPE)
         stdout, stderr = p.communicate()


[10/23] allura git commit: [#7885] Added tooltips to admin form fields

Posted by he...@apache.org.
[#7885] Added tooltips to admin form fields


Project: http://git-wip-us.apache.org/repos/asf/allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/allura/commit/0edad6ef
Tree: http://git-wip-us.apache.org/repos/asf/allura/tree/0edad6ef
Diff: http://git-wip-us.apache.org/repos/asf/allura/diff/0edad6ef

Branch: refs/heads/hs/7894
Commit: 0edad6efbcf2dd66a4066343a2cff90046bab472
Parents: 85eec26
Author: Heith Seewald <hs...@slashdotmedia.com>
Authored: Tue Jun 9 16:23:51 2015 -0400
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Mon Jun 29 18:25:52 2015 +0000

----------------------------------------------------------------------
 Allura/allura/ext/admin/widgets.py | 25 ++++++++++++++++++++-----
 Allura/allura/lib/widgets/forms.py |  4 ++++
 2 files changed, 24 insertions(+), 5 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/allura/blob/0edad6ef/Allura/allura/ext/admin/widgets.py
----------------------------------------------------------------------
diff --git a/Allura/allura/ext/admin/widgets.py b/Allura/allura/ext/admin/widgets.py
index 0d5cae9..0f7d33d 100644
--- a/Allura/allura/ext/admin/widgets.py
+++ b/Allura/allura/ext/admin/widgets.py
@@ -150,7 +150,14 @@ class ScreenshotAdmin(ff.ForgeForm):
         fields = [
             ew.InputField(name='screenshot', field_type='file',
                           label='New Screenshot'),
-            ew.InputField(name='caption', field_type="text", label='Caption')
+            ew.InputField(name='caption',
+                          field_type="text",
+                          label='Caption',
+                          attrs={
+                              'title': "Reuse your project name in screenshot file names and create a caption to briefly describe each screenshot.",
+                              'class': 'tooltip',
+                          }
+                          )
         ]
         return fields
 
@@ -176,18 +183,26 @@ class MetadataAdmin(ff.AdminForm):
                              validator=formencode.All(
                                  fev.UnicodeString(not_empty=True, max=40),
                                  V.MaxBytesValidator(max=40)),
-                             attrs=dict(maxlength=40,
-                                        title="This is the publicly viewable name of the project, and will appear on project listings. It should be what you want to see as the project title in search listing."))
+                             attrs={'maxlength': 40,
+                                    'title': "This is the publicly viewable name of the project, and will appear on project listings. It should be what you want to see as the project title in search listing.",
+                                    'class': 'tooltip',
+                                    })
         summary = ew.InputField(field_type="text", label='Short Summary',
                                 validator=formencode.All(
                                     fev.UnicodeString(max=70),
                                     V.MaxBytesValidator(max=70)),
-                                attrs=dict(maxlength=70))
+                                attrs={'maxlength': 70,
+                                       'title': 'Briefly state what your project is and what it does without repeating the project name. This summary appears in Google search results beneath the project name.',
+                                       'class': 'tooltip',
+                                       })
         short_description = ew.TextArea(label='Full Description',
                                         validator=formencode.All(
                                             fev.UnicodeString(max=1000),
                                             V.MaxBytesValidator(max=1000)),
-                                        attrs=dict(title="Add a few paragraphs describing your project to new users."))
+                                        attrs={
+                                            'title': 'Describe the full functionality of your project using related keywords. The first sentence has the most impact on search. Provide unique content that calls out keywords and describes the merits of your project.',
+                                            'class': 'tooltip'
+                                        })
         # Apparently, child field must be CompoundField with custom template
         # for SortableRepeatedField to work properly, that's why FeaturesField
         # is not just ew.TextField

http://git-wip-us.apache.org/repos/asf/allura/blob/0edad6ef/Allura/allura/lib/widgets/forms.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/widgets/forms.py b/Allura/allura/lib/widgets/forms.py
index 714a32b..f523208 100644
--- a/Allura/allura/lib/widgets/forms.py
+++ b/Allura/allura/lib/widgets/forms.py
@@ -947,6 +947,10 @@ class NeighborhoodAddProjectForm(ForgeForm):
                           ),
             ew.InputField(name='project_unixname',
                           label='Short Name', field_type='text',
+                          attrs={
+                              'title': 'Create a URL name that matches your project name as closely as possible to improve search indexing and maximize visibility.',
+                              'class': 'tooltip'
+                          },
                           validator=provider.shortname_validator),
             ew.CheckboxSet(name='tools', options=tools_options),
         ])


[21/23] allura git commit: [#7894] Merge request timestamps are no longer updated incorrectly

Posted by he...@apache.org.
[#7894] Merge request timestamps are no longer updated incorrectly


Project: http://git-wip-us.apache.org/repos/asf/allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/allura/commit/23bcdc9b
Tree: http://git-wip-us.apache.org/repos/asf/allura/tree/23bcdc9b
Diff: http://git-wip-us.apache.org/repos/asf/allura/diff/23bcdc9b

Branch: refs/heads/hs/7894
Commit: 23bcdc9b1dcee3c230fa49003902d22aaa72520f
Parents: 0fd24ba
Author: Heith Seewald <hs...@slashdotmedia.com>
Authored: Mon Jun 15 16:33:06 2015 -0400
Committer: Heith Seewald <hs...@slashdotmedia.com>
Committed: Thu Jul 2 11:08:58 2015 -0400

----------------------------------------------------------------------
 Allura/allura/model/repository.py | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/allura/blob/23bcdc9b/Allura/allura/model/repository.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/repository.py b/Allura/allura/model/repository.py
index a3d574c..3f4799a 100644
--- a/Allura/allura/model/repository.py
+++ b/Allura/allura/model/repository.py
@@ -56,7 +56,8 @@ from .timeline import ActivityObject
 from .monq_model import MonQTask
 from .project import AppConfig
 from .session import main_doc_session
-from .session import repository_orm_session
+from .session import repository_orm_session, artifact_orm_session
+
 
 log = logging.getLogger(__name__)
 config = utils.ConfigProxy(
@@ -831,6 +832,9 @@ class MergeRequest(VersionedArtifact, ActivityObject):
             return False
         if self.app.config.options.get('merge_disabled'):
             return False
+
+        _session = artifact_orm_session._get()
+        _session.skip_mod_date = True
         return True
 
     def can_merge_cache_key(self):


[11/23] allura git commit: [#7885] Add license info for tooltipster

Posted by he...@apache.org.
[#7885] Add license info for tooltipster


Project: http://git-wip-us.apache.org/repos/asf/allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/allura/commit/6ba4a431
Tree: http://git-wip-us.apache.org/repos/asf/allura/tree/6ba4a431
Diff: http://git-wip-us.apache.org/repos/asf/allura/diff/6ba4a431

Branch: refs/heads/hs/7894
Commit: 6ba4a431f8de2440a1e2d26e63ea8bfa36c983c9
Parents: 3f47be1
Author: Heith Seewald <hs...@slashdotmedia.com>
Authored: Thu Jun 25 18:05:32 2015 -0400
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Mon Jun 29 18:25:54 2015 +0000

----------------------------------------------------------------------
 Allura/LICENSE   | 2 ++
 LICENSE          | 2 ++
 rat-excludes.txt | 2 ++
 3 files changed, 6 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/allura/blob/6ba4a431/Allura/LICENSE
----------------------------------------------------------------------
diff --git a/Allura/LICENSE b/Allura/LICENSE
index a447b69..452064c 100644
--- a/Allura/LICENSE
+++ b/Allura/LICENSE
@@ -233,6 +233,8 @@ under the MIT license.  For details, see the individual files:
     allura/public/nf/js/jquery.maxlength.min.js
     allura/public/nf/js/jquery.viewport.js
     allura/public/nf/js/jquery.tablesorter.js
+    allura/public/nf/css/forge/tooltipster.css
+    allura/public/nf/js/jquery.tooltipster.js
 
 Blueprint, which is available under the MIT license.
 For details, see allura/public/nf/css/blueprint/

http://git-wip-us.apache.org/repos/asf/allura/blob/6ba4a431/LICENSE
----------------------------------------------------------------------
diff --git a/LICENSE b/LICENSE
index 71c5c69..d36ef6e 100644
--- a/LICENSE
+++ b/LICENSE
@@ -236,6 +236,8 @@ under the MIT license.  For details, see the individual files:
     ForgeTracker/forgetracker/widgets/resources/js/jquery.multiselect.min.js
     ForgeTracker/forgetracker/widgets/resources/css/jquery.multiselect.css
     Allura/allura/public/nf/js/jquery.tablesorter.js
+    Allura/allura/public/nf/css/forge/tooltipster.css
+    Allura/allura/public/nf/js/jquery.tooltipster.js
 
 Blueprint, which is available under the MIT license.
 For details, see Allura/allura/public/nf/css/blueprint/

http://git-wip-us.apache.org/repos/asf/allura/blob/6ba4a431/rat-excludes.txt
----------------------------------------------------------------------
diff --git a/rat-excludes.txt b/rat-excludes.txt
index 4e3b800..ee547eb 100644
--- a/rat-excludes.txt
+++ b/rat-excludes.txt
@@ -26,6 +26,8 @@ Allura/allura/lib/widgets/resources/js/jquery.textarea.js
 Allura/allura/public/nf/js/jquery.flot.js
 Allura/allura/public/nf/js/jquery.maxlength.min.js
 Allura/allura/public/nf/js/jquery.tablesorter.js
+Allura/allura/public/nf/css/forge/tooltipster.css
+Allura/allura/public/nf/js/jquery.tooltipster.js
 Allura/allura/public/nf/js/jquery.viewport.js
 Allura/allura/public/nf/css/blueprint/
 Allura/allura/public/nf/js/sylvester.js


[09/23] allura git commit: [#7885] Added static files for the jquery tool tip lib

Posted by he...@apache.org.
[#7885] Added static files for the jquery tool tip lib


Project: http://git-wip-us.apache.org/repos/asf/allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/allura/commit/1bac7286
Tree: http://git-wip-us.apache.org/repos/asf/allura/tree/1bac7286
Diff: http://git-wip-us.apache.org/repos/asf/allura/diff/1bac7286

Branch: refs/heads/hs/7894
Commit: 1bac72864d3623370773d8d1d0a28617c5d95420
Parents: cddde56
Author: Heith Seewald <hs...@slashdotmedia.com>
Authored: Mon Jun 8 15:42:02 2015 -0400
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Mon Jun 29 18:25:47 2015 +0000

----------------------------------------------------------------------
 .../allura/public/nf/css/forge/tooltipster.css  |  316 +++++
 .../allura/public/nf/js/jquery.tooltipster.js   | 1327 ++++++++++++++++++
 2 files changed, 1643 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/allura/blob/1bac7286/Allura/allura/public/nf/css/forge/tooltipster.css
----------------------------------------------------------------------
diff --git a/Allura/allura/public/nf/css/forge/tooltipster.css b/Allura/allura/public/nf/css/forge/tooltipster.css
new file mode 100755
index 0000000..db56bc3
--- /dev/null
+++ b/Allura/allura/public/nf/css/forge/tooltipster.css
@@ -0,0 +1,316 @@
+/* This is the default Tooltipster theme (feel free to modify or duplicate and create multiple themes!): */
+.tooltipster-default {
+    border-radius: 5px;
+    border: 2px solid #000;
+    background: #4c4c4c;
+    color: #fff;
+}
+
+/* Use this next selector to style things like font-size and line-height: */
+.tooltipster-default .tooltipster-content {
+    font-family: Arial, sans-serif;
+    font-size: 14px;
+    line-height: 16px;
+    padding: 8px 10px;
+    overflow: hidden;
+}
+
+/* This next selector defines the color of the border on the outside of the arrow. This will automatically match the color and size of the border set on the main tooltip styles. Set display: none; if you would like a border around the tooltip but no border around the arrow */
+.tooltipster-default .tooltipster-arrow .tooltipster-arrow-border {
+    /* border-color: ... !important; */
+}
+
+/* If you're using the icon option, use this next selector to style them */
+.tooltipster-icon {
+    cursor: help;
+    background: #00AFFE;
+    font-size: medium;
+    font-weight: 600;
+    color: white;
+    border: 1px solid #067DB8;
+    border-radius: 15%;
+    position: absolute;
+    padding: 0 4px;
+    left: 41.3%;
+    margin-top: -27.5px;
+    margin-bottom: 141px;
+}
+
+/* If you're using the icon option, use this next selector to style them */
+.tooltipster-icon:hover {
+    background: #067DB8;
+    border: 1px solid #00AFFE;
+}
+
+/* This is the base styling required to make all Tooltipsters work */
+.tooltipster-base {
+    padding: 0;
+    font-size: 0;
+    line-height: 0;
+    position: absolute;
+    left: 0;
+    top: 0;
+    z-index: 9999999;
+    pointer-events: none;
+    width: auto;
+    overflow: visible;
+}
+
+.tooltipster-base .tooltipster-content {
+    overflow: hidden;
+}
+
+/* These next classes handle the styles for the little arrow attached to the tooltip. By default, the arrow will inherit the same colors and border as what is set on the main tooltip itself. */
+.tooltipster-arrow {
+    display: block;
+    text-align: center;
+    width: 100%;
+    height: 100%;
+    position: absolute;
+    top: 0;
+    left: 0;
+    z-index: -1;
+}
+
+.tooltipster-arrow span, .tooltipster-arrow-border {
+    display: block;
+    width: 0;
+    height: 0;
+    position: absolute;
+}
+
+.tooltipster-arrow-top span, .tooltipster-arrow-top-right span, .tooltipster-arrow-top-left span {
+    border-left: 8px solid transparent !important;
+    border-right: 8px solid transparent !important;
+    border-top: 8px solid;
+    bottom: -7px;
+}
+
+.tooltipster-arrow-top .tooltipster-arrow-border, .tooltipster-arrow-top-right .tooltipster-arrow-border, .tooltipster-arrow-top-left .tooltipster-arrow-border {
+    border-left: 9px solid transparent !important;
+    border-right: 9px solid transparent !important;
+    border-top: 9px solid;
+    bottom: -7px;
+}
+
+.tooltipster-arrow-bottom span, .tooltipster-arrow-bottom-right span, .tooltipster-arrow-bottom-left span {
+    border-left: 8px solid transparent !important;
+    border-right: 8px solid transparent !important;
+    border-bottom: 8px solid;
+    top: -7px;
+}
+
+.tooltipster-arrow-bottom .tooltipster-arrow-border, .tooltipster-arrow-bottom-right .tooltipster-arrow-border, .tooltipster-arrow-bottom-left .tooltipster-arrow-border {
+    border-left: 9px solid transparent !important;
+    border-right: 9px solid transparent !important;
+    border-bottom: 9px solid;
+    top: -7px;
+}
+
+.tooltipster-arrow-top span, .tooltipster-arrow-top .tooltipster-arrow-border, .tooltipster-arrow-bottom span, .tooltipster-arrow-bottom .tooltipster-arrow-border {
+    left: 0;
+    right: 0;
+    margin: 0 auto;
+}
+
+.tooltipster-arrow-top-left span, .tooltipster-arrow-bottom-left span {
+    left: 6px;
+}
+
+.tooltipster-arrow-top-left .tooltipster-arrow-border, .tooltipster-arrow-bottom-left .tooltipster-arrow-border {
+    left: 5px;
+}
+
+.tooltipster-arrow-top-right span, .tooltipster-arrow-bottom-right span {
+    right: 6px;
+}
+
+.tooltipster-arrow-top-right .tooltipster-arrow-border, .tooltipster-arrow-bottom-right .tooltipster-arrow-border {
+    right: 5px;
+}
+
+.tooltipster-arrow-left span, .tooltipster-arrow-left .tooltipster-arrow-border {
+    border-top: 8px solid transparent !important;
+    border-bottom: 8px solid transparent !important;
+    border-left: 8px solid;
+    top: 50%;
+    margin-top: -7px;
+    right: -7px;
+}
+
+.tooltipster-arrow-left .tooltipster-arrow-border {
+    border-top: 9px solid transparent !important;
+    border-bottom: 9px solid transparent !important;
+    border-left: 9px solid;
+    margin-top: -8px;
+}
+
+.tooltipster-arrow-right span, .tooltipster-arrow-right .tooltipster-arrow-border {
+    border-top: 8px solid transparent !important;
+    border-bottom: 8px solid transparent !important;
+    border-right: 8px solid;
+    top: 50%;
+    margin-top: -7px;
+    left: -7px;
+}
+
+.tooltipster-arrow-right .tooltipster-arrow-border {
+    border-top: 9px solid transparent !important;
+    border-bottom: 9px solid transparent !important;
+    border-right: 9px solid;
+    margin-top: -8px;
+}
+
+/* Some CSS magic for the awesome animations - feel free to make your own custom animations and reference it in your Tooltipster settings! */
+
+.tooltipster-fade {
+    opacity: 0;
+    -webkit-transition-property: opacity;
+    -moz-transition-property: opacity;
+    -o-transition-property: opacity;
+    -ms-transition-property: opacity;
+    transition-property: opacity;
+}
+
+.tooltipster-fade-show {
+    opacity: 1;
+}
+
+.tooltipster-grow {
+    -webkit-transform: scale(0, 0);
+    -moz-transform: scale(0, 0);
+    -o-transform: scale(0, 0);
+    -ms-transform: scale(0, 0);
+    transform: scale(0, 0);
+    -webkit-transition-property: -webkit-transform;
+    -moz-transition-property: -moz-transform;
+    -o-transition-property: -o-transform;
+    -ms-transition-property: -ms-transform;
+    transition-property: transform;
+    -webkit-backface-visibility: hidden;
+}
+
+.tooltipster-grow-show {
+    -webkit-transform: scale(1, 1);
+    -moz-transform: scale(1, 1);
+    -o-transform: scale(1, 1);
+    -ms-transform: scale(1, 1);
+    transform: scale(1, 1);
+    -webkit-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
+    -webkit-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
+    -moz-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
+    -ms-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
+    -o-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
+    transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
+}
+
+.tooltipster-swing {
+    opacity: 0;
+    -webkit-transform: rotateZ(4deg);
+    -moz-transform: rotateZ(4deg);
+    -o-transform: rotateZ(4deg);
+    -ms-transform: rotateZ(4deg);
+    transform: rotateZ(4deg);
+    -webkit-transition-property: -webkit-transform, opacity;
+    -moz-transition-property: -moz-transform;
+    -o-transition-property: -o-transform;
+    -ms-transition-property: -ms-transform;
+    transition-property: transform;
+}
+
+.tooltipster-swing-show {
+    opacity: 1;
+    -webkit-transform: rotateZ(0deg);
+    -moz-transform: rotateZ(0deg);
+    -o-transform: rotateZ(0deg);
+    -ms-transform: rotateZ(0deg);
+    transform: rotateZ(0deg);
+    -webkit-transition-timing-function: cubic-bezier(0.230, 0.635, 0.495, 1);
+    -webkit-transition-timing-function: cubic-bezier(0.230, 0.635, 0.495, 2.4);
+    -moz-transition-timing-function: cubic-bezier(0.230, 0.635, 0.495, 2.4);
+    -ms-transition-timing-function: cubic-bezier(0.230, 0.635, 0.495, 2.4);
+    -o-transition-timing-function: cubic-bezier(0.230, 0.635, 0.495, 2.4);
+    transition-timing-function: cubic-bezier(0.230, 0.635, 0.495, 2.4);
+}
+
+.tooltipster-fall {
+    top: 0;
+    -webkit-transition-property: top;
+    -moz-transition-property: top;
+    -o-transition-property: top;
+    -ms-transition-property: top;
+    transition-property: top;
+    -webkit-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
+    -webkit-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
+    -moz-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
+    -ms-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
+    -o-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
+    transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
+}
+
+.tooltipster-fall-show {
+}
+
+.tooltipster-fall.tooltipster-dying {
+    -webkit-transition-property: all;
+    -moz-transition-property: all;
+    -o-transition-property: all;
+    -ms-transition-property: all;
+    transition-property: all;
+    top: 0px !important;
+    opacity: 0;
+}
+
+.tooltipster-slide {
+    left: -40px;
+    -webkit-transition-property: left;
+    -moz-transition-property: left;
+    -o-transition-property: left;
+    -ms-transition-property: left;
+    transition-property: left;
+    -webkit-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
+    -webkit-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
+    -moz-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
+    -ms-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
+    -o-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
+    transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
+}
+
+.tooltipster-slide.tooltipster-slide-show {
+}
+
+.tooltipster-slide.tooltipster-dying {
+    -webkit-transition-property: all;
+    -moz-transition-property: all;
+    -o-transition-property: all;
+    -ms-transition-property: all;
+    transition-property: all;
+    left: 0px !important;
+    opacity: 0;
+}
+
+/* CSS transition for when contenting is changing in a tooltip that is still open. The only properties that will NOT transition are: width, height, top, and left */
+.tooltipster-content-changing {
+    opacity: 0.5;
+    -webkit-transform: scale(1.1, 1.1);
+    -moz-transform: scale(1.1, 1.1);
+    -o-transform: scale(1.1, 1.1);
+    -ms-transform: scale(1.1, 1.1);
+    transform: scale(1.1, 1.1);
+}
+
+.tooltipster-light {
+    border-radius: 5px;
+    border: 0 solid #cccccc;
+    background: #0077AB;
+    color: #666666;
+}
+
+.tooltipster-light .tooltipster-content {
+    font-family: Arial, sans-serif;
+    font-size: 12px;
+    color: white;
+    font-style: italic;
+    line-height: 16px;
+    padding: 8px 10px;
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/allura/blob/1bac7286/Allura/allura/public/nf/js/jquery.tooltipster.js
----------------------------------------------------------------------
diff --git a/Allura/allura/public/nf/js/jquery.tooltipster.js b/Allura/allura/public/nf/js/jquery.tooltipster.js
new file mode 100755
index 0000000..439c4f7
--- /dev/null
+++ b/Allura/allura/public/nf/js/jquery.tooltipster.js
@@ -0,0 +1,1327 @@
+/*
+
+Tooltipster 3.3.0 | 2014-11-08
+A rockin' custom tooltip jQuery plugin
+
+Developed by Caleb Jacob under the MIT license http://opensource.org/licenses/MIT
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+*/
+
+;(function ($, window, document) {
+
+	var pluginName = "tooltipster",
+		defaults = {
+			animation: 'fade',
+			arrow: true,
+			arrowColor: '',
+			autoClose: true,
+			content: null,
+			contentAsHTML: false,
+			contentCloning: true,
+			debug: true,
+			delay: 200,
+			minWidth: 0,
+			maxWidth: null,
+			functionInit: function(origin, content) {},
+			functionBefore: function(origin, continueTooltip) {
+				continueTooltip();
+			},
+			functionReady: function(origin, tooltip) {},
+			functionAfter: function(origin) {},
+			hideOnClick: false,
+			icon: '(?)',
+			iconCloning: true,
+			iconDesktop: false,
+			iconTouch: false,
+			iconTheme: 'tooltipster-icon',
+			interactive: false,
+			interactiveTolerance: 350,
+			multiple: false,
+			offsetX: 0,
+			offsetY: 0,
+			onlyOne: false,
+			position: 'top',
+			positionTracker: false,
+			positionTrackerCallback: function(origin){
+				// the default tracker callback will close the tooltip when the trigger is
+				// 'hover' (see https://github.com/iamceege/tooltipster/pull/253)
+				if(this.option('trigger') == 'hover' && this.option('autoClose')) {
+					this.hide();
+				}
+			},
+			restoration: 'current',
+			speed: 350,
+			timer: 0,
+			theme: 'tooltipster-default',
+			touchDevices: true,
+			trigger: 'hover',
+			updateAnimation: true
+		};
+	
+	function Plugin(element, options) {
+		
+		// list of instance variables
+		
+		this.bodyOverflowX;
+		// stack of custom callbacks provided as parameters to API methods
+		this.callbacks = {
+			hide: [],
+			show: []
+		};
+		this.checkInterval = null;
+		// this will be the user content shown in the tooltip. A capital "C" is used because there is also a method called content()
+		this.Content;
+		// this is the original element which is being applied the tooltipster plugin
+		this.$el = $(element);
+		// this will be the element which triggers the appearance of the tooltip on hover/click/custom events.
+		// it will be the same as this.$el if icons are not used (see in the options), otherwise it will correspond to the created icon
+		this.$elProxy;
+		this.elProxyPosition;
+		this.enabled = true;
+		this.options = $.extend({}, defaults, options);
+		this.mouseIsOverProxy = false;
+		// a unique namespace per instance, for easy selective unbinding
+		this.namespace = 'tooltipster-'+ Math.round(Math.random()*100000);
+		// Status (capital S) can be either : appearing, shown, disappearing, hidden
+		this.Status = 'hidden';
+		this.timerHide = null;
+		this.timerShow = null;
+		// this will be the tooltip element (jQuery wrapped HTML element)
+		this.$tooltip;
+		
+		// for backward compatibility
+		this.options.iconTheme = this.options.iconTheme.replace('.', '');
+		this.options.theme = this.options.theme.replace('.', '');
+		
+		// launch
+		
+		this._init();
+	}
+	
+	Plugin.prototype = {
+		
+		_init: function() {
+			
+			var self = this;
+			
+			// disable the plugin on old browsers (including IE7 and lower)
+			if (document.querySelector) {
+				
+				// note : the content is null (empty) by default and can stay that way if the plugin remains initialized but not fed any content. The tooltip will just not appear.
+				
+				// let's save the initial value of the title attribute for later restoration if need be.
+				var initialTitle = null;
+				// it will already have been saved in case of multiple tooltips
+				if (self.$el.data('tooltipster-initialTitle') === undefined) {
+					
+					initialTitle = self.$el.attr('title');
+					
+					// we do not want initialTitle to have the value "undefined" because of how jQuery's .data() method works
+					if (initialTitle === undefined) initialTitle = null;
+					
+					self.$el.data('tooltipster-initialTitle', initialTitle);
+				}
+				
+				// if content is provided in the options, its has precedence over the title attribute.
+				// Note : an empty string is considered content, only 'null' represents the absence of content.
+				// Also, an existing title="" attribute will result in an empty string content
+				if (self.options.content !== null){
+					self._content_set(self.options.content);
+				}
+				else {
+					self._content_set(initialTitle);
+				}
+				
+				var c = self.options.functionInit.call(self.$el, self.$el, self.Content);
+				if(typeof c !== 'undefined') self._content_set(c);
+				
+				self.$el
+					// strip the title off of the element to prevent the default tooltips from popping up
+					.removeAttr('title')
+					// to be able to find all instances on the page later (upon window events in particular)
+					.addClass('tooltipstered');
+
+				// detect if we're changing the tooltip origin to an icon
+				// note about this condition : if the device has touch capability and self.options.iconTouch is false, you'll have no icons event though you may consider your device as a desktop if it also has a mouse. Not sure why someone would have this use case though.
+				if ((!deviceHasTouchCapability && self.options.iconDesktop) || (deviceHasTouchCapability && self.options.iconTouch)) {
+					
+					// TODO : the tooltip should be automatically be given an absolute position to be near the origin. Otherwise, when the origin is floating or what, it's going to be nowhere near it and disturb the position flow of the page elements. It will imply that the icon also detects when its origin moves, to follow it : not trivial.
+					// Until it's done, the icon feature does not really make sense since the user still has most of the work to do by himself
+					
+					// if the icon provided is in the form of a string
+					if(typeof self.options.icon === 'string'){
+						// wrap it in a span with the icon class
+						self.$elProxy = $('<span class="'+ self.options.iconTheme +'"></span>');
+						self.$elProxy.text(self.options.icon);
+					}
+					// if it is an object (sensible choice)
+					else {
+						// (deep) clone the object if iconCloning == true, to make sure every instance has its own proxy. We use the icon without wrapping, no need to. We do not give it a class either, as the user will undoubtedly style the object on his own and since our css properties may conflict with his own
+						if (self.options.iconCloning) self.$elProxy = self.options.icon.clone(true);
+						else self.$elProxy = self.options.icon;
+					}
+					
+					self.$elProxy.insertAfter(self.$el);
+				}
+				else {
+					self.$elProxy = self.$el;
+				}
+				
+				// for 'click' and 'hover' triggers : bind on events to open the tooltip. Closing is now handled in _showNow() because of its bindings.
+				// Notes about touch events :
+					// - mouseenter, mouseleave and clicks happen even on pure touch devices because they are emulated. deviceIsPureTouch() is a simple attempt to detect them.
+					// - on hybrid devices, we do not prevent touch gesture from opening tooltips. It would be too complex to differentiate real mouse events from emulated ones.
+					// - we check deviceIsPureTouch() at each event rather than prior to binding because the situation may change during browsing
+				if (self.options.trigger == 'hover') {
+					
+					// these binding are for mouse interaction only
+					self.$elProxy
+						.on('mouseenter.'+ self.namespace, function() {
+							if (!deviceIsPureTouch() || self.options.touchDevices) {
+								self.mouseIsOverProxy = true;
+								self._show();
+							}
+						})
+						.on('mouseleave.'+ self.namespace, function() {
+							if (!deviceIsPureTouch() || self.options.touchDevices) {
+								self.mouseIsOverProxy = false;
+							}
+						});
+					
+					// for touch interaction only
+					if (deviceHasTouchCapability && self.options.touchDevices) {
+						
+						// for touch devices, we immediately display the tooltip because we cannot rely on mouseleave to handle the delay
+						self.$elProxy.on('touchstart.'+ self.namespace, function() {
+							self._showNow();
+						});
+					}
+				}
+				else if (self.options.trigger == 'click') {
+					
+					// note : for touch devices, we do not bind on touchstart, we only rely on the emulated clicks (triggered by taps)
+					self.$elProxy.on('click.'+ self.namespace, function() {
+						if (!deviceIsPureTouch() || self.options.touchDevices) {
+							self._show();
+						}
+					});
+				}
+			}
+		},
+		
+		// this function will schedule the opening of the tooltip after the delay, if there is one
+		_show: function() {
+			
+			var self = this;
+			
+			if (self.Status != 'shown' && self.Status != 'appearing') {
+				
+				if (self.options.delay) {
+					self.timerShow = setTimeout(function(){
+						
+						// for hover trigger, we check if the mouse is still over the proxy, otherwise we do not show anything
+						if (self.options.trigger == 'click' || (self.options.trigger == 'hover' && self.mouseIsOverProxy)) {
+							self._showNow();
+						}
+					}, self.options.delay);
+				}
+				else self._showNow();
+			}
+		},
+		
+		// this function will open the tooltip right away
+		_showNow: function(callback) {
+			
+			var self = this;
+			
+			// call our constructor custom function before continuing
+			self.options.functionBefore.call(self.$el, self.$el, function() {
+				
+				// continue only if the tooltip is enabled and has any content
+				if (self.enabled && self.Content !== null) {
+				
+					// save the method callback and cancel hide method callbacks
+					if (callback) self.callbacks.show.push(callback);
+					self.callbacks.hide = [];
+					
+					//get rid of any appearance timer
+					clearTimeout(self.timerShow);
+					self.timerShow = null;
+					clearTimeout(self.timerHide);
+					self.timerHide = null;
+					
+					// if we only want one tooltip open at a time, close all auto-closing tooltips currently open and not already disappearing
+					if (self.options.onlyOne) {
+						$('.tooltipstered').not(self.$el).each(function(i,el) {
+							
+							var $el = $(el),
+								nss = $el.data('tooltipster-ns');
+							
+							// iterate on all tooltips of the element
+							$.each(nss, function(i, ns){
+								var instance = $el.data(ns),
+									// we have to use the public methods here
+									s = instance.status(),
+									ac = instance.option('autoClose');
+								
+								if (s !== 'hidden' && s !== 'disappearing' && ac) {
+									instance.hide();
+								}
+							});
+						});
+					}
+					
+					var finish = function() {
+						self.Status = 'shown';
+						
+						// trigger any show method custom callbacks and reset them
+						$.each(self.callbacks.show, function(i,c) { c.call(self.$el); });
+						self.callbacks.show = [];
+					};
+					
+					// if this origin already has its tooltip open
+					if (self.Status !== 'hidden') {
+						
+						// the timer (if any) will start (or restart) right now
+						var extraTime = 0;
+						
+						// if it was disappearing, cancel that
+						if (self.Status === 'disappearing') {
+							
+							self.Status = 'appearing';
+							
+							if (supportsTransitions()) {
+								
+								self.$tooltip
+									.clearQueue()
+									.removeClass('tooltipster-dying')
+									.addClass('tooltipster-'+ self.options.animation +'-show');
+								
+								if (self.options.speed > 0) self.$tooltip.delay(self.options.speed);
+								
+								self.$tooltip.queue(finish);
+							}
+							else {
+								// in case the tooltip was currently fading out, bring it back to life
+								self.$tooltip
+									.stop()
+									.fadeIn(finish);
+							}
+						}
+						// if the tooltip is already open, we still need to trigger the method custom callback
+						else if(self.Status === 'shown') {
+							finish();
+						}
+					}
+					// if the tooltip isn't already open, open that sucker up!
+					else {
+						
+						self.Status = 'appearing';
+						
+						// the timer (if any) will start when the tooltip has fully appeared after its transition
+						var extraTime = self.options.speed;
+						
+						// disable horizontal scrollbar to keep overflowing tooltips from jacking with it and then restore it to its previous value
+						self.bodyOverflowX = $('body').css('overflow-x');
+						$('body').css('overflow-x', 'hidden');
+						
+						// get some other settings related to building the tooltip
+						var animation = 'tooltipster-' + self.options.animation,
+							animationSpeed = '-webkit-transition-duration: '+ self.options.speed +'ms; -webkit-animation-duration: '+ self.options.speed +'ms; -moz-transition-duration: '+ self.options.speed +'ms; -moz-animation-duration: '+ self.options.speed +'ms; -o-transition-duration: '+ self.options.speed +'ms; -o-animation-duration: '+ self.options.speed +'ms; -ms-transition-duration: '+ self.options.speed +'ms; -ms-animation-duration: '+ self.options.speed +'ms; transition-duration: '+ self.options.speed +'ms; animation-duration: '+ self.options.speed +'ms;',
+							minWidth = self.options.minWidth ? 'min-width:'+ Math.round(self.options.minWidth) +'px;' : '',
+							maxWidth = self.options.maxWidth ? 'max-width:'+ Math.round(self.options.maxWidth) +'px;' : '',
+							pointerEvents = self.options.interactive ? 'pointer-events: auto;' : '';
+						
+						// build the base of our tooltip
+						self.$tooltip = $('<div class="tooltipster-base '+ self.options.theme +'" style="'+ minWidth +' '+ maxWidth +' '+ pointerEvents +' '+ animationSpeed +'"><div class="tooltipster-content"></div></div>');
+						
+						// only add the animation class if the user has a browser that supports animations
+						if (supportsTransitions()) self.$tooltip.addClass(animation);
+						
+						// insert the content
+						self._content_insert();
+						
+						// attach
+						self.$tooltip.appendTo('body');
+						
+						// do all the crazy calculations and positioning
+						self.reposition();
+						
+						// call our custom callback since the content of the tooltip is now part of the DOM
+						self.options.functionReady.call(self.$el, self.$el, self.$tooltip);
+						
+						// animate in the tooltip
+						if (supportsTransitions()) {
+							
+							self.$tooltip.addClass(animation + '-show');
+							
+							if(self.options.speed > 0) self.$tooltip.delay(self.options.speed);
+							
+							self.$tooltip.queue(finish);
+						}
+						else {
+							self.$tooltip.css('display', 'none').fadeIn(self.options.speed, finish);
+						}
+						
+						// will check if our tooltip origin is removed while the tooltip is shown
+						self._interval_set();
+						
+						// reposition on scroll (otherwise position:fixed element's tooltips will move away form their origin) and on resize (in case position can/has to be changed)
+						$(window).on('scroll.'+ self.namespace +' resize.'+ self.namespace, function() {
+							self.reposition();
+						});
+						
+						// auto-close bindings
+						if (self.options.autoClose) {
+							
+							// in case a listener is already bound for autoclosing (mouse or touch, hover or click), unbind it first
+							$('body').off('.'+ self.namespace);
+							
+							// here we'll have to set different sets of bindings for both touch and mouse
+							if (self.options.trigger == 'hover') {
+								
+								// if the user touches the body, hide
+								if (deviceHasTouchCapability) {
+									// timeout 0 : explanation below in click section
+									setTimeout(function() {
+										// we don't want to bind on click here because the initial touchstart event has not yet triggered its click event, which is thus about to happen
+										$('body').on('touchstart.'+ self.namespace, function() {
+											self.hide();
+										});
+									}, 0);
+								}
+								
+								// if we have to allow interaction
+								if (self.options.interactive) {
+									
+									// touch events inside the tooltip must not close it
+									if (deviceHasTouchCapability) {
+										self.$tooltip.on('touchstart.'+ self.namespace, function(event) {
+											event.stopPropagation();
+										});
+									}
+									
+									// as for mouse interaction, we get rid of the tooltip only after the mouse has spent some time out of it
+									var tolerance = null;
+									
+									self.$elProxy.add(self.$tooltip)
+										// hide after some time out of the proxy and the tooltip
+										.on('mouseleave.'+ self.namespace + '-autoClose', function() {
+											clearTimeout(tolerance);
+											tolerance = setTimeout(function(){
+												self.hide();
+											}, self.options.interactiveTolerance);
+										})
+										// suspend timeout when the mouse is over the proxy or the tooltip
+										.on('mouseenter.'+ self.namespace + '-autoClose', function() {
+											clearTimeout(tolerance);
+										});
+								}
+								// if this is a non-interactive tooltip, get rid of it if the mouse leaves
+								else {
+									self.$elProxy.on('mouseleave.'+ self.namespace + '-autoClose', function() {
+										self.hide();
+									});
+								}
+								
+								// close the tooltip when the proxy gets a click (common behavior of native tooltips)
+								if (self.options.hideOnClick) {
+									
+									self.$elProxy.on('click.'+ self.namespace + '-autoClose', function() {
+										self.hide();
+									});
+								}
+							}
+							// here we'll set the same bindings for both clicks and touch on the body to hide the tooltip
+							else if(self.options.trigger == 'click'){
+								
+								// use a timeout to prevent immediate closing if the method was called on a click event and if options.delay == 0 (because of bubbling)
+								setTimeout(function() {
+									$('body').on('click.'+ self.namespace +' touchstart.'+ self.namespace, function() {
+										self.hide();
+									});
+								}, 0);
+								
+								// if interactive, we'll stop the events that were emitted from inside the tooltip to stop autoClosing
+								if (self.options.interactive) {
+									
+									// note : the touch events will just not be used if the plugin is not enabled on touch devices
+									self.$tooltip.on('click.'+ self.namespace +' touchstart.'+ self.namespace, function(event) {
+										event.stopPropagation();
+									});
+								}
+							}
+						}
+					}
+					
+					// if we have a timer set, let the countdown begin
+					if (self.options.timer > 0) {
+						
+						self.timerHide = setTimeout(function() {
+							self.timerHide = null;
+							self.hide();
+						}, self.options.timer + extraTime);
+					}
+				}
+			});
+		},
+		
+		_interval_set: function() {
+			
+			var self = this;
+			
+			self.checkInterval = setInterval(function() {
+				
+				// if the tooltip and/or its interval should be stopped
+				if (
+						// if the origin has been removed
+						$('body').find(self.$el).length === 0
+						// if the elProxy has been removed
+					||	$('body').find(self.$elProxy).length === 0
+						// if the tooltip has been closed
+					||	self.Status == 'hidden'
+						// if the tooltip has somehow been removed
+					||	$('body').find(self.$tooltip).length === 0
+				) {
+					// remove the tooltip if it's still here
+					if (self.Status == 'shown' || self.Status == 'appearing') self.hide();
+					
+					// clear this interval as it is no longer necessary
+					self._interval_cancel();
+				}
+				// if everything is alright
+				else {
+					// compare the former and current positions of the elProxy to reposition the tooltip if need be
+					if(self.options.positionTracker){
+						
+						var p = self._repositionInfo(self.$elProxy),
+							identical = false;
+						
+						// compare size first (a change requires repositioning too)
+						if(areEqual(p.dimension, self.elProxyPosition.dimension)){
+							
+							// for elements with a fixed position, we track the top and left properties (relative to window)
+							if(self.$elProxy.css('position') === 'fixed'){
+								if(areEqual(p.position, self.elProxyPosition.position)) identical = true;
+							}
+							// otherwise, track total offset (relative to document)
+							else {
+								if(areEqual(p.offset, self.elProxyPosition.offset)) identical = true;
+							}
+						}
+						
+						if(!identical){
+							self.reposition();
+							self.options.positionTrackerCallback.call(self, self.$el);
+						}
+					}
+				}
+			}, 200);
+		},
+		
+		_interval_cancel: function() {
+			clearInterval(this.checkInterval);
+			// clean delete
+			this.checkInterval = null;
+		},
+		
+		_content_set: function(content) {
+			// clone if asked. Cloning the object makes sure that each instance has its own version of the content (in case a same object were provided for several instances)
+			// reminder : typeof null === object
+			if (typeof content === 'object' && content !== null && this.options.contentCloning) {
+				content = content.clone(true);
+			}
+			this.Content = content;
+		},
+		
+		_content_insert: function() {
+			
+			var self = this,
+				$d = this.$tooltip.find('.tooltipster-content');
+			
+			if (typeof self.Content === 'string' && !self.options.contentAsHTML) {
+				$d.text(self.Content);
+			}
+			else {
+				$d
+					.empty()
+					.append(self.Content);
+			}
+		},
+		
+		_update: function(content) {
+			
+			var self = this;
+			
+			// change the content
+			self._content_set(content);
+			
+			if (self.Content !== null) {
+				
+				// update the tooltip if it is open
+				if (self.Status !== 'hidden') {
+					
+					// reset the content in the tooltip
+					self._content_insert();
+					
+					// reposition and resize the tooltip
+					self.reposition();
+					
+					// if we want to play a little animation showing the content changed
+					if (self.options.updateAnimation) {
+						
+						if (supportsTransitions()) {
+							
+							self.$tooltip.css({
+								'width': '',
+								'-webkit-transition': 'all ' + self.options.speed + 'ms, width 0ms, height 0ms, left 0ms, top 0ms',
+								'-moz-transition': 'all ' + self.options.speed + 'ms, width 0ms, height 0ms, left 0ms, top 0ms',
+								'-o-transition': 'all ' + self.options.speed + 'ms, width 0ms, height 0ms, left 0ms, top 0ms',
+								'-ms-transition': 'all ' + self.options.speed + 'ms, width 0ms, height 0ms, left 0ms, top 0ms',
+								'transition': 'all ' + self.options.speed + 'ms, width 0ms, height 0ms, left 0ms, top 0ms'
+							}).addClass('tooltipster-content-changing');
+							
+							// reset the CSS transitions and finish the change animation
+							setTimeout(function() {
+								
+								if(self.Status != 'hidden'){
+									
+									self.$tooltip.removeClass('tooltipster-content-changing');
+									
+									// after the changing animation has completed, reset the CSS transitions
+									setTimeout(function() {
+										
+										if(self.Status !== 'hidden'){
+											self.$tooltip.css({
+												'-webkit-transition': self.options.speed + 'ms',
+												'-moz-transition': self.options.speed + 'ms',
+												'-o-transition': self.options.speed + 'ms',
+												'-ms-transition': self.options.speed + 'ms',
+												'transition': self.options.speed + 'ms'
+											});
+										}
+									}, self.options.speed);
+								}
+							}, self.options.speed);
+						}
+						else {
+							self.$tooltip.fadeTo(self.options.speed, 0.5, function() {
+								if(self.Status != 'hidden'){
+									self.$tooltip.fadeTo(self.options.speed, 1);
+								}
+							});
+						}
+					}
+				}
+			}
+			else {
+				self.hide();
+			}
+		},
+		
+		_repositionInfo: function($el) {
+			return {
+				dimension: {
+					height: $el.outerHeight(false),
+					width: $el.outerWidth(false)
+				},
+				offset: $el.offset(),
+				position: {
+					left: parseInt($el.css('left')),
+					top: parseInt($el.css('top'))
+				}
+			};
+		},
+		
+		hide: function(callback) {
+			
+			var self = this;
+			
+			// save the method custom callback and cancel any show method custom callbacks
+			if (callback) self.callbacks.hide.push(callback);
+			self.callbacks.show = [];
+			
+			// get rid of any appearance timeout
+			clearTimeout(self.timerShow);
+			self.timerShow = null;
+			clearTimeout(self.timerHide);
+			self.timerHide = null;
+			
+			var finishCallbacks = function() {
+				// trigger any hide method custom callbacks and reset them
+				$.each(self.callbacks.hide, function(i,c) { c.call(self.$el); });
+				self.callbacks.hide = [];
+			};
+			
+			// hide
+			if (self.Status == 'shown' || self.Status == 'appearing') {
+				
+				self.Status = 'disappearing';
+				
+				var finish = function() {
+					
+					self.Status = 'hidden';
+					
+					// detach our content object first, so the next jQuery's remove() call does not unbind its event handlers
+					if (typeof self.Content == 'object' && self.Content !== null) {
+						self.Content.detach();
+					}
+					
+					self.$tooltip.remove();
+					self.$tooltip = null;
+					
+					// unbind orientationchange, scroll and resize listeners
+					$(window).off('.'+ self.namespace);
+					
+					$('body')
+						// unbind any auto-closing click/touch listeners
+						.off('.'+ self.namespace)
+						.css('overflow-x', self.bodyOverflowX);
+					
+					// unbind any auto-closing click/touch listeners
+					$('body').off('.'+ self.namespace);
+					
+					// unbind any auto-closing hover listeners
+					self.$elProxy.off('.'+ self.namespace + '-autoClose');
+					
+					// call our constructor custom callback function
+					self.options.functionAfter.call(self.$el, self.$el);
+					
+					// call our method custom callbacks functions
+					finishCallbacks();
+				};
+				
+				if (supportsTransitions()) {
+					
+					self.$tooltip
+						.clearQueue()
+						.removeClass('tooltipster-' + self.options.animation + '-show')
+						// for transitions only
+						.addClass('tooltipster-dying');
+					
+					if(self.options.speed > 0) self.$tooltip.delay(self.options.speed);
+					
+					self.$tooltip.queue(finish);
+				}
+				else {
+					self.$tooltip
+						.stop()
+						.fadeOut(self.options.speed, finish);
+				}
+			}
+			// if the tooltip is already hidden, we still need to trigger the method custom callback
+			else if(self.Status == 'hidden') {
+				finishCallbacks();
+			}
+			
+			return self;
+		},
+		
+		// the public show() method is actually an alias for the private showNow() method
+		show: function(callback) {
+			this._showNow(callback);
+			return this;
+		},
+		
+		// 'update' is deprecated in favor of 'content' but is kept for backward compatibility
+		update: function(c) {
+			return this.content(c);
+		},
+		content: function(c) {
+			// getter method
+			if(typeof c === 'undefined'){
+				return this.Content;
+			}
+			// setter method
+			else {
+				this._update(c);
+				return this;
+			}
+		},
+		
+		reposition: function() {
+			
+			var self = this;
+			
+			// in case the tooltip has been removed from DOM manually
+			if ($('body').find(self.$tooltip).length !== 0) {
+				
+				// reset width
+				self.$tooltip.css('width', '');
+				
+				// find variables to determine placement
+				self.elProxyPosition = self._repositionInfo(self.$elProxy);
+				var arrowReposition = null,
+					windowWidth = $(window).width(),
+					// shorthand
+					proxy = self.elProxyPosition,
+					tooltipWidth = self.$tooltip.outerWidth(false),
+					tooltipInnerWidth = self.$tooltip.innerWidth() + 1, // this +1 stops FireFox from sometimes forcing an additional text line
+					tooltipHeight = self.$tooltip.outerHeight(false);
+				
+				// if this is an <area> tag inside a <map>, all hell breaks loose. Recalculate all the measurements based on coordinates
+				if (self.$elProxy.is('area')) {
+					var areaShape = self.$elProxy.attr('shape'),
+						mapName = self.$elProxy.parent().attr('name'),
+						map = $('img[usemap="#'+ mapName +'"]'),
+						mapOffsetLeft = map.offset().left,
+						mapOffsetTop = map.offset().top,
+						areaMeasurements = self.$elProxy.attr('coords') !== undefined ? self.$elProxy.attr('coords').split(',') : undefined;
+					
+					if (areaShape == 'circle') {
+						var areaLeft = parseInt(areaMeasurements[0]),
+							areaTop = parseInt(areaMeasurements[1]),
+							areaWidth = parseInt(areaMeasurements[2]);
+						proxy.dimension.height = areaWidth * 2;
+						proxy.dimension.width = areaWidth * 2;
+						proxy.offset.top = mapOffsetTop + areaTop - areaWidth;
+						proxy.offset.left = mapOffsetLeft + areaLeft - areaWidth;
+					}
+					else if (areaShape == 'rect') {
+						var areaLeft = parseInt(areaMeasurements[0]),
+							areaTop = parseInt(areaMeasurements[1]),
+							areaRight = parseInt(areaMeasurements[2]),
+							areaBottom = parseInt(areaMeasurements[3]);
+						proxy.dimension.height = areaBottom - areaTop;
+						proxy.dimension.width = areaRight - areaLeft;
+						proxy.offset.top = mapOffsetTop + areaTop;
+						proxy.offset.left = mapOffsetLeft + areaLeft;
+					}
+					else if (areaShape == 'poly') {
+						var areaXs = [],
+							areaYs = [],
+							areaSmallestX = 0,
+							areaSmallestY = 0,
+							areaGreatestX = 0,
+							areaGreatestY = 0,
+							arrayAlternate = 'even';
+						
+						for (var i = 0; i < areaMeasurements.length; i++) {
+							var areaNumber = parseInt(areaMeasurements[i]);
+							
+							if (arrayAlternate == 'even') {
+								if (areaNumber > areaGreatestX) {
+									areaGreatestX = areaNumber;
+									if (i === 0) {
+										areaSmallestX = areaGreatestX;
+									}
+								}
+								
+								if (areaNumber < areaSmallestX) {
+									areaSmallestX = areaNumber;
+								}
+								
+								arrayAlternate = 'odd';
+							}
+							else {
+								if (areaNumber > areaGreatestY) {
+									areaGreatestY = areaNumber;
+									if (i == 1) {
+										areaSmallestY = areaGreatestY;
+									}
+								}
+								
+								if (areaNumber < areaSmallestY) {
+									areaSmallestY = areaNumber;
+								}
+								
+								arrayAlternate = 'even';
+							}
+						}
+					
+						proxy.dimension.height = areaGreatestY - areaSmallestY;
+						proxy.dimension.width = areaGreatestX - areaSmallestX;
+						proxy.offset.top = mapOffsetTop + areaSmallestY;
+						proxy.offset.left = mapOffsetLeft + areaSmallestX;
+					}
+					else {
+						proxy.dimension.height = map.outerHeight(false);
+						proxy.dimension.width = map.outerWidth(false);
+						proxy.offset.top = mapOffsetTop;
+						proxy.offset.left = mapOffsetLeft;
+					}
+				}
+				
+				// our function and global vars for positioning our tooltip
+				var myLeft = 0,
+					myLeftMirror = 0,
+					myTop = 0,
+					offsetY = parseInt(self.options.offsetY),
+					offsetX = parseInt(self.options.offsetX),
+					// this is the arrow position that will eventually be used. It may differ from the position option if the tooltip cannot be displayed in this position
+					practicalPosition = self.options.position;
+				
+				// a function to detect if the tooltip is going off the screen horizontally. If so, reposition the crap out of it!
+				function dontGoOffScreenX() {
+				
+					var windowLeft = $(window).scrollLeft();
+					
+					// if the tooltip goes off the left side of the screen, line it up with the left side of the window
+					if((myLeft - windowLeft) < 0) {
+						arrowReposition = myLeft - windowLeft;
+						myLeft = windowLeft;
+					}
+					
+					// if the tooltip goes off the right of the screen, line it up with the right side of the window
+					if (((myLeft + tooltipWidth) - windowLeft) > windowWidth) {
+						arrowReposition = myLeft - ((windowWidth + windowLeft) - tooltipWidth);
+						myLeft = (windowWidth + windowLeft) - tooltipWidth;
+					}
+				}
+				
+				// a function to detect if the tooltip is going off the screen vertically. If so, switch to the opposite!
+				function dontGoOffScreenY(switchTo, switchFrom) {
+					// if it goes off the top off the page
+					if(((proxy.offset.top - $(window).scrollTop() - tooltipHeight - offsetY - 12) < 0) && (switchFrom.indexOf('top') > -1)) {
+						practicalPosition = switchTo;
+					}
+					
+					// if it goes off the bottom of the page
+					if (((proxy.offset.top + proxy.dimension.height + tooltipHeight + 12 + offsetY) > ($(window).scrollTop() + $(window).height())) && (switchFrom.indexOf('bottom') > -1)) {
+						practicalPosition = switchTo;
+						myTop = (proxy.offset.top - tooltipHeight) - offsetY - 12;
+					}
+				}
+				
+				if(practicalPosition == 'top') {
+					var leftDifference = (proxy.offset.left + tooltipWidth) - (proxy.offset.left + proxy.dimension.width);
+					myLeft = (proxy.offset.left + offsetX) - (leftDifference / 2);
+					myTop = (proxy.offset.top - tooltipHeight) - offsetY - 12;
+					dontGoOffScreenX();
+					dontGoOffScreenY('bottom', 'top');
+				}
+				
+				if(practicalPosition == 'top-left') {
+					myLeft = proxy.offset.left + offsetX;
+					myTop = (proxy.offset.top - tooltipHeight) - offsetY - 12;
+					dontGoOffScreenX();
+					dontGoOffScreenY('bottom-left', 'top-left');
+				}
+				
+				if(practicalPosition == 'top-right') {
+					myLeft = (proxy.offset.left + proxy.dimension.width + offsetX) - tooltipWidth;
+					myTop = (proxy.offset.top - tooltipHeight) - offsetY - 12;
+					dontGoOffScreenX();
+					dontGoOffScreenY('bottom-right', 'top-right');
+				}
+				
+				if(practicalPosition == 'bottom') {
+					var leftDifference = (proxy.offset.left + tooltipWidth) - (proxy.offset.left + proxy.dimension.width);
+					myLeft = proxy.offset.left - (leftDifference / 2) + offsetX;
+					myTop = (proxy.offset.top + proxy.dimension.height) + offsetY + 12;
+					dontGoOffScreenX();
+					dontGoOffScreenY('top', 'bottom');
+				}
+				
+				if(practicalPosition == 'bottom-left') {
+					myLeft = proxy.offset.left + offsetX;
+					myTop = (proxy.offset.top + proxy.dimension.height) + offsetY + 12;
+					dontGoOffScreenX();
+					dontGoOffScreenY('top-left', 'bottom-left');
+				}
+				
+				if(practicalPosition == 'bottom-right') {
+					myLeft = (proxy.offset.left + proxy.dimension.width + offsetX) - tooltipWidth;
+					myTop = (proxy.offset.top + proxy.dimension.height) + offsetY + 12;
+					dontGoOffScreenX();
+					dontGoOffScreenY('top-right', 'bottom-right');
+				}
+				
+				if(practicalPosition == 'left') {
+					myLeft = proxy.offset.left - offsetX - tooltipWidth - 12;
+					myLeftMirror = proxy.offset.left + offsetX + proxy.dimension.width + 12;
+					var topDifference = (proxy.offset.top + tooltipHeight) - (proxy.offset.top + proxy.dimension.height);
+					myTop = proxy.offset.top - (topDifference / 2) - offsetY;
+					
+					// if the tooltip goes off boths sides of the page
+					if((myLeft < 0) && ((myLeftMirror + tooltipWidth) > windowWidth)) {
+						var borderWidth = parseFloat(self.$tooltip.css('border-width')) * 2,
+							newWidth = (tooltipWidth + myLeft) - borderWidth;
+						self.$tooltip.css('width', newWidth + 'px');
+						
+						tooltipHeight = self.$tooltip.outerHeight(false);
+						myLeft = proxy.offset.left - offsetX - newWidth - 12 - borderWidth;
+						topDifference = (proxy.offset.top + tooltipHeight) - (proxy.offset.top + proxy.dimension.height);
+						myTop = proxy.offset.top - (topDifference / 2) - offsetY;
+					}
+					
+					// if it only goes off one side, flip it to the other side
+					else if(myLeft < 0) {
+						myLeft = proxy.offset.left + offsetX + proxy.dimension.width + 12;
+						arrowReposition = 'left';
+					}
+				}
+				
+				if(practicalPosition == 'right') {
+					myLeft = proxy.offset.left + offsetX + proxy.dimension.width + 12;
+					myLeftMirror = proxy.offset.left - offsetX - tooltipWidth - 12;
+					var topDifference = (proxy.offset.top + tooltipHeight) - (proxy.offset.top + proxy.dimension.height);
+					myTop = proxy.offset.top - (topDifference / 2) - offsetY;
+					
+					// if the tooltip goes off boths sides of the page
+					if(((myLeft + tooltipWidth) > windowWidth) && (myLeftMirror < 0)) {
+						var borderWidth = parseFloat(self.$tooltip.css('border-width')) * 2,
+							newWidth = (windowWidth - myLeft) - borderWidth;
+						self.$tooltip.css('width', newWidth + 'px');
+						
+						tooltipHeight = self.$tooltip.outerHeight(false);
+						topDifference = (proxy.offset.top + tooltipHeight) - (proxy.offset.top + proxy.dimension.height);
+						myTop = proxy.offset.top - (topDifference / 2) - offsetY;
+					}
+						
+					// if it only goes off one side, flip it to the other side
+					else if((myLeft + tooltipWidth) > windowWidth) {
+						myLeft = proxy.offset.left - offsetX - tooltipWidth - 12;
+						arrowReposition = 'right';
+					}
+				}
+				
+				// if arrow is set true, style it and append it
+				if (self.options.arrow) {
+	
+					var arrowClass = 'tooltipster-arrow-' + practicalPosition;
+					
+					// set color of the arrow
+					if(self.options.arrowColor.length < 1) {
+						var arrowColor = self.$tooltip.css('background-color');
+					}
+					else {
+						var arrowColor = self.options.arrowColor;
+					}
+					
+					// if the tooltip was going off the page and had to re-adjust, we need to update the arrow's position
+					if (!arrowReposition) {
+						arrowReposition = '';
+					}
+					else if (arrowReposition == 'left') {
+						arrowClass = 'tooltipster-arrow-right';
+						arrowReposition = '';
+					}
+					else if (arrowReposition == 'right') {
+						arrowClass = 'tooltipster-arrow-left';
+						arrowReposition = '';
+					}
+					else {
+						arrowReposition = 'left:'+ Math.round(arrowReposition) +'px;';
+					}
+					
+					// building the logic to create the border around the arrow of the tooltip
+					if ((practicalPosition == 'top') || (practicalPosition == 'top-left') || (practicalPosition == 'top-right')) {
+						var tooltipBorderWidth = parseFloat(self.$tooltip.css('border-bottom-width')),
+							tooltipBorderColor = self.$tooltip.css('border-bottom-color');
+					}
+					else if ((practicalPosition == 'bottom') || (practicalPosition == 'bottom-left') || (practicalPosition == 'bottom-right')) {
+						var tooltipBorderWidth = parseFloat(self.$tooltip.css('border-top-width')),
+							tooltipBorderColor = self.$tooltip.css('border-top-color');
+					}
+					else if (practicalPosition == 'left') {
+						var tooltipBorderWidth = parseFloat(self.$tooltip.css('border-right-width')),
+							tooltipBorderColor = self.$tooltip.css('border-right-color');
+					}
+					else if (practicalPosition == 'right') {
+						var tooltipBorderWidth = parseFloat(self.$tooltip.css('border-left-width')),
+							tooltipBorderColor = self.$tooltip.css('border-left-color');
+					}
+					else {
+						var tooltipBorderWidth = parseFloat(self.$tooltip.css('border-bottom-width')),
+							tooltipBorderColor = self.$tooltip.css('border-bottom-color');
+					}
+					
+					if (tooltipBorderWidth > 1) {
+						tooltipBorderWidth++;
+					}
+					
+					var arrowBorder = '';
+					if (tooltipBorderWidth !== 0) {
+						var arrowBorderSize = '',
+							arrowBorderColor = 'border-color: '+ tooltipBorderColor +';';
+						if (arrowClass.indexOf('bottom') !== -1) {
+							arrowBorderSize = 'margin-top: -'+ Math.round(tooltipBorderWidth) +'px;';
+						}
+						else if (arrowClass.indexOf('top') !== -1) {
+							arrowBorderSize = 'margin-bottom: -'+ Math.round(tooltipBorderWidth) +'px;';
+						}
+						else if (arrowClass.indexOf('left') !== -1) {
+							arrowBorderSize = 'margin-right: -'+ Math.round(tooltipBorderWidth) +'px;';
+						}
+						else if (arrowClass.indexOf('right') !== -1) {
+							arrowBorderSize = 'margin-left: -'+ Math.round(tooltipBorderWidth) +'px;';
+						}
+						arrowBorder = '<span class="tooltipster-arrow-border" style="'+ arrowBorderSize +' '+ arrowBorderColor +';"></span>';
+					}
+					
+					// if the arrow already exists, remove and replace it
+					self.$tooltip.find('.tooltipster-arrow').remove();
+					
+					// build out the arrow and append it		
+					var arrowConstruct = '<div class="'+ arrowClass +' tooltipster-arrow" style="'+ arrowReposition +'">'+ arrowBorder +'<span style="border-color:'+ arrowColor +';"></span></div>';
+					self.$tooltip.append(arrowConstruct);
+				}
+				
+				// position the tooltip
+				self.$tooltip.css({'top': Math.round(myTop) + 'px', 'left': Math.round(myLeft) + 'px'});
+			}
+			
+			return self;
+		},
+		
+		enable: function() {
+			this.enabled = true;
+			return this;
+		},
+		
+		disable: function() {
+			// hide first, in case the tooltip would not disappear on its own (autoClose false)
+			this.hide();
+			this.enabled = false;
+			return this;
+		},
+		
+		destroy: function() {
+			
+			var self = this;
+			
+			self.hide();
+			
+			// remove the icon, if any
+			if (self.$el[0] !== self.$elProxy[0]) {
+				self.$elProxy.remove();
+			}
+			
+			self.$el
+				.removeData(self.namespace)
+				.off('.'+ self.namespace);
+			
+			var ns = self.$el.data('tooltipster-ns');
+			
+			// if there are no more tooltips on this element
+			if(ns.length === 1){
+				
+				// optional restoration of a title attribute
+				var title = null;
+				if (self.options.restoration === 'previous'){
+					title = self.$el.data('tooltipster-initialTitle');
+				}
+				else if(self.options.restoration === 'current'){
+					
+					// old school technique to stringify when outerHTML is not supported
+					title =
+						(typeof self.Content === 'string') ?
+						self.Content :
+						$('<div></div>').append(self.Content).html();
+				}
+				
+				if (title) {
+					self.$el.attr('title', title);
+				}
+				
+				// final cleaning
+				self.$el
+					.removeClass('tooltipstered')
+					.removeData('tooltipster-ns')
+					.removeData('tooltipster-initialTitle');
+			}
+			else {
+				// remove the instance namespace from the list of namespaces of tooltips present on the element
+				ns = $.grep(ns, function(el, i){
+					return el !== self.namespace;
+				});
+				self.$el.data('tooltipster-ns', ns);
+			}
+			
+			return self;
+		},
+		
+		elementIcon: function() {
+			return (this.$el[0] !== this.$elProxy[0]) ? this.$elProxy[0] : undefined;
+		},
+		
+		elementTooltip: function() {
+			return this.$tooltip ? this.$tooltip[0] : undefined;
+		},
+		
+		// public methods but for internal use only
+		// getter if val is ommitted, setter otherwise
+		option: function(o, val) {
+			if (typeof val == 'undefined') return this.options[o];
+			else {
+				this.options[o] = val;
+				return this;
+			}
+		},
+		status: function() {
+			return this.Status;
+		}
+	};
+	
+	$.fn[pluginName] = function () {
+		
+		// for using in closures
+		var args = arguments;
+		
+		// if we are not in the context of jQuery wrapped HTML element(s) :
+		// this happens when calling static methods in the form $.fn.tooltipster('methodName'), or when calling $(sel).tooltipster('methodName or options') where $(sel) does not match anything
+		if (this.length === 0) {
+			
+			// if the first argument is a method name
+			if (typeof args[0] === 'string') {
+				
+				var methodIsStatic = true;
+				
+				// list static methods here (usable by calling $.fn.tooltipster('methodName');)
+				switch (args[0]) {
+					
+					case 'setDefaults':
+						// change default options for all future instances
+						$.extend(defaults, args[1]);
+						break;
+					
+					default:
+						methodIsStatic = false;
+						break;
+				}
+				
+				// $.fn.tooltipster('methodName') calls will return true
+				if (methodIsStatic) return true;
+				// $(sel).tooltipster('methodName') calls will return the list of objects event though it's empty because chaining should work on empty lists
+				else return this;
+			}
+			// the first argument is undefined or an object of options : we are initalizing but there is no element matched by selector
+			else {
+				// still chainable : same as above
+				return this;
+			}
+		}
+		// this happens when calling $(sel).tooltipster('methodName or options') where $(sel) matches one or more elements
+		else {
+			
+			// method calls
+			if (typeof args[0] === 'string') {
+				
+				var v = '#*$~&';
+				
+				this.each(function() {
+					
+					// retrieve the namepaces of the tooltip(s) that exist on that element. We will interact with the first tooltip only.
+					var ns = $(this).data('tooltipster-ns'),
+						// self represents the instance of the first tooltipster plugin associated to the current HTML object of the loop
+						self = ns ? $(this).data(ns[0]) : null;
+					
+					// if the current element holds a tooltipster instance
+					if (self) {
+						
+						if (typeof self[args[0]] === 'function') {
+							// note : args[1] and args[2] may not be defined
+							var resp = self[args[0]](args[1], args[2]);
+						}
+						else {
+							throw new Error('Unknown method .tooltipster("' + args[0] + '")');
+						}
+						
+						// if the function returned anything other than the instance itself (which implies chaining)
+						if (resp !== self){
+							v = resp;
+							// return false to stop .each iteration on the first element matched by the selector
+							return false;
+						}
+					}
+					else {
+						throw new Error('You called Tooltipster\'s "' + args[0] + '" method on an uninitialized element');
+					}
+				});
+				
+				return (v !== '#*$~&') ? v : this;
+			}
+			// first argument is undefined or an object : the tooltip is initializing
+			else {
+				
+				var instances = [],
+					// is there a defined value for the multiple option in the options object ?
+					multipleIsSet = args[0] && typeof args[0].multiple !== 'undefined',
+					// if the multiple option is set to true, or if it's not defined but set to true in the defaults
+					multiple = (multipleIsSet && args[0].multiple) || (!multipleIsSet && defaults.multiple),
+					// same for debug
+					debugIsSet = args[0] && typeof args[0].debug !== 'undefined',
+					debug = (debugIsSet && args[0].debug) || (!debugIsSet && defaults.debug);
+				
+				// initialize a tooltipster instance for each element if it doesn't already have one or if the multiple option is set, and attach the object to it
+				this.each(function () {
+					
+					var go = false,
+						ns = $(this).data('tooltipster-ns'),
+						instance = null;
+					
+					if (!ns) {
+						go = true;
+					}
+					else if (multiple) {
+						go = true;
+					}
+					else if (debug) {
+						console.log('Tooltipster: one or more tooltips are already attached to this element: ignoring. Use the "multiple" option to attach more tooltips.');
+					}
+					
+					if (go) {
+						instance = new Plugin(this, args[0]);
+						
+						// save the reference of the new instance
+						if (!ns) ns = [];
+						ns.push(instance.namespace);
+						$(this).data('tooltipster-ns', ns)
+						
+						// save the instance itself
+						$(this).data(instance.namespace, instance);
+					}
+					
+					instances.push(instance);
+				});
+				
+				if (multiple) return instances;
+				else return this;
+			}
+		}
+	};
+	
+	// quick & dirty compare function (not bijective nor multidimensional)
+	function areEqual(a,b) {
+		var same = true;
+		$.each(a, function(i, el){
+			if(typeof b[i] === 'undefined' || a[i] !== b[i]){
+				same = false;
+				return false;
+			}
+		});
+		return same;
+	}
+	
+	// detect if this device can trigger touch events
+	var deviceHasTouchCapability = !!('ontouchstart' in window);
+	
+	// we'll assume the device has no mouse until we detect any mouse movement
+	var deviceHasMouse = false;
+	$('body').one('mousemove', function() {
+		deviceHasMouse = true;
+	});
+	
+	function deviceIsPureTouch() {
+		return (!deviceHasMouse && deviceHasTouchCapability);
+	}
+	
+	// detecting support for CSS transitions
+	function supportsTransitions() {
+		var b = document.body || document.documentElement,
+			s = b.style,
+			p = 'transition';
+		
+		if(typeof s[p] == 'string') {return true; }
+
+		v = ['Moz', 'Webkit', 'Khtml', 'O', 'ms'],
+		p = p.charAt(0).toUpperCase() + p.substr(1);
+		for(var i=0; i<v.length; i++) {
+			if(typeof s[v[i] + p] == 'string') { return true; }
+		}
+		return false;
+	}
+})( jQuery, window, document );


[05/23] allura git commit: [#7884] ticket:805 Fix test

Posted by he...@apache.org.
[#7884] ticket:805 Fix test


Project: http://git-wip-us.apache.org/repos/asf/allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/allura/commit/cddde562
Tree: http://git-wip-us.apache.org/repos/asf/allura/tree/cddde562
Diff: http://git-wip-us.apache.org/repos/asf/allura/diff/cddde562

Branch: refs/heads/hs/7894
Commit: cddde5627f636b0b6b6322dfefad49e060703701
Parents: cbd25fb
Author: Igor Bondarenko <je...@gmail.com>
Authored: Wed Jun 17 17:37:36 2015 +0300
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Mon Jun 29 18:04:52 2015 +0000

----------------------------------------------------------------------
 Allura/allura/tests/functional/test_admin.py | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/allura/blob/cddde562/Allura/allura/tests/functional/test_admin.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/functional/test_admin.py b/Allura/allura/tests/functional/test_admin.py
index bd7f373..28931c0 100644
--- a/Allura/allura/tests/functional/test_admin.py
+++ b/Allura/allura/tests/functional/test_admin.py
@@ -170,8 +170,10 @@ class TestProjectAdmin(TestController):
                 'features-1.feature': '  ',
                 'features-2.feature': ' Two '})
         r = self.app.get('/admin/overview')
-        features = r.html.find('fieldset').findAll('input', {'type': 'text'})
-        assert_equals(len(features), 2+1)  # two features + extra empty input
+        features = r.html.find('div', {'id': 'features'})
+        features = features.findAll('input', {'type': 'text'})
+        # two features + extra empty input + stub hidden input for js
+        assert_equals(len(features), 2+1+1)
         assert_equals(features[0]['value'], u'One')
         assert_equals(features[1]['value'], u'Two')
         proj = M.Project.query.get(shortname='test')


[14/23] allura git commit: [#7885] Show tooltip on focus

Posted by he...@apache.org.
[#7885] Show tooltip on focus


Project: http://git-wip-us.apache.org/repos/asf/allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/allura/commit/3f47be12
Tree: http://git-wip-us.apache.org/repos/asf/allura/tree/3f47be12
Diff: http://git-wip-us.apache.org/repos/asf/allura/diff/3f47be12

Branch: refs/heads/hs/7894
Commit: 3f47be12981aadd13b48e5f83d7925bf6f288b09
Parents: 3cda474
Author: Heith Seewald <hs...@slashdotmedia.com>
Authored: Thu Jun 25 18:03:46 2015 -0400
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Mon Jun 29 18:25:54 2015 +0000

----------------------------------------------------------------------
 Allura/allura/templates/jinja_master/master.html | 4 ++++
 1 file changed, 4 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/allura/blob/3f47be12/Allura/allura/templates/jinja_master/master.html
----------------------------------------------------------------------
diff --git a/Allura/allura/templates/jinja_master/master.html b/Allura/allura/templates/jinja_master/master.html
index 74b09bc..69979ae 100644
--- a/Allura/allura/templates/jinja_master/master.html
+++ b/Allura/allura/templates/jinja_master/master.html
@@ -178,6 +178,10 @@
             position: 'right',
             iconCloning: false,
             maxWidth: 300
+        }).focus(function () {
+            $(this).tooltipster('show');
+        }).blur(function () {
+            $(this).tooltipster('hide');
         });
     })
 </script>


[15/23] allura git commit: [#7803] ticket:810 Updated taskd_cleanup to search for right process name

Posted by he...@apache.org.
[#7803] ticket:810 Updated taskd_cleanup to search for right process name


Project: http://git-wip-us.apache.org/repos/asf/allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/allura/commit/6b7a5fde
Tree: http://git-wip-us.apache.org/repos/asf/allura/tree/6b7a5fde
Diff: http://git-wip-us.apache.org/repos/asf/allura/diff/6b7a5fde

Branch: refs/heads/hs/7894
Commit: 6b7a5fdedf5dc2379c6de09180830887ec7e23c3
Parents: 6ba4a43
Author: Aleksey 'LXj' Alekseyev <go...@gmail.com>
Authored: Sun Jun 28 13:44:59 2015 +0300
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Mon Jun 29 20:26:56 2015 +0000

----------------------------------------------------------------------
 Allura/allura/command/taskd_cleanup.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/allura/blob/6b7a5fde/Allura/allura/command/taskd_cleanup.py
----------------------------------------------------------------------
diff --git a/Allura/allura/command/taskd_cleanup.py b/Allura/allura/command/taskd_cleanup.py
index 517917f..99caa80 100644
--- a/Allura/allura/command/taskd_cleanup.py
+++ b/Allura/allura/command/taskd_cleanup.py
@@ -129,7 +129,7 @@ class TaskdCleanupCommand(base.Command):
 
     def _taskd_pids(self):
         # space after "taskd" to ensure no match on taskd_cleanup (ourself)
-        p = subprocess.Popen(['pgrep', '-f', '/paster taskd '],
+        p = subprocess.Popen(['pgrep', '-f', '^taskd '],
                              stdout=subprocess.PIPE,
                              stderr=subprocess.PIPE)
         stdout, stderr = p.communicate()


[04/23] allura git commit: [#7884] Add "Project Features" functionality

Posted by he...@apache.org.
[#7884] Add "Project Features" functionality


Project: http://git-wip-us.apache.org/repos/asf/allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/allura/commit/3bbf0f6d
Tree: http://git-wip-us.apache.org/repos/asf/allura/tree/3bbf0f6d
Diff: http://git-wip-us.apache.org/repos/asf/allura/diff/3bbf0f6d

Branch: refs/heads/hs/7894
Commit: 3bbf0f6de6dfcbbf33a9c70486b917ebac57ce10
Parents: fbb8da9
Author: Igor Bondarenko <je...@gmail.com>
Authored: Thu Jun 11 15:08:04 2015 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Mon Jun 29 18:04:51 2015 +0000

----------------------------------------------------------------------
 Allura/allura/ext/admin/admin_main.py           | 13 ++++++++
 .../templates/admin_widgets/features_field.html | 32 ++++++++++++++++++++
 .../templates/admin_widgets/metadata_admin.html |  7 ++++-
 .../ext/admin/templates/project_overview.html   |  2 +-
 Allura/allura/ext/admin/widgets.py              | 18 +++++++++++
 Allura/allura/lib/helpers.py                    | 12 ++++++++
 Allura/allura/model/project.py                  |  1 +
 Allura/allura/tests/functional/test_admin.py    | 16 ++++++++++
 8 files changed, 99 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/allura/blob/3bbf0f6d/Allura/allura/ext/admin/admin_main.py
----------------------------------------------------------------------
diff --git a/Allura/allura/ext/admin/admin_main.py b/Allura/allura/ext/admin/admin_main.py
index 46cdf9c..5bde57c 100644
--- a/Allura/allura/ext/admin/admin_main.py
+++ b/Allura/allura/ext/admin/admin_main.py
@@ -222,6 +222,10 @@ class ProjectAdminController(BaseController):
     def overview(self, **kw):
         c.markdown_editor = W.markdown_editor
         c.metadata_admin = W.metadata_admin
+        # need this because features field expects data in specific format
+        metadata_admin_value = h.fixed_attrs_proxy(
+            c.project,
+            features=[{'feature': f} for f in c.project.features])
         c.explain_export_modal = W.explain_export_modal
         show_export_control = asbool(config.get('show_export_control', False))
         allow_project_delete = asbool(config.get('allow_project_delete', True))
@@ -236,6 +240,7 @@ class ProjectAdminController(BaseController):
                 'please contact <a href="mailto:{contact}">{contact}</a>.'.format(contact=config['us_export_contact'])
         return dict(show_export_control=show_export_control,
                     allow_project_delete=allow_project_delete,
+                    metadata_admin_value=metadata_admin_value,
                     explain_export_text=explain_export_text)
 
     @without_trailing_slash
@@ -354,6 +359,7 @@ class ProjectAdminController(BaseController):
     @expose()
     @require_post()
     @validate(W.metadata_admin, error_handler=overview)
+    @h.vardec
     def update(self, name=None,
                short_description=None,
                summary='',
@@ -370,6 +376,7 @@ class ProjectAdminController(BaseController):
                export_controlled=False,
                export_control_type=None,
                tracking_id='',
+               features=None,
                **kw):
         require_access(c.project, 'update')
 
@@ -470,6 +477,12 @@ class ProjectAdminController(BaseController):
             h.log_action(log, 'change project tracking ID').info('')
             M.AuditLog.log('change project tracking ID to %s', tracking_id)
             c.project.tracking_id = tracking_id
+        features = [f['feature'].strip() for f in features or []
+                    if f.get('feature', '').strip()]
+        if features != c.project.features:
+            h.log_action(log, 'change project features').info('')
+            M.AuditLog.log('change project features to %s', features)
+            c.project.features = features
 
         if icon is not None and icon != '':
             if c.project.icon:

http://git-wip-us.apache.org/repos/asf/allura/blob/3bbf0f6d/Allura/allura/ext/admin/templates/admin_widgets/features_field.html
----------------------------------------------------------------------
diff --git a/Allura/allura/ext/admin/templates/admin_widgets/features_field.html b/Allura/allura/ext/admin/templates/admin_widgets/features_field.html
new file mode 100644
index 0000000..c5ef066
--- /dev/null
+++ b/Allura/allura/ext/admin/templates/admin_widgets/features_field.html
@@ -0,0 +1,32 @@
+{#-
+       Licensed to the Apache Software Foundation (ASF) under one
+       or more contributor license agreements.  See the NOTICE file
+       distributed with this work for additional information
+       regarding copyright ownership.  The ASF licenses this file
+       to you under the Apache License, Version 2.0 (the
+       "License"); you may not use this file except in compliance
+       with the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+       Unless required by applicable law or agreed to in writing,
+       software distributed under the License is distributed on an
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+       KIND, either express or implied.  See the License for the
+       specific language governing permissions and limitations
+       under the License.
+-#}
+
+<div class="{{css_class}}">
+  {% for f in widget.fields %}
+    {% set ctx=widget.context_for(f) %}
+    <div data-name="{{f.name}}">
+      {% if f.show_label %}
+        <div class="grid-4"><label>{{f.label}}:</label></div>
+        <div class="grid-14">{{f.display(**ctx)}}</div>
+      {% else %}
+        <div>{{f.display(**ctx)}}</div>
+      {% endif %}
+    </div>
+  {% endfor %}
+</div>

http://git-wip-us.apache.org/repos/asf/allura/blob/3bbf0f6d/Allura/allura/ext/admin/templates/admin_widgets/metadata_admin.html
----------------------------------------------------------------------
diff --git a/Allura/allura/ext/admin/templates/admin_widgets/metadata_admin.html b/Allura/allura/ext/admin/templates/admin_widgets/metadata_admin.html
index 16d1349..a9f9758 100644
--- a/Allura/allura/ext/admin/templates/admin_widgets/metadata_admin.html
+++ b/Allura/allura/ext/admin/templates/admin_widgets/metadata_admin.html
@@ -48,7 +48,12 @@
     {{widget.display_field(widget.fields.short_description) }}
 
     <div style="clear:both">&nbsp;</div>
-    
+
+    <fieldset class="preferences">
+      <legend>{{ widget.display_label(widget.fields.features) }}</legend>
+      {{ widget.display_field(widget.fields.features) }}
+    </fieldset>
+
     {% if tg.config.get('support_tool_choices') %}
     Preferred Support Page (for users of your project):<br>
     {% if c.form_errors.get('support_page_url') %}

http://git-wip-us.apache.org/repos/asf/allura/blob/3bbf0f6d/Allura/allura/ext/admin/templates/project_overview.html
----------------------------------------------------------------------
diff --git a/Allura/allura/ext/admin/templates/project_overview.html b/Allura/allura/ext/admin/templates/project_overview.html
index 3991241..90eca92 100644
--- a/Allura/allura/ext/admin/templates/project_overview.html
+++ b/Allura/allura/ext/admin/templates/project_overview.html
@@ -26,7 +26,7 @@
   {% if c.project.deleted %}
     <div class="notice">This project has been deleted and is not visible to non-admin users</div>
   {% endif %}
-  {{c.metadata_admin.display(value=c.project,
+  {{c.metadata_admin.display(value=metadata_admin_value,
                              show_export_control=show_export_control,
                              allow_project_delete=allow_project_delete)}}
 

http://git-wip-us.apache.org/repos/asf/allura/blob/3bbf0f6d/Allura/allura/ext/admin/widgets.py
----------------------------------------------------------------------
diff --git a/Allura/allura/ext/admin/widgets.py b/Allura/allura/ext/admin/widgets.py
index 5fb5bbd..4ea1fcd 100644
--- a/Allura/allura/ext/admin/widgets.py
+++ b/Allura/allura/ext/admin/widgets.py
@@ -155,6 +155,11 @@ class ScreenshotAdmin(ff.ForgeForm):
         return fields
 
 
+class FeaturesField(ew.CompoundField):
+    template = 'jinja:allura.ext.admin:templates/admin_widgets/features_field.html'
+    fields = [ew.TextField(name='feature', show_label=False)]
+
+
 class MetadataAdmin(ff.AdminForm):
     template = 'jinja:allura.ext.admin:templates/admin_widgets/metadata_admin.html'
     defaults = dict(
@@ -180,6 +185,19 @@ class MetadataAdmin(ff.AdminForm):
                                             fev.UnicodeString(max=1000),
                                             V.MaxBytesValidator(max=1000)),
                                         attrs=dict(title="Add a few paragraphs describing your project to new users."))
+        # Apparently, child field must be CompoundField with custom template
+        # for SortableRepeatedField to work properly, that's why FeaturesField
+        # is not just ew.TextField
+        features = ffw.SortableRepeatedField(
+            label='Features',
+            empty_msg='No features yet',
+            nonempty_msg='Drag and drop features to reorder. '
+                         'Leave empty to delete a feature.',
+            button=ew.InputField(
+                css_class='add',
+                field_type='button',
+                value='Add feature'),
+            field=FeaturesField())
         icon = ew.FileField(label='Icon')
         external_homepage = ew.InputField(field_type="text", label='Homepage',
                                           validator=fev.URL(add_http=True))

http://git-wip-us.apache.org/repos/asf/allura/blob/3bbf0f6d/Allura/allura/lib/helpers.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/helpers.py b/Allura/allura/lib/helpers.py
index 1c562f0..3a85c19 100644
--- a/Allura/allura/lib/helpers.py
+++ b/Allura/allura/lib/helpers.py
@@ -526,6 +526,18 @@ class proxy(object):
         return self._obj(*args, **kwargs)
 
 
+class fixed_attrs_proxy(proxy):
+    """
+    On attribute lookup, if keyword parameter matching attribute name was
+    provided during object construction, returns it's value. Otherwise proxies
+    to obj.
+    """
+    def __init__(self, obj, **kw):
+        self._obj = obj
+        for k, v in kw.iteritems():
+            setattr(self, k, v)
+
+
 def render_genshi_plaintext(template_name, **template_vars):
     assert os.path.exists(template_name)
     fd = open(template_name)

http://git-wip-us.apache.org/repos/asf/allura/blob/3bbf0f6d/Allura/allura/model/project.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/project.py b/Allura/allura/model/project.py
index 2b0566c..b45af77 100644
--- a/Allura/allura/model/project.py
+++ b/Allura/allura/model/project.py
@@ -241,6 +241,7 @@ class Project(SearchIndexable, MappedClass, ActivityNode, ActivityObject):
     trove_environment = FieldProperty([S.ObjectId])
     tracking_id = FieldProperty(str, if_missing='')
     is_nbhd_project = FieldProperty(bool, if_missing=False)
+    features = FieldProperty([str])
 
     # transient properties
     notifications_disabled = False

http://git-wip-us.apache.org/repos/asf/allura/blob/3bbf0f6d/Allura/allura/tests/functional/test_admin.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/functional/test_admin.py b/Allura/allura/tests/functional/test_admin.py
index d239591..bd7f373 100644
--- a/Allura/allura/tests/functional/test_admin.py
+++ b/Allura/allura/tests/functional/test_admin.py
@@ -161,6 +161,22 @@ class TestProjectAdmin(TestController):
         r = self.app.get('/admin/audit/')
         assert "uninstall tool test-tool" in r.body, r.body
 
+    def test_features(self):
+        proj = M.Project.query.get(shortname='test')
+        assert_equals(proj.features, [])
+        with audits(u"change project features to \[u'One', u'Two'\]"):
+            self.app.post('/admin/update', params={
+                'features-0.feature': 'One',
+                'features-1.feature': '  ',
+                'features-2.feature': ' Two '})
+        r = self.app.get('/admin/overview')
+        features = r.html.find('fieldset').findAll('input', {'type': 'text'})
+        assert_equals(len(features), 2+1)  # two features + extra empty input
+        assert_equals(features[0]['value'], u'One')
+        assert_equals(features[1]['value'], u'Two')
+        proj = M.Project.query.get(shortname='test')
+        assert_equals(proj.features, [u'One', u'Two'])
+
     def test_admin_export_control(self):
         self.app.get('/admin/')
         with audits('change project export controlled status to True'):