Browse Source

Advanced search

Rafał Pitoń 11 years ago
parent
commit
6ffa44fad5
70 changed files with 3099 additions and 185 deletions
  1. 51 21
      misago/apps/search/forms.py
  2. 4 1
      misago/apps/search/urls.py
  3. 189 77
      misago/apps/search/views.py
  4. 2 1
      misago/forms/__init__.py
  5. 12 1
      misago/forms/fields.py
  6. 6 18
      misago/forms/forms.py
  7. 23 7
      misago/search.py
  8. 5 1
      static/cranefly/css/cranefly.css
  9. 1 0
      static/cranefly/css/cranefly.less
  10. 30 6
      static/cranefly/css/cranefly/search.less
  11. 481 0
      static/cranefly/css/datepicker.css
  12. 0 1
      static/cranefly/css/style.css
  13. 1395 0
      static/cranefly/js/bootstrap-datepicker.js
  14. 15 0
      static/cranefly/js/locales/bootstrap-datepicker.ar.js
  15. 14 0
      static/cranefly/js/locales/bootstrap-datepicker.bg.js
  16. 14 0
      static/cranefly/js/locales/bootstrap-datepicker.ca.js
  17. 15 0
      static/cranefly/js/locales/bootstrap-datepicker.cs.js
  18. 14 0
      static/cranefly/js/locales/bootstrap-datepicker.cy.js
  19. 14 0
      static/cranefly/js/locales/bootstrap-datepicker.da.js
  20. 17 0
      static/cranefly/js/locales/bootstrap-datepicker.de.js
  21. 13 0
      static/cranefly/js/locales/bootstrap-datepicker.el.js
  22. 14 0
      static/cranefly/js/locales/bootstrap-datepicker.es.js
  23. 14 0
      static/cranefly/js/locales/bootstrap-datepicker.et.js
  24. 16 0
      static/cranefly/js/locales/bootstrap-datepicker.fi.js
  25. 17 0
      static/cranefly/js/locales/bootstrap-datepicker.fr.js
  26. 15 0
      static/cranefly/js/locales/bootstrap-datepicker.he.js
  27. 13 0
      static/cranefly/js/locales/bootstrap-datepicker.hr.js
  28. 16 0
      static/cranefly/js/locales/bootstrap-datepicker.hu.js
  29. 15 0
      static/cranefly/js/locales/bootstrap-datepicker.id.js
  30. 14 0
      static/cranefly/js/locales/bootstrap-datepicker.is.js
  31. 16 0
      static/cranefly/js/locales/bootstrap-datepicker.it.js
  32. 15 0
      static/cranefly/js/locales/bootstrap-datepicker.ja.js
  33. 15 0
      static/cranefly/js/locales/bootstrap-datepicker.ka.js
  34. 13 0
      static/cranefly/js/locales/bootstrap-datepicker.kr.js
  35. 16 0
      static/cranefly/js/locales/bootstrap-datepicker.lt.js
  36. 16 0
      static/cranefly/js/locales/bootstrap-datepicker.lv.js
  37. 14 0
      static/cranefly/js/locales/bootstrap-datepicker.mk.js
  38. 14 0
      static/cranefly/js/locales/bootstrap-datepicker.ms.js
  39. 14 0
      static/cranefly/js/locales/bootstrap-datepicker.nb.js
  40. 16 0
      static/cranefly/js/locales/bootstrap-datepicker.nl-BE.js
  41. 14 0
      static/cranefly/js/locales/bootstrap-datepicker.nl.js
  42. 15 0
      static/cranefly/js/locales/bootstrap-datepicker.no.js
  43. 15 0
      static/cranefly/js/locales/bootstrap-datepicker.pl.js
  44. 15 0
      static/cranefly/js/locales/bootstrap-datepicker.pt-BR.js
  45. 16 0
      static/cranefly/js/locales/bootstrap-datepicker.pt.js
  46. 15 0
      static/cranefly/js/locales/bootstrap-datepicker.ro.js
  47. 14 0
      static/cranefly/js/locales/bootstrap-datepicker.rs-latin.js
  48. 14 0
      static/cranefly/js/locales/bootstrap-datepicker.rs.js
  49. 15 0
      static/cranefly/js/locales/bootstrap-datepicker.ru.js
  50. 15 0
      static/cranefly/js/locales/bootstrap-datepicker.sk.js
  51. 14 0
      static/cranefly/js/locales/bootstrap-datepicker.sl.js
  52. 15 0
      static/cranefly/js/locales/bootstrap-datepicker.sq.js
  53. 16 0
      static/cranefly/js/locales/bootstrap-datepicker.sv.js
  54. 15 0
      static/cranefly/js/locales/bootstrap-datepicker.sw.js
  55. 14 0
      static/cranefly/js/locales/bootstrap-datepicker.th.js
  56. 16 0
      static/cranefly/js/locales/bootstrap-datepicker.tr.js
  57. 15 0
      static/cranefly/js/locales/bootstrap-datepicker.ua.js
  58. 14 0
      static/cranefly/js/locales/bootstrap-datepicker.uk.js
  59. 16 0
      static/cranefly/js/locales/bootstrap-datepicker.zh-CN.js
  60. 17 0
      static/cranefly/js/locales/bootstrap-datepicker.zh-TW.js
  61. 6 6
      templates/cranefly/layout.html
  62. 0 26
      templates/cranefly/macros.html
  63. 0 17
      templates/cranefly/search/home.html
  64. 15 0
      templates/cranefly/search/layout.html
  65. 10 2
      templates/cranefly/search/results.html
  66. 37 0
      templates/cranefly/search/search_form.html
  67. 47 0
      templates/cranefly/search/search_forums.html
  68. 39 0
      templates/cranefly/search/search_private_threads.html
  69. 41 0
      templates/cranefly/search/search_reports.html
  70. 6 0
      templates/forms.html

+ 51 - 21
misago/apps/search/forms.py

@@ -1,36 +1,29 @@
 from django.utils import timezone
 from django.utils import timezone
 from django.utils.translation import ungettext_lazy, ugettext_lazy as _
 from django.utils.translation import ungettext_lazy, ugettext_lazy as _
 import floppyforms as forms
 import floppyforms as forms
-from misago.forms import Form
+from misago.forms import Form, ForumMultipleChoiceField
+from misago.models import Forum
 
 
-class QuickSearchForm(Form):
-    search_query = forms.CharField(max_length=255, required=False)
+class SearchFormBase(Form):
+    search_query = forms.CharField(label=_("Search Phrases"), max_length=255)
+    search_thread_titles = forms.BooleanField(label=_("Search Thread Titles"), required=False)
+    search_thread = forms.CharField(label=_("Thread Name or Link"),
+                                    help_text=_("Limit search to specified thread by entering it's name or link here."),
+                                    max_length=255,
+                                    required=False)
+    search_author = forms.CharField(label=_("Author Name"),
+                                    help_text=_("Limit search to specified user by entering his or her name here."),
+                                    max_length=255,
+                                    required=False)
 
 
     def clean_search_query(self):
     def clean_search_query(self):
         data = self.cleaned_data['search_query']
         data = self.cleaned_data['search_query']
         if len(data) < 3:
         if len(data) < 3:
             raise forms.ValidationError(_("Search query should be at least 3 characters long."))
             raise forms.ValidationError(_("Search query should be at least 3 characters long."))
-
-        self.mode = None
-
-        if data[0:6].lower() == 'forum:':
-            forum_name = data[6:].strip()
-            if len(forum_name) < 2:
-                raise forms.ValidationError(_("In order to jump to forum, You have to enter full forum name or first few characters of it."))
-            self.mode = 'forum'
-            self.target = forum_name
-
-        if data[0:5].lower() == 'user:':
-            username = data[5:].strip()
-            if len(username) < 2:
-                raise forms.ValidationError(_("In order to jump to user profile, You have to enter full user name or first few characters of it."))
-            self.mode = 'user'
-            self.target = username
-
         return data
         return data
 
 
     def clean(self):
     def clean(self):
-        cleaned_data = super(QuickSearchForm, self).clean()
+        cleaned_data = super(SearchFormBase, self).clean()
         if self.request.user.is_authenticated():
         if self.request.user.is_authenticated():
             self.check_flood_user()
             self.check_flood_user()
         if self.request.user.is_anonymous():
         if self.request.user.is_anonymous():
@@ -73,3 +66,40 @@ class QuickSearchForm(Form):
                         })
                         })
 
 
 
 
+class QuickSearchForm(SearchFormBase):
+    pass
+
+
+class AdvancedSearchForm(SearchFormBase):
+    search_before = forms.DateField(label=_("Posted Before"),
+                                    help_text=_("Exclude posts made before specified date from search. Use YYYY-MM-DD format, for example 2013-11-23."),
+                                    required=False)
+    search_after = forms.DateField(label=_("Posted After"),
+                                   help_text=_("Exclude posts made after specified date from search. Use YYYY-MM-DD format, for example 2013-11-23."),
+                                   required=False)
+
+
+class ForumsSearchForm(AdvancedSearchForm):
+    def finalize_form(self):
+        self.add_field('search_forums', ForumMultipleChoiceField(label=_("Search Forums"),
+                                                                 help_text=_("If you want, you can limit search to specified forums."),
+                                                                 queryset=Forum.objects.get(special='root').get_descendants().filter(pk__in=self.request.acl.forums.acl['can_browse']),
+                                                                 required=False))
+        self.add_field('search_forums_childs', forms.BooleanField(label=_("Include Children Forums"), required=False))
+
+
+class PrivateThreadsSearchForm(AdvancedSearchForm):
+    pass
+
+
+class ReportsSearchForm(AdvancedSearchForm):
+    search_weight = forms.TypedMultipleChoiceField(label=_("Report Types"),
+                                                   help_text=_("Limit search to certain report types."),
+                                                   choices=(
+                                                            (2, _("Open")),
+                                                            (1, _("Resolved")),
+                                                            (0, _("Bogus")),
+                                                           ),
+                                                   coerce=int,
+                                                   widget=forms.CheckboxSelectMultiple,
+                                                   required=False)

+ 4 - 1
misago/apps/search/urls.py

@@ -1,7 +1,10 @@
 from django.conf.urls import patterns, url
 from django.conf.urls import patterns, url
 
 
 urlpatterns = patterns('misago.apps.search.views',
 urlpatterns = patterns('misago.apps.search.views',
-    url(r'^$', 'QuickSearchView', name="search"),
+    url(r'^$', 'SearchForumsView', name="search_forums"),
+    url(r'^quick/$', 'QuickSearchView', name="search_quick"),
+    url(r'^private-threads/$', 'SearchPrivateThreadsView', name="search_private_threads"),
+    url(r'^reports/$', 'SearchReportsView', name="search_reports"),
     url(r'^results/$', 'SearchResultsView', name="search_results"),
     url(r'^results/$', 'SearchResultsView', name="search_results"),
     url(r'^results/(?P<page>[1-9]([0-9]+)?)/$', 'SearchResultsView', name="search_results"),
     url(r'^results/(?P<page>[1-9]([0-9]+)?)/$', 'SearchResultsView', name="search_results"),
 )
 )

+ 189 - 77
misago/apps/search/views.py

@@ -1,4 +1,5 @@
-from django.core.urlresolvers import reverse
+from urlparse import urlparse
+from django.core.urlresolvers import reverse, resolve
 from django.http import Http404
 from django.http import Http404
 from django.shortcuts import redirect
 from django.shortcuts import redirect
 from django.template import RequestContext
 from django.template import RequestContext
@@ -9,46 +10,81 @@ from haystack.query import SearchQuerySet, RelatedSearchQuerySet
 from misago.acl.exceptions import ACLError403, ACLError404
 from misago.acl.exceptions import ACLError403, ACLError404
 from misago.conf import settings
 from misago.conf import settings
 from misago.decorators import block_crawlers
 from misago.decorators import block_crawlers
+from misago.messages import Message
 from misago.models import Forum, Thread, Post, User
 from misago.models import Forum, Thread, Post, User
 from misago.search import SearchException, MisagoSearchQuerySet
 from misago.search import SearchException, MisagoSearchQuerySet
 from misago.shortcuts import render_to_response
 from misago.shortcuts import render_to_response
 from misago.utils.pagination import make_pagination
 from misago.utils.pagination import make_pagination
 from misago.apps.errors import error403, error404
 from misago.apps.errors import error403, error404
 from misago.apps.profiles.views import list as users_list
 from misago.apps.profiles.views import list as users_list
-from misago.apps.search.forms import QuickSearchForm
+from misago.apps.search.forms import (QuickSearchForm, ForumsSearchForm,
+                                      PrivateThreadsSearchForm, ReportsSearchForm)
 
 
 class ViewBase(object):
 class ViewBase(object):
     search_route = 'search'
     search_route = 'search'
+    search_form = None
 
 
     def check_acl(self):
     def check_acl(self):
         pass
         pass
 
 
-    def make_query(self, search_query):
+    def make_query(self, search_data):
         try:
         try:
             sqs = MisagoSearchQuerySet(self.request.user, self.request.acl)
             sqs = MisagoSearchQuerySet(self.request.user, self.request.acl)
-
-            if self.request.POST.get('search_in') == 'thread':
-                thread_id = int(self.request.POST.get('search_thread'))
-                sqs.search_in(Thread.objects.get(id=thread_id))
-            elif self.request.POST.get('search_in') == 'private_threads':
-                sqs.search_in(Forum.objects.special_model('private_threads'))
-            elif self.request.POST.get('search_in') == 'reports':
-                sqs.search_in(Forum.objects.special_model('reports'))
+            if self.search_route == 'search_private_threads':
+                sqs.in_forums([Forum.objects.special_pk('private_threads')])
+            elif self.search_route == 'search_reports':
+                sqs.in_forums([Forum.objects.special_pk('reports')])
             else:
             else:
-                sqs.search_in(Forum.objects.special_model('root'))
+                if search_data.get('search_forums'):
+                    if search_data.get('search_forums_childs'):
+                        forums_tree = Forum.objects.forums_tree
+                        readable_forums = Forum.objects.readable_forums(self.request.acl)
+
+                        ranges = []
+                        for forum in search_data.get('search_forums'):
+                            if not ranges or ranges[-1][1] < forum.rght:
+                                ranges.append((forum.lft, forum.rght))
+
+                        forums = []
+                        for rang in ranges:
+                            for pk, forum in forums_tree.items():
+                                if pk in readable_forums:
+                                    if forum.lft >= rang[0] and forum.rght <= rang[1]:
+                                        forums.append(pk)
+                                    if forum.lft > rang[1]:
+                                        break
+
+                        sqs.in_forums(forums)
+                    else:
+                        sqs.in_forums([f.pk for f in search_data.get('search_forums')])
+                else:
+                    sqs.in_forums(Forum.objects.readable_forums(self.request.acl))
 
 
-            if self.request.POST.get('search_thread_titles'):
-                sqs.search_thread_name(search_query)
+            if search_data.get('search_thread_titles'):
+                sqs.search_thread_name(search_data.get('search_query'))
+                sqs.search_thread_titles()
             else:
             else:
-                sqs.search_content(search_query)
+                sqs.search_content(search_data.get('search_query'))
+
+            if search_data.get('search_thread'):
+                sqs.search_thread_name_link(search_data.get('search_thread'))
+
+            if search_data.get('search_author'):
+                sqs.search_user_name(search_data.get('search_author'))
+
+            if search_data.get('search_before'):
+                sqs.search_before(search_data.get('search_before'))
+
+            if search_data.get('search_after'):
+                sqs.search_after(search_data.get('search_after'))
 
 
-            if self.request.POST.get('search_author'):
-                sqs.search_user_name(search_query)
             return sqs
             return sqs
         except Thread.DoesNotExist:
         except Thread.DoesNotExist:
             raise ACLError404()
             raise ACLError404()
 
 
     def render_to_response(self, template, form, context):
     def render_to_response(self, template, form, context):
+        context['search_route'] = self.search_route
+        context['form'] = form
         for i in ('search_query', 'search_in', 'search_author', 'search_thread_titles'):
         for i in ('search_query', 'search_in', 'search_author', 'search_thread_titles'):
             if self.request.POST.get(i):
             if self.request.POST.get(i):
                 context[i] = self.request.POST.get(i)
                 context[i] = self.request.POST.get(i)
@@ -60,56 +96,61 @@ class ViewBase(object):
                                   context,
                                   context,
                                   context_instance=RequestContext(self.request))
                                   context_instance=RequestContext(self.request))
 
 
-    def __new__(cls, request, **kwargs):
-        obj = super(ViewBase, cls).__new__(cls)
-        return obj(request, **kwargs)
-
-    def __call__(self, request, **kwargs):
-        try:
-            if request.user.is_crawler():
-                raise ACLError404()
-            self.check_acl()
-            if not request.acl.search.can_search():
-                raise ACLError403(_("You don't have permission to search community."))
-            self.request = request
-            return self.call(**kwargs)
-        except ACLError403 as e:
-            return error403(request, unicode(e))
-        except ACLError404 as e:
-            return error404(request, unicode(e))
+    def draw_form(self, request):
+        search_form_data = self.request.session.get('search_form_data')
+        if search_form_data and search_form_data['form'] == self.search_route:
+            form = self.search_form(request=self.request, initial=search_form_data['data'])
+        else:
+            form = self.search_form(request=self.request)
+        return self.render_to_response(self.search_route, form,
+                                       {'search_result': self.request.session.get('search_results')})
 
 
+    def search(self, request):
+        self.request.session['search_form_data'] = None
+        message = None
 
 
-class QuickSearchView(ViewBase):
-    def call(self, **kwargs):
-        form_type = QuickSearchForm
-        if self.request.method != "POST":
-            form = QuickSearchForm(request=self.request)
-            return self.render_to_response('home', form,
-                                           {'search_result': self.request.session.get('search_results')})
+        # Hackish interception of quick search form
+        if self.search_route == 'search_quick':
+            if self.request.POST.get('search_in') == 'thread':
+                try:
+                    link = resolve(urlparse(self.request.POST.get('search_thread')).path)
+                    search_thread = Thread.objects.get(pk=link.kwargs['thread'])
+                    self.request.acl.threads.allow_thread_view(self.request.user, search_thread)
+                    if search_thread.forum_id == Forum.objects.special_pk('private_threads'):
+                        self.search_route = 'search_private_threads'
+                        self.search_form = PrivateThreadsSearchForm
+                    elif search_thread.forum_id == Forum.objects.special_pk('reports'):
+                        self.search_route = 'search_reports'
+                        self.search_form = ReportsSearchForm
+                    else:
+                        self.search_route = 'search_forums'
+                        self.search_form = ForumsSearchForm
+                except (Http404, KeyError, Thread.DoesNotExist):
+                    raise ACLError404()
+            elif self.request.POST.get('search_in') in ('forums', 'private_threads', 'reports'):
+                if self.request.POST.get('search_in') == 'forums':
+                    self.search_route = 'search_forums'
+                    self.search_form = ForumsSearchForm
+                elif self.request.POST.get('search_in') == 'private_threads':
+                    self.search_route = 'search_private_threads'
+                    self.search_form = PrivateThreadsSearchForm
+                elif self.request.POST.get('search_in') == 'reports':
+                    self.search_route = 'search_reports'
+                    self.search_form = ReportsSearchForm
 
 
+        form = self.search_form(self.request.POST, request=self.request)
         try:
         try:
-            form = QuickSearchForm(self.request.POST, request=self.request)
             if form.is_valid():
             if form.is_valid():
-                if form.mode == 'forum':
-                    jump_to = Forum.objects.forum_by_name(form.target, self.request.acl)
-                    if jump_to:
-                        if jump_to.level == 1:
-                            return redirect(reverse('index') + ('#%s' % jump_to.slug))
-                        return redirect(jump_to.url)
-                    else:
-                        raise SearchException(_('Forum "%(forum)s" could not be found.') % {'forum': form.target})
-                if form.mode == 'user':
-                    self.request.POST = self.request.POST.copy()
-                    self.request.POST['username'] = form.target
-                    return users_list(self.request)
-
-                sqs = self.make_query(form.cleaned_data['search_query']).query.load_all()[:120]
+                sqs = self.make_query(form.cleaned_data).query.load_all()[:120]
                 results = []
                 results = []
+                search_weight = form.cleaned_data.get('search_weight')
                 for p in sqs:
                 for p in sqs:
                     post = p.object
                     post = p.object
+                    if search_weight and post.thread.weight not in search_weight:
+                        continue
                     try:
                     try:
                         self.request.acl.threads.allow_post_view(self.request.user, post.thread, post)
                         self.request.acl.threads.allow_post_view(self.request.user, post.thread, post)
-                        results.append(post)
+                        results.append(post.pk)
                     except ACLError404:
                     except ACLError404:
                         pass
                         pass
 
 
@@ -119,11 +160,14 @@ class QuickSearchView(ViewBase):
                 if self.request.user.is_anonymous():
                 if self.request.user.is_anonymous():
                     self.request.session['last_search'] = timezone.now()
                     self.request.session['last_search'] = timezone.now()
 
 
+                self.request.session['search_form_data'] = {'form': self.search_route, 'data': form.cleaned_data}
+
                 if not results:
                 if not results:
                     raise SearchException(_("Search returned no results. Change search query and try again."))
                     raise SearchException(_("Search returned no results. Change search query and try again."))
 
 
                 self.request.session['search_results'] = {
                 self.request.session['search_results'] = {
                                                           'search_query': form.cleaned_data['search_query'],
                                                           'search_query': form.cleaned_data['search_query'],
+                                                          'search_route': self.search_route,
                                                           'search_in': self.request.POST.get('search_in'),
                                                           'search_in': self.request.POST.get('search_in'),
                                                           'search_author': self.request.POST.get('search_author'),
                                                           'search_author': self.request.POST.get('search_author'),
                                                           'search_thread_titles': self.request.POST.get('search_thread_titles'),
                                                           'search_thread_titles': self.request.POST.get('search_thread_titles'),
@@ -135,21 +179,88 @@ class QuickSearchView(ViewBase):
                     pass
                     pass
                 return redirect(reverse('search_results'))
                 return redirect(reverse('search_results'))
             else:
             else:
-                if 'search_query' in form.errors:
-                    raise SearchException(form.errors['search_query'][0])
                 raise SearchException(form.errors['__all__'][0])
                 raise SearchException(form.errors['__all__'][0])
         except SearchException as e:
         except SearchException as e:
-            return self.render_to_response('error', form,
-                                           {'message': unicode(e)})
+            message = Message(e)
+
+        return self.render_to_response(self.search_route, form,
+                                       {
+                                        'message': message,
+                                        'search_result': self.request.session.get('search_results')
+                                       })
 
 
 
 
-class SearchResultsView(ViewBase):
+    def __new__(cls, request, **kwargs):
+        obj = super(ViewBase, cls).__new__(cls)
+        return obj(request, **kwargs)
+
+    def __call__(self, request, **kwargs):
+        try:
+            self.request = request
+            if request.user.is_crawler():
+                raise ACLError404()
+            self.check_acl()
+            if not request.acl.search.can_search():
+                raise ACLError403(_("You don't have permission to search community."))
+            if self.request.method == "POST":
+                return self.search(request)
+            return self.draw_form(request)
+        except ACLError403 as e:
+            return error403(request, unicode(e))
+        except ACLError404 as e:
+            return error404(request, unicode(e))
+
+
+class QuickSearchView(ViewBase):
+    search_route = 'search_quick'
+    search_form = QuickSearchForm
+
+
+class SearchForumsView(ViewBase):
+    search_route = 'search_forums'
+    search_form = ForumsSearchForm
+
+
+class SearchPrivateThreadsView(ViewBase):
+    search_route = 'search_private_threads'
+    search_form = PrivateThreadsSearchForm
+
+    def check_acl(self):
+        if not self.request.acl.private_threads.can_participate():
+            raise ACLError404()
+
+
+class SearchReportsView(ViewBase):
+    search_route = 'search_reports'
+    search_form = ReportsSearchForm
+
+    def check_acl(self):
+        if not self.request.acl.reports.can_handle():
+            raise ACLError404()
+
+
+class SearchResultsView(object):
+    def __new__(cls, request, **kwargs):
+        obj = super(SearchResultsView, cls).__new__(cls)
+        return obj(request, **kwargs)
+
+    def __call__(self, request, **kwargs):
+        try:
+            if request.user.is_crawler():
+                raise ACLError404()
+            if not request.acl.search.can_search():
+                raise ACLError403(_("You don't have permission to search community."))
+            self.request = request
+            return self.call(**kwargs)
+        except ACLError403 as e:
+            return error403(request, unicode(e))
+        except ACLError404 as e:
+            return error404(request, unicode(e))
+
     def call(self, **kwargs):
     def call(self, **kwargs):
         result = self.request.session.get('search_results')
         result = self.request.session.get('search_results')
         if not result:
         if not result:
-            form = QuickSearchForm(request=self.request)
-            return self.render_to_response('error', form,
-                                           {'message': _("No search results were found.")})
+            return error404(self.request, _("No search results were found."))
 
 
         items = result['search_results']
         items = result['search_results']
         items_total = len(items);
         items_total = len(items);
@@ -158,15 +269,16 @@ class SearchResultsView(ViewBase):
         except Http404:
         except Http404:
             return redirect(reverse('search_results'))
             return redirect(reverse('search_results'))
 
 
-        form = QuickSearchForm(request=self.request, initial={'search_query': result['search_query']})
-        return self.render_to_response('results', form,
-                                       {
-                                        'search_query': result['search_query'],
-                                        'search_in': result.get('search_in'),
-                                        'search_author': result.get('search_author'),
-                                        'search_thread_titles': result.get('search_thread_titles'),
-                                        'search_thread': result.get('search_thread'),
-                                        'results': items[pagination['start']:pagination['stop']],
-                                        'items_total': items_total,
-                                        'pagination': pagination,
-                                       })
+        return render_to_response('search/results.html',
+                                  {
+                                   'search_in': result.get('search_in'),
+                                   'search_route': result.get('search_route'),
+                                   'search_query': result['search_query'],
+                                   'search_author': result.get('search_author'),
+                                   'search_thread_titles': result.get('search_thread_titles'),
+                                   'search_thread': result.get('search_thread'),
+                                   'results': Post.objects.filter(id__in=items).select_related('forum', 'thread', 'user')[pagination['start']:pagination['stop']],
+                                   'items_total': items_total,
+                                   'pagination': pagination,
+                                  },
+                                  context_instance=RequestContext(self.request))

+ 2 - 1
misago/forms/__init__.py

@@ -1,4 +1,5 @@
-from misago.forms.fields import ForumChoiceField, ReCaptchaField, QACaptchaField
+from misago.forms.fields import (ForumChoiceField, ForumMultipleChoiceField,
+                                 ReCaptchaField, QACaptchaField)
 from misago.forms.forms import Form
 from misago.forms.forms import Form
 from misago.forms.iterators import FormIterator
 from misago.forms.iterators import FormIterator
 from misago.forms.widgets import ReCaptchaWidget, YesNoSwitch, ForumTOS
 from misago.forms.widgets import ReCaptchaWidget, YesNoSwitch, ForumTOS

+ 12 - 1
misago/forms/fields.py

@@ -1,7 +1,7 @@
 from django.utils.html import conditional_escape, mark_safe
 from django.utils.html import conditional_escape, mark_safe
 from django.utils.translation import ugettext_lazy as _
 from django.utils.translation import ugettext_lazy as _
 from floppyforms import fields, widgets
 from floppyforms import fields, widgets
-from mptt.forms import TreeNodeChoiceField
+from mptt.forms import TreeNodeChoiceField, TreeNodeMultipleChoiceField
 from misago.forms.widgets import ReCaptchaWidget
 from misago.forms.widgets import ReCaptchaWidget
 
 
 class ForumChoiceField(TreeNodeChoiceField):
 class ForumChoiceField(TreeNodeChoiceField):
@@ -19,6 +19,17 @@ class ForumChoiceField(TreeNodeChoiceField):
         return mark_safe(conditional_escape(self.level_indicator) * (level - 1))
         return mark_safe(conditional_escape(self.level_indicator) * (level - 1))
 
 
 
 
+class ForumMultipleChoiceField(TreeNodeMultipleChoiceField):
+    widget = widgets.CheckboxSelectMultiple
+
+    def __init__(self, *args, **kwargs):
+        kwargs['level_indicator'] = u'- - '
+        super(ForumMultipleChoiceField, self).__init__(*args, **kwargs)
+
+    def _get_level_indicator(self, obj):
+        level = getattr(obj, obj._mptt_meta.level_attr)
+        return mark_safe(conditional_escape(self.level_indicator) * (level - 1))
+
 class ReCaptchaField(fields.CharField):
 class ReCaptchaField(fields.CharField):
     widget = ReCaptchaWidget
     widget = ReCaptchaWidget
     api_error = None
     api_error = None

+ 6 - 18
misago/forms/forms.py

@@ -66,24 +66,12 @@ class Form(forms.Form):
         """
         """
         self.ensure_finalization()
         self.ensure_finalization()
         self.data = self.data.copy()
         self.data = self.data.copy()
-        for key, field in self.fields.iteritems():
-            try:
-                if field.__class__.__name__ in ['ModelChoiceField', 'TreeForeignKey'] and self.data[key]:
-                    self.data[key] = int(self.data[key])
-                elif field.__class__.__name__ == 'ModelMultipleChoiceField':
-                    self.data.setlist(key, [int(x) for x in self.data.getlist(key, [])])
-                elif field.__class__.__name__ not in ['DateField', 'DateTimeField']:
-                    if not key in self.dont_strip:
-                        if field.__class__.__name__ in ['MultipleChoiceField', 'TypedMultipleChoiceField']:
-                            self.data.setlist(key, [x.strip() for x in self.data.getlist(key, [])])
-                        else:
-                            self.data[key] = self.data[key].strip()
-                    if field.__class__.__name__ in ['MultipleChoiceField', 'TypedMultipleChoiceField']:
-                        self.data.setlist(key, [x.replace("\r\n", '') for x in self.data.getlist(key, [])])
-                    elif not field.widget.__class__.__name__ in ['Textarea']:
-                        self.data[key] = self.data[key].replace("\r\n", '')
-            except (KeyError, AttributeError):
-                pass
+        for name, field in self.fields.iteritems():
+            if field.__class__ == forms.CharField:
+                try:
+                    self.data[name] = self.data[name].strip()
+                except KeyError:
+                    pass
         super(Form, self).full_clean()
         super(Form, self).full_clean()
 
 
     def clean(self):
     def clean(self):

+ 23 - 7
misago/search.py

@@ -1,4 +1,7 @@
+from urlparse import urlparse
+from django.core.urlresolvers import resolve
 from django.db.models import Q
 from django.db.models import Q
+from django.http import Http404
 from django.utils.translation import ugettext_lazy as _
 from django.utils.translation import ugettext_lazy as _
 from haystack.inputs import AutoQuery
 from haystack.inputs import AutoQuery
 from haystack.query import SearchQuerySet, RelatedSearchQuerySet
 from haystack.query import SearchQuerySet, RelatedSearchQuerySet
@@ -97,6 +100,7 @@ class SearchQuery(object):
 class MisagoSearchQuerySet(object):
 class MisagoSearchQuerySet(object):
     def __init__(self, user, acl):
     def __init__(self, user, acl):
         self._content = None
         self._content = None
+        self._thread_start = None
         self._thread_name = None
         self._thread_name = None
         self._user_name = None
         self._user_name = None
         self._after = None
         self._after = None
@@ -135,7 +139,7 @@ class MisagoSearchQuerySet(object):
             if thread.pk in self._threads:
             if thread.pk in self._threads:
                 self._threads = [thread.pk]
                 self._threads = [thread.pk]
             else:
             else:
-                raise ACLError403()
+                raise ACLError404()
         self._threads = [thread.pk]
         self._threads = [thread.pk]
 
 
     def search_content(self, query):
     def search_content(self, query):
@@ -144,21 +148,31 @@ class MisagoSearchQuerySet(object):
     def restrict_threads(self, threads=None):
     def restrict_threads(self, threads=None):
         self._threads = threads
         self._threads = threads
 
 
+    def search_thread_name_link(self, query):
+        try:
+            link = resolve(urlparse(query).path)
+            thread = Thread.objects.get(pk=link.kwargs['thread'])
+            self.allow_thread_search(thread)
+        except (Http404, KeyError, Thread.DoesNotExist):
+            self._thread_name = query
+
+    def search_thread_titles(self, value=True):
+        self._thread_start = value
+
     def search_thread_name(self, query):
     def search_thread_name(self, query):
         self._thread_name = query
         self._thread_name = query
 
 
     def search_user_name(self, query):
     def search_user_name(self, query):
         self._user_name = query
         self._user_name = query
 
 
-    def after(self, datetime):
+    def search_after(self, datetime):
         self._after = datetime
         self._after = datetime
 
 
-    def before(self, datetime):
+    def search_before(self, datetime):
         self._before = datetime
         self._before = datetime
 
 
-    def in_forum(self, forum, children=False):
-        self._forum = forum
-        self._children = children
+    def in_forums(self, forums):
+        self._forums = forums
 
 
     @property
     @property
     def query(self):
     def query(self):
@@ -173,7 +187,9 @@ class MisagoSearchQuerySet(object):
             sqs = sqs.auto_query(self._content)
             sqs = sqs.auto_query(self._content)
 
 
         if self._thread_name:
         if self._thread_name:
-            sqs = sqs.filter(thread_name=AutoQuery(self._thread_name), start_post=1)
+            sqs = sqs.filter(thread_name=AutoQuery(self._thread_name))
+            if self._thread_start:
+                sqs = sqs.filter(start_post=1)
 
 
         if self._user_name:
         if self._user_name:
             sqs = sqs.filter(username=self._user_name)
             sqs = sqs.filter(username=self._user_name)

+ 5 - 1
static/cranefly/css/cranefly.css

@@ -1,5 +1,6 @@
 @import "jquery.atwho.css";
 @import "jquery.atwho.css";
 @import "jquery.Jcrop.min.css";
 @import "jquery.Jcrop.min.css";
+@import "datepicker.css";
 @import "font-awesome.min.css";
 @import "font-awesome.min.css";
 article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}
 article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}
 audio,canvas,video{display:inline-block;*display:inline;*zoom:1}
 audio,canvas,video{display:inline-block;*display:inline;*zoom:1}
@@ -1106,7 +1107,10 @@ a.btn-link:hover,a.btn-link:active,a.btn-link:focus{color:#333;text-decoration:n
 .search-suggestion{overflow:auto}.search-suggestion p,.search-suggestion form{float:left}
 .search-suggestion{overflow:auto}.search-suggestion p,.search-suggestion form{float:left}
 .search-suggestion .lead{color:#7b7b7b}
 .search-suggestion .lead{color:#7b7b7b}
 .search-suggestion form .btn-link{margin-top:1px;color:#3c85a3;font-size:21px;font-style:italic;font-weight:200;text-decoration:underline}.search-suggestion form .btn-link:hover,.search-suggestion form .btn-link:active{color:#3c85a3;text-decoration:underline !important}
 .search-suggestion form .btn-link{margin-top:1px;color:#3c85a3;font-size:21px;font-style:italic;font-weight:200;text-decoration:underline}.search-suggestion form .btn-link:hover,.search-suggestion form .btn-link:active{color:#3c85a3;text-decoration:underline !important}
-.search-resume .muted{color:#7b7b7b}.search-resume .muted a{color:#333}
+.search-resume a:link,.search-resume a:visited{color:#999}.search-resume a:link strong,.search-resume a:visited strong{color:#555}
+.search-resume a:hover,.search-resume a:active{color:#555}.search-resume a:hover strong,.search-resume a:active strong{color:#333}
+.search-results h2{margin-bottom:30px;color:#999;font-weight:normal}.search-results h2 strong{color:#333}
+.search-results h2 .btn{position:relative;top:6px}
 .search-results .results-list .result{border-bottom:1px solid #eee;margin-bottom:10px;padding-bottom:10px}.search-results .results-list .result .result-avatar{float:left}.search-results .results-list .result .result-avatar img{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;width:70px;height:70px}
 .search-results .results-list .result{border-bottom:1px solid #eee;margin-bottom:10px;padding-bottom:10px}.search-results .results-list .result .result-avatar{float:left}.search-results .results-list .result .result-avatar img{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;width:70px;height:70px}
 .search-results .results-list .result .result-content{margin-left:84px}.search-results .results-list .result .result-content h3{margin:0;line-height:20px}.search-results .results-list .result .result-content h3 a:link,.search-results .results-list .result .result-content h3 a:visited{color:#555;font-weight:normal;font-size:18.2px;text-decoration:underline}
 .search-results .results-list .result .result-content{margin-left:84px}.search-results .results-list .result .result-content h3{margin:0;line-height:20px}.search-results .results-list .result .result-content h3 a:link,.search-results .results-list .result .result-content h3 a:visited{color:#555;font-weight:normal;font-size:18.2px;text-decoration:underline}
 .search-results .results-list .result .result-content h3 a:hover,.search-results .results-list .result .result-content h3 a:active{color:#333}
 .search-results .results-list .result .result-content h3 a:hover,.search-results .results-list .result .result-content h3 a:active{color:#333}

+ 1 - 0
static/cranefly/css/cranefly.less

@@ -97,6 +97,7 @@
 // Extra css
 // Extra css
 @import "jquery.atwho.css";
 @import "jquery.atwho.css";
 @import "jquery.Jcrop.min.css";
 @import "jquery.Jcrop.min.css";
+@import "datepicker.css";
 @import "font-awesome.min.css";
 @import "font-awesome.min.css";
 
 
 // Keep ranks last for easy overrides!
 // Keep ranks last for easy overrides!

+ 30 - 6
static/cranefly/css/cranefly/search.less

@@ -31,16 +31,40 @@
 }
 }
 
 
 .search-resume {
 .search-resume {
-  .muted {
-    color: lighten(@gray, 15%);
+	a:link, a:visited {
+		color: @grayLight;
 
 
-    a {
-      color: @textColor;
-    }
-  }
+		strong {
+			color: @gray;
+		}
+	}
+
+	a:hover, a:active {
+		color: @gray;
+
+		strong {
+			color: @textColor;
+		}
+	}
 }
 }
 
 
 .search-results {
 .search-results {
+	h2 {
+		margin-bottom: @baseLineHeight * 1.5;
+
+		color: @grayLight;
+		font-weight: normal;
+
+		strong {
+			color: @textColor;
+		}
+
+		.btn {
+			position: relative;
+			top: 6px;
+		}
+	}
+
   .results-list {
   .results-list {
     .result {
     .result {
       border-bottom: 1px solid darken(@bodyBackground, 5%);
       border-bottom: 1px solid darken(@bodyBackground, 5%);

+ 481 - 0
static/cranefly/css/datepicker.css

@@ -0,0 +1,481 @@
+/*!
+ * Datepicker for Bootstrap
+ *
+ * Copyright 2012 Stefan Petre
+ * Improvements by Andrew Rowls
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ */
+.datepicker {
+  padding: 4px;
+  -webkit-border-radius: 4px;
+  -moz-border-radius: 4px;
+  border-radius: 4px;
+  direction: ltr;
+  /*.dow {
+		border-top: 1px solid #ddd !important;
+	}*/
+
+}
+.datepicker-inline {
+  width: 220px;
+}
+.datepicker.datepicker-rtl {
+  direction: rtl;
+}
+.datepicker.datepicker-rtl table tr td span {
+  float: right;
+}
+.datepicker-dropdown {
+  top: 0;
+  left: 0;
+}
+.datepicker-dropdown:before {
+  content: '';
+  display: inline-block;
+  border-left: 7px solid transparent;
+  border-right: 7px solid transparent;
+  border-bottom: 7px solid #ccc;
+  border-top: 0;
+  border-bottom-color: rgba(0, 0, 0, 0.2);
+  position: absolute;
+}
+.datepicker-dropdown:after {
+  content: '';
+  display: inline-block;
+  border-left: 6px solid transparent;
+  border-right: 6px solid transparent;
+  border-bottom: 6px solid #ffffff;
+  border-top: 0;
+  position: absolute;
+}
+.datepicker-dropdown.datepicker-orient-left:before {
+  left: 6px;
+}
+.datepicker-dropdown.datepicker-orient-left:after {
+  left: 7px;
+}
+.datepicker-dropdown.datepicker-orient-right:before {
+  right: 6px;
+}
+.datepicker-dropdown.datepicker-orient-right:after {
+  right: 7px;
+}
+.datepicker-dropdown.datepicker-orient-top:before {
+  top: -7px;
+}
+.datepicker-dropdown.datepicker-orient-top:after {
+  top: -6px;
+}
+.datepicker-dropdown.datepicker-orient-bottom:before {
+  bottom: -7px;
+  border-bottom: 0;
+  border-top: 7px solid #999;
+}
+.datepicker-dropdown.datepicker-orient-bottom:after {
+  bottom: -6px;
+  border-bottom: 0;
+  border-top: 6px solid #ffffff;
+}
+.datepicker > div {
+  display: none;
+}
+.datepicker.days div.datepicker-days {
+  display: block;
+}
+.datepicker.months div.datepicker-months {
+  display: block;
+}
+.datepicker.years div.datepicker-years {
+  display: block;
+}
+.datepicker table {
+  margin: 0;
+  -webkit-touch-callout: none;
+  -webkit-user-select: none;
+  -khtml-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  user-select: none;
+}
+.datepicker td,
+.datepicker th {
+  text-align: center;
+  width: 20px;
+  height: 20px;
+  -webkit-border-radius: 4px;
+  -moz-border-radius: 4px;
+  border-radius: 4px;
+  border: none;
+}
+.table-striped .datepicker table tr td,
+.table-striped .datepicker table tr th {
+  background-color: transparent;
+}
+.datepicker table tr td.day:hover {
+  background: #eeeeee;
+  cursor: pointer;
+}
+.datepicker table tr td.old,
+.datepicker table tr td.new {
+  color: #999999;
+}
+.datepicker table tr td.disabled,
+.datepicker table tr td.disabled:hover {
+  background: none;
+  color: #999999;
+  cursor: default;
+}
+.datepicker table tr td.today,
+.datepicker table tr td.today:hover,
+.datepicker table tr td.today.disabled,
+.datepicker table tr td.today.disabled:hover {
+  background-color: #fde19a;
+  background-image: -moz-linear-gradient(top, #fdd49a, #fdf59a);
+  background-image: -ms-linear-gradient(top, #fdd49a, #fdf59a);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fdd49a), to(#fdf59a));
+  background-image: -webkit-linear-gradient(top, #fdd49a, #fdf59a);
+  background-image: -o-linear-gradient(top, #fdd49a, #fdf59a);
+  background-image: linear-gradient(top, #fdd49a, #fdf59a);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fdd49a', endColorstr='#fdf59a', GradientType=0);
+  border-color: #fdf59a #fdf59a #fbed50;
+  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+  color: #000;
+}
+.datepicker table tr td.today:hover,
+.datepicker table tr td.today:hover:hover,
+.datepicker table tr td.today.disabled:hover,
+.datepicker table tr td.today.disabled:hover:hover,
+.datepicker table tr td.today:active,
+.datepicker table tr td.today:hover:active,
+.datepicker table tr td.today.disabled:active,
+.datepicker table tr td.today.disabled:hover:active,
+.datepicker table tr td.today.active,
+.datepicker table tr td.today:hover.active,
+.datepicker table tr td.today.disabled.active,
+.datepicker table tr td.today.disabled:hover.active,
+.datepicker table tr td.today.disabled,
+.datepicker table tr td.today:hover.disabled,
+.datepicker table tr td.today.disabled.disabled,
+.datepicker table tr td.today.disabled:hover.disabled,
+.datepicker table tr td.today[disabled],
+.datepicker table tr td.today:hover[disabled],
+.datepicker table tr td.today.disabled[disabled],
+.datepicker table tr td.today.disabled:hover[disabled] {
+  background-color: #fdf59a;
+}
+.datepicker table tr td.today:active,
+.datepicker table tr td.today:hover:active,
+.datepicker table tr td.today.disabled:active,
+.datepicker table tr td.today.disabled:hover:active,
+.datepicker table tr td.today.active,
+.datepicker table tr td.today:hover.active,
+.datepicker table tr td.today.disabled.active,
+.datepicker table tr td.today.disabled:hover.active {
+  background-color: #fbf069 \9;
+}
+.datepicker table tr td.today:hover:hover {
+  color: #000;
+}
+.datepicker table tr td.today.active:hover {
+  color: #fff;
+}
+.datepicker table tr td.range,
+.datepicker table tr td.range:hover,
+.datepicker table tr td.range.disabled,
+.datepicker table tr td.range.disabled:hover {
+  background: #eeeeee;
+  -webkit-border-radius: 0;
+  -moz-border-radius: 0;
+  border-radius: 0;
+}
+.datepicker table tr td.range.today,
+.datepicker table tr td.range.today:hover,
+.datepicker table tr td.range.today.disabled,
+.datepicker table tr td.range.today.disabled:hover {
+  background-color: #f3d17a;
+  background-image: -moz-linear-gradient(top, #f3c17a, #f3e97a);
+  background-image: -ms-linear-gradient(top, #f3c17a, #f3e97a);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f3c17a), to(#f3e97a));
+  background-image: -webkit-linear-gradient(top, #f3c17a, #f3e97a);
+  background-image: -o-linear-gradient(top, #f3c17a, #f3e97a);
+  background-image: linear-gradient(top, #f3c17a, #f3e97a);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#f3c17a', endColorstr='#f3e97a', GradientType=0);
+  border-color: #f3e97a #f3e97a #edde34;
+  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+  -webkit-border-radius: 0;
+  -moz-border-radius: 0;
+  border-radius: 0;
+}
+.datepicker table tr td.range.today:hover,
+.datepicker table tr td.range.today:hover:hover,
+.datepicker table tr td.range.today.disabled:hover,
+.datepicker table tr td.range.today.disabled:hover:hover,
+.datepicker table tr td.range.today:active,
+.datepicker table tr td.range.today:hover:active,
+.datepicker table tr td.range.today.disabled:active,
+.datepicker table tr td.range.today.disabled:hover:active,
+.datepicker table tr td.range.today.active,
+.datepicker table tr td.range.today:hover.active,
+.datepicker table tr td.range.today.disabled.active,
+.datepicker table tr td.range.today.disabled:hover.active,
+.datepicker table tr td.range.today.disabled,
+.datepicker table tr td.range.today:hover.disabled,
+.datepicker table tr td.range.today.disabled.disabled,
+.datepicker table tr td.range.today.disabled:hover.disabled,
+.datepicker table tr td.range.today[disabled],
+.datepicker table tr td.range.today:hover[disabled],
+.datepicker table tr td.range.today.disabled[disabled],
+.datepicker table tr td.range.today.disabled:hover[disabled] {
+  background-color: #f3e97a;
+}
+.datepicker table tr td.range.today:active,
+.datepicker table tr td.range.today:hover:active,
+.datepicker table tr td.range.today.disabled:active,
+.datepicker table tr td.range.today.disabled:hover:active,
+.datepicker table tr td.range.today.active,
+.datepicker table tr td.range.today:hover.active,
+.datepicker table tr td.range.today.disabled.active,
+.datepicker table tr td.range.today.disabled:hover.active {
+  background-color: #efe24b \9;
+}
+.datepicker table tr td.selected,
+.datepicker table tr td.selected:hover,
+.datepicker table tr td.selected.disabled,
+.datepicker table tr td.selected.disabled:hover {
+  background-color: #9e9e9e;
+  background-image: -moz-linear-gradient(top, #b3b3b3, #808080);
+  background-image: -ms-linear-gradient(top, #b3b3b3, #808080);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#b3b3b3), to(#808080));
+  background-image: -webkit-linear-gradient(top, #b3b3b3, #808080);
+  background-image: -o-linear-gradient(top, #b3b3b3, #808080);
+  background-image: linear-gradient(top, #b3b3b3, #808080);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#b3b3b3', endColorstr='#808080', GradientType=0);
+  border-color: #808080 #808080 #595959;
+  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+  color: #fff;
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+}
+.datepicker table tr td.selected:hover,
+.datepicker table tr td.selected:hover:hover,
+.datepicker table tr td.selected.disabled:hover,
+.datepicker table tr td.selected.disabled:hover:hover,
+.datepicker table tr td.selected:active,
+.datepicker table tr td.selected:hover:active,
+.datepicker table tr td.selected.disabled:active,
+.datepicker table tr td.selected.disabled:hover:active,
+.datepicker table tr td.selected.active,
+.datepicker table tr td.selected:hover.active,
+.datepicker table tr td.selected.disabled.active,
+.datepicker table tr td.selected.disabled:hover.active,
+.datepicker table tr td.selected.disabled,
+.datepicker table tr td.selected:hover.disabled,
+.datepicker table tr td.selected.disabled.disabled,
+.datepicker table tr td.selected.disabled:hover.disabled,
+.datepicker table tr td.selected[disabled],
+.datepicker table tr td.selected:hover[disabled],
+.datepicker table tr td.selected.disabled[disabled],
+.datepicker table tr td.selected.disabled:hover[disabled] {
+  background-color: #808080;
+}
+.datepicker table tr td.selected:active,
+.datepicker table tr td.selected:hover:active,
+.datepicker table tr td.selected.disabled:active,
+.datepicker table tr td.selected.disabled:hover:active,
+.datepicker table tr td.selected.active,
+.datepicker table tr td.selected:hover.active,
+.datepicker table tr td.selected.disabled.active,
+.datepicker table tr td.selected.disabled:hover.active {
+  background-color: #666666 \9;
+}
+.datepicker table tr td.active,
+.datepicker table tr td.active:hover,
+.datepicker table tr td.active.disabled,
+.datepicker table tr td.active.disabled:hover {
+  background-color: #006dcc;
+  background-image: -moz-linear-gradient(top, #0088cc, #0044cc);
+  background-image: -ms-linear-gradient(top, #0088cc, #0044cc);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));
+  background-image: -webkit-linear-gradient(top, #0088cc, #0044cc);
+  background-image: -o-linear-gradient(top, #0088cc, #0044cc);
+  background-image: linear-gradient(top, #0088cc, #0044cc);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0);
+  border-color: #0044cc #0044cc #002a80;
+  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+  color: #fff;
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+}
+.datepicker table tr td.active:hover,
+.datepicker table tr td.active:hover:hover,
+.datepicker table tr td.active.disabled:hover,
+.datepicker table tr td.active.disabled:hover:hover,
+.datepicker table tr td.active:active,
+.datepicker table tr td.active:hover:active,
+.datepicker table tr td.active.disabled:active,
+.datepicker table tr td.active.disabled:hover:active,
+.datepicker table tr td.active.active,
+.datepicker table tr td.active:hover.active,
+.datepicker table tr td.active.disabled.active,
+.datepicker table tr td.active.disabled:hover.active,
+.datepicker table tr td.active.disabled,
+.datepicker table tr td.active:hover.disabled,
+.datepicker table tr td.active.disabled.disabled,
+.datepicker table tr td.active.disabled:hover.disabled,
+.datepicker table tr td.active[disabled],
+.datepicker table tr td.active:hover[disabled],
+.datepicker table tr td.active.disabled[disabled],
+.datepicker table tr td.active.disabled:hover[disabled] {
+  background-color: #0044cc;
+}
+.datepicker table tr td.active:active,
+.datepicker table tr td.active:hover:active,
+.datepicker table tr td.active.disabled:active,
+.datepicker table tr td.active.disabled:hover:active,
+.datepicker table tr td.active.active,
+.datepicker table tr td.active:hover.active,
+.datepicker table tr td.active.disabled.active,
+.datepicker table tr td.active.disabled:hover.active {
+  background-color: #003399 \9;
+}
+.datepicker table tr td span {
+  display: block;
+  width: 23%;
+  height: 54px;
+  line-height: 54px;
+  float: left;
+  margin: 1%;
+  cursor: pointer;
+  -webkit-border-radius: 4px;
+  -moz-border-radius: 4px;
+  border-radius: 4px;
+}
+.datepicker table tr td span:hover {
+  background: #eeeeee;
+}
+.datepicker table tr td span.disabled,
+.datepicker table tr td span.disabled:hover {
+  background: none;
+  color: #999999;
+  cursor: default;
+}
+.datepicker table tr td span.active,
+.datepicker table tr td span.active:hover,
+.datepicker table tr td span.active.disabled,
+.datepicker table tr td span.active.disabled:hover {
+  background-color: #006dcc;
+  background-image: -moz-linear-gradient(top, #0088cc, #0044cc);
+  background-image: -ms-linear-gradient(top, #0088cc, #0044cc);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));
+  background-image: -webkit-linear-gradient(top, #0088cc, #0044cc);
+  background-image: -o-linear-gradient(top, #0088cc, #0044cc);
+  background-image: linear-gradient(top, #0088cc, #0044cc);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0);
+  border-color: #0044cc #0044cc #002a80;
+  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+  color: #fff;
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+}
+.datepicker table tr td span.active:hover,
+.datepicker table tr td span.active:hover:hover,
+.datepicker table tr td span.active.disabled:hover,
+.datepicker table tr td span.active.disabled:hover:hover,
+.datepicker table tr td span.active:active,
+.datepicker table tr td span.active:hover:active,
+.datepicker table tr td span.active.disabled:active,
+.datepicker table tr td span.active.disabled:hover:active,
+.datepicker table tr td span.active.active,
+.datepicker table tr td span.active:hover.active,
+.datepicker table tr td span.active.disabled.active,
+.datepicker table tr td span.active.disabled:hover.active,
+.datepicker table tr td span.active.disabled,
+.datepicker table tr td span.active:hover.disabled,
+.datepicker table tr td span.active.disabled.disabled,
+.datepicker table tr td span.active.disabled:hover.disabled,
+.datepicker table tr td span.active[disabled],
+.datepicker table tr td span.active:hover[disabled],
+.datepicker table tr td span.active.disabled[disabled],
+.datepicker table tr td span.active.disabled:hover[disabled] {
+  background-color: #0044cc;
+}
+.datepicker table tr td span.active:active,
+.datepicker table tr td span.active:hover:active,
+.datepicker table tr td span.active.disabled:active,
+.datepicker table tr td span.active.disabled:hover:active,
+.datepicker table tr td span.active.active,
+.datepicker table tr td span.active:hover.active,
+.datepicker table tr td span.active.disabled.active,
+.datepicker table tr td span.active.disabled:hover.active {
+  background-color: #003399 \9;
+}
+.datepicker table tr td span.old,
+.datepicker table tr td span.new {
+  color: #999999;
+}
+.datepicker th.datepicker-switch {
+  width: 145px;
+}
+.datepicker thead tr:first-child th,
+.datepicker tfoot tr th {
+  cursor: pointer;
+}
+.datepicker thead tr:first-child th:hover,
+.datepicker tfoot tr th:hover {
+  background: #eeeeee;
+}
+.datepicker .cw {
+  font-size: 10px;
+  width: 12px;
+  padding: 0 2px 0 5px;
+  vertical-align: middle;
+}
+.datepicker thead tr:first-child th.cw {
+  cursor: default;
+  background-color: transparent;
+}
+.input-append.date .add-on i,
+.input-prepend.date .add-on i {
+  display: block;
+  cursor: pointer;
+  width: 16px;
+  height: 16px;
+}
+.input-daterange input {
+  text-align: center;
+}
+.input-daterange input:first-child {
+  -webkit-border-radius: 3px 0 0 3px;
+  -moz-border-radius: 3px 0 0 3px;
+  border-radius: 3px 0 0 3px;
+}
+.input-daterange input:last-child {
+  -webkit-border-radius: 0 3px 3px 0;
+  -moz-border-radius: 0 3px 3px 0;
+  border-radius: 0 3px 3px 0;
+}
+.input-daterange .add-on {
+  display: inline-block;
+  width: auto;
+  min-width: 16px;
+  height: 18px;
+  padding: 4px 5px;
+  font-weight: normal;
+  line-height: 18px;
+  text-align: center;
+  text-shadow: 0 1px 0 #ffffff;
+  vertical-align: middle;
+  background-color: #eeeeee;
+  border: 1px solid #ccc;
+  margin-left: -5px;
+  margin-right: -5px;
+}

+ 0 - 1
static/cranefly/css/style.css

@@ -1 +0,0 @@
-lessc: ENOENT, open '/Users/rafapiton/Documents/misago/static/cranefly/css/style.less'

+ 1395 - 0
static/cranefly/js/bootstrap-datepicker.js

@@ -0,0 +1,1395 @@
+/* =========================================================
+ * bootstrap-datepicker.js
+ * http://www.eyecon.ro/bootstrap-datepicker
+ * =========================================================
+ * Copyright 2012 Stefan Petre
+ * Improvements by Andrew Rowls
+ *
+ * Licensed 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.
+ * ========================================================= */
+
+(function( $ ) {
+
+	var $window = $(window);
+
+	function UTCDate(){
+		return new Date(Date.UTC.apply(Date, arguments));
+	}
+	function UTCToday(){
+		var today = new Date();
+		return UTCDate(today.getUTCFullYear(), today.getUTCMonth(), today.getUTCDate());
+	}
+
+
+	// Picker object
+
+	var Datepicker = function(element, options) {
+		var that = this;
+
+		this._process_options(options);
+
+		this.element = $(element);
+		this.isInline = false;
+		this.isInput = this.element.is('input');
+		this.component = this.element.is('.date') ? this.element.find('.add-on, .btn') : false;
+		this.hasInput = this.component && this.element.find('input').length;
+		if(this.component && this.component.length === 0)
+			this.component = false;
+
+		this.picker = $(DPGlobal.template);
+		this._buildEvents();
+		this._attachEvents();
+
+		if(this.isInline) {
+			this.picker.addClass('datepicker-inline').appendTo(this.element);
+		} else {
+			this.picker.addClass('datepicker-dropdown dropdown-menu');
+		}
+
+		if (this.o.rtl){
+			this.picker.addClass('datepicker-rtl');
+			this.picker.find('.prev i, .next i')
+						.toggleClass('icon-arrow-left icon-arrow-right');
+		}
+
+
+		this.viewMode = this.o.startView;
+
+		if (this.o.calendarWeeks)
+			this.picker.find('tfoot th.today')
+						.attr('colspan', function(i, val){
+							return parseInt(val) + 1;
+						});
+
+		this._allow_update = false;
+
+		this.setStartDate(this._o.startDate);
+		this.setEndDate(this._o.endDate);
+		this.setDaysOfWeekDisabled(this.o.daysOfWeekDisabled);
+
+		this.fillDow();
+		this.fillMonths();
+
+		this._allow_update = true;
+
+		this.update();
+		this.showMode();
+
+		if(this.isInline) {
+			this.show();
+		}
+	};
+
+	Datepicker.prototype = {
+		constructor: Datepicker,
+
+		_process_options: function(opts){
+			// Store raw options for reference
+			this._o = $.extend({}, this._o, opts);
+			// Processed options
+			var o = this.o = $.extend({}, this._o);
+
+			// Check if "de-DE" style date is available, if not language should
+			// fallback to 2 letter code eg "de"
+			var lang = o.language;
+			if (!dates[lang]) {
+				lang = lang.split('-')[0];
+				if (!dates[lang])
+					lang = defaults.language;
+			}
+			o.language = lang;
+
+			switch(o.startView){
+				case 2:
+				case 'decade':
+					o.startView = 2;
+					break;
+				case 1:
+				case 'year':
+					o.startView = 1;
+					break;
+				default:
+					o.startView = 0;
+			}
+
+			switch (o.minViewMode) {
+				case 1:
+				case 'months':
+					o.minViewMode = 1;
+					break;
+				case 2:
+				case 'years':
+					o.minViewMode = 2;
+					break;
+				default:
+					o.minViewMode = 0;
+			}
+
+			o.startView = Math.max(o.startView, o.minViewMode);
+
+			o.weekStart %= 7;
+			o.weekEnd = ((o.weekStart + 6) % 7);
+
+			var format = DPGlobal.parseFormat(o.format);
+			if (o.startDate !== -Infinity) {
+				if (!!o.startDate) {
+					if (o.startDate instanceof Date)
+						o.startDate = this._local_to_utc(this._zero_time(o.startDate));
+					else
+						o.startDate = DPGlobal.parseDate(o.startDate, format, o.language);
+				} else {
+					o.startDate = -Infinity;
+				}
+			}
+			if (o.endDate !== Infinity) {
+				if (!!o.endDate) {
+					if (o.endDate instanceof Date)
+						o.endDate = this._local_to_utc(this._zero_time(o.endDate));
+					else
+						o.endDate = DPGlobal.parseDate(o.endDate, format, o.language);
+				} else {
+					o.endDate = Infinity;
+				}
+			}
+
+			o.daysOfWeekDisabled = o.daysOfWeekDisabled||[];
+			if (!$.isArray(o.daysOfWeekDisabled))
+				o.daysOfWeekDisabled = o.daysOfWeekDisabled.split(/[,\s]*/);
+			o.daysOfWeekDisabled = $.map(o.daysOfWeekDisabled, function (d) {
+				return parseInt(d, 10);
+			});
+
+			var plc = String(o.orientation).toLowerCase().split(/\s+/g),
+				_plc = o.orientation.toLowerCase();
+			plc = $.grep(plc, function(word){
+				return (/^auto|left|right|top|bottom$/).test(word);
+			});
+			o.orientation = {x: 'auto', y: 'auto'};
+			if (!_plc || _plc === 'auto')
+				; // no action
+			else if (plc.length === 1){
+				switch(plc[0]){
+					case 'top':
+					case 'bottom':
+						o.orientation.y = plc[0];
+						break;
+					case 'left':
+					case 'right':
+						o.orientation.x = plc[0];
+						break;
+				}
+			}
+			else {
+				_plc = $.grep(plc, function(word){
+					return (/^left|right$/).test(word);
+				});
+				o.orientation.x = _plc[0] || 'auto';
+
+				_plc = $.grep(plc, function(word){
+					return (/^top|bottom$/).test(word);
+				});
+				o.orientation.y = _plc[0] || 'auto';
+			}
+		},
+		_events: [],
+		_secondaryEvents: [],
+		_applyEvents: function(evs){
+			for (var i=0, el, ev; i<evs.length; i++){
+				el = evs[i][0];
+				ev = evs[i][1];
+				el.on(ev);
+			}
+		},
+		_unapplyEvents: function(evs){
+			for (var i=0, el, ev; i<evs.length; i++){
+				el = evs[i][0];
+				ev = evs[i][1];
+				el.off(ev);
+			}
+		},
+		_buildEvents: function(){
+			if (this.isInput) { // single input
+				this._events = [
+					[this.element, {
+						focus: $.proxy(this.show, this),
+						keyup: $.proxy(this.update, this),
+						keydown: $.proxy(this.keydown, this)
+					}]
+				];
+			}
+			else if (this.component && this.hasInput){ // component: input + button
+				this._events = [
+					// For components that are not readonly, allow keyboard nav
+					[this.element.find('input'), {
+						focus: $.proxy(this.show, this),
+						keyup: $.proxy(this.update, this),
+						keydown: $.proxy(this.keydown, this)
+					}],
+					[this.component, {
+						click: $.proxy(this.show, this)
+					}]
+				];
+			}
+			else if (this.element.is('div')) {  // inline datepicker
+				this.isInline = true;
+			}
+			else {
+				this._events = [
+					[this.element, {
+						click: $.proxy(this.show, this)
+					}]
+				];
+			}
+
+			this._secondaryEvents = [
+				[this.picker, {
+					click: $.proxy(this.click, this)
+				}],
+				[$(window), {
+					resize: $.proxy(this.place, this)
+				}],
+				[$(document), {
+					'mousedown touchstart': $.proxy(function (e) {
+						// Clicked outside the datepicker, hide it
+						if (!(
+							this.element.is(e.target) ||
+							this.element.find(e.target).length ||
+							this.picker.is(e.target) ||
+							this.picker.find(e.target).length
+						)) {
+							this.hide();
+						}
+					}, this)
+				}]
+			];
+		},
+		_attachEvents: function(){
+			this._detachEvents();
+			this._applyEvents(this._events);
+		},
+		_detachEvents: function(){
+			this._unapplyEvents(this._events);
+		},
+		_attachSecondaryEvents: function(){
+			this._detachSecondaryEvents();
+			this._applyEvents(this._secondaryEvents);
+		},
+		_detachSecondaryEvents: function(){
+			this._unapplyEvents(this._secondaryEvents);
+		},
+		_trigger: function(event, altdate){
+			var date = altdate || this.date,
+				local_date = this._utc_to_local(date);
+
+			this.element.trigger({
+				type: event,
+				date: local_date,
+				format: $.proxy(function(altformat){
+					var format = altformat || this.o.format;
+					return DPGlobal.formatDate(date, format, this.o.language);
+				}, this)
+			});
+		},
+
+		show: function(e) {
+			if (!this.isInline)
+				this.picker.appendTo('body');
+			this.picker.show();
+			this.height = this.component ? this.component.outerHeight() : this.element.outerHeight();
+			this.place();
+			this._attachSecondaryEvents();
+			if (e) {
+				e.preventDefault();
+			}
+			this._trigger('show');
+		},
+
+		hide: function(e){
+			if(this.isInline) return;
+			if (!this.picker.is(':visible')) return;
+			this.picker.hide().detach();
+			this._detachSecondaryEvents();
+			this.viewMode = this.o.startView;
+			this.showMode();
+
+			if (
+				this.o.forceParse &&
+				(
+					this.isInput && this.element.val() ||
+					this.hasInput && this.element.find('input').val()
+				)
+			)
+				this.setValue();
+			this._trigger('hide');
+		},
+
+		remove: function() {
+			this.hide();
+			this._detachEvents();
+			this._detachSecondaryEvents();
+			this.picker.remove();
+			delete this.element.data().datepicker;
+			if (!this.isInput) {
+				delete this.element.data().date;
+			}
+		},
+
+		_utc_to_local: function(utc){
+			return new Date(utc.getTime() + (utc.getTimezoneOffset()*60000));
+		},
+		_local_to_utc: function(local){
+			return new Date(local.getTime() - (local.getTimezoneOffset()*60000));
+		},
+		_zero_time: function(local){
+			return new Date(local.getFullYear(), local.getMonth(), local.getDate());
+		},
+		_zero_utc_time: function(utc){
+			return new Date(Date.UTC(utc.getUTCFullYear(), utc.getUTCMonth(), utc.getUTCDate()));
+		},
+
+		getDate: function() {
+			return this._utc_to_local(this.getUTCDate());
+		},
+
+		getUTCDate: function() {
+			return this.date;
+		},
+
+		setDate: function(d) {
+			this.setUTCDate(this._local_to_utc(d));
+		},
+
+		setUTCDate: function(d) {
+			this.date = d;
+			this.setValue();
+		},
+
+		setValue: function() {
+			var formatted = this.getFormattedDate();
+			if (!this.isInput) {
+				if (this.component){
+					this.element.find('input').val(formatted).change();
+				}
+			} else {
+				this.element.val(formatted).change();
+			}
+		},
+
+		getFormattedDate: function(format) {
+			if (format === undefined)
+				format = this.o.format;
+			return DPGlobal.formatDate(this.date, format, this.o.language);
+		},
+
+		setStartDate: function(startDate){
+			this._process_options({startDate: startDate});
+			this.update();
+			this.updateNavArrows();
+		},
+
+		setEndDate: function(endDate){
+			this._process_options({endDate: endDate});
+			this.update();
+			this.updateNavArrows();
+		},
+
+		setDaysOfWeekDisabled: function(daysOfWeekDisabled){
+			this._process_options({daysOfWeekDisabled: daysOfWeekDisabled});
+			this.update();
+			this.updateNavArrows();
+		},
+
+		place: function(){
+						if(this.isInline) return;
+			var calendarWidth = this.picker.outerWidth(),
+				calendarHeight = this.picker.outerHeight(),
+				visualPadding = 10,
+				windowWidth = $window.width(),
+				windowHeight = $window.height(),
+				scrollTop = $window.scrollTop();
+
+			var zIndex = parseInt(this.element.parents().filter(function() {
+							return $(this).css('z-index') != 'auto';
+						}).first().css('z-index'))+10;
+			var offset = this.component ? this.component.parent().offset() : this.element.offset();
+			var height = this.component ? this.component.outerHeight(true) : this.element.outerHeight(false);
+			var width = this.component ? this.component.outerWidth(true) : this.element.outerWidth(false);
+			var left = offset.left,
+				top = offset.top;
+
+			this.picker.removeClass(
+				'datepicker-orient-top datepicker-orient-bottom '+
+				'datepicker-orient-right datepicker-orient-left'
+			);
+
+			if (this.o.orientation.x !== 'auto') {
+				this.picker.addClass('datepicker-orient-' + this.o.orientation.x);
+				if (this.o.orientation.x === 'right')
+					left -= calendarWidth - width;
+			}
+			// auto x orientation is best-placement: if it crosses a window
+			// edge, fudge it sideways
+			else {
+				// Default to left
+				this.picker.addClass('datepicker-orient-left');
+				if (offset.left < 0)
+					left -= offset.left - visualPadding;
+				else if (offset.left + calendarWidth > windowWidth)
+					left = windowWidth - calendarWidth - visualPadding;
+			}
+
+			// auto y orientation is best-situation: top or bottom, no fudging,
+			// decision based on which shows more of the calendar
+			var yorient = this.o.orientation.y,
+				top_overflow, bottom_overflow;
+			if (yorient === 'auto') {
+				top_overflow = -scrollTop + offset.top - calendarHeight;
+				bottom_overflow = scrollTop + windowHeight - (offset.top + height + calendarHeight);
+				if (Math.max(top_overflow, bottom_overflow) === bottom_overflow)
+					yorient = 'top';
+				else
+					yorient = 'bottom';
+			}
+			this.picker.addClass('datepicker-orient-' + yorient);
+			if (yorient === 'top')
+				top += height;
+			else
+				top -= calendarHeight + parseInt(this.picker.css('padding-top'));
+
+			this.picker.css({
+				top: top,
+				left: left,
+				zIndex: zIndex
+			});
+		},
+
+		_allow_update: true,
+		update: function(){
+			if (!this._allow_update) return;
+
+			var oldDate = new Date(this.date),
+				date, fromArgs = false;
+			if(arguments && arguments.length && (typeof arguments[0] === 'string' || arguments[0] instanceof Date)) {
+				date = arguments[0];
+				if (date instanceof Date)
+					date = this._local_to_utc(date);
+				fromArgs = true;
+			} else {
+				date = this.isInput ? this.element.val() : this.element.data('date') || this.element.find('input').val();
+				delete this.element.data().date;
+			}
+
+			this.date = DPGlobal.parseDate(date, this.o.format, this.o.language);
+
+			if (fromArgs) {
+				// setting date by clicking
+				this.setValue();
+			} else if (date) {
+				// setting date by typing
+				if (oldDate.getTime() !== this.date.getTime())
+					this._trigger('changeDate');
+			} else {
+				// clearing date
+				this._trigger('clearDate');
+			}
+
+			if (this.date < this.o.startDate) {
+				this.viewDate = new Date(this.o.startDate);
+				this.date = new Date(this.o.startDate);
+			} else if (this.date > this.o.endDate) {
+				this.viewDate = new Date(this.o.endDate);
+				this.date = new Date(this.o.endDate);
+			} else {
+				this.viewDate = new Date(this.date);
+				this.date = new Date(this.date);
+			}
+			this.fill();
+		},
+
+		fillDow: function(){
+			var dowCnt = this.o.weekStart,
+			html = '<tr>';
+			if(this.o.calendarWeeks){
+				var cell = '<th class="cw">&nbsp;</th>';
+				html += cell;
+				this.picker.find('.datepicker-days thead tr:first-child').prepend(cell);
+			}
+			while (dowCnt < this.o.weekStart + 7) {
+				html += '<th class="dow">'+dates[this.o.language].daysMin[(dowCnt++)%7]+'</th>';
+			}
+			html += '</tr>';
+			this.picker.find('.datepicker-days thead').append(html);
+		},
+
+		fillMonths: function(){
+			var html = '',
+			i = 0;
+			while (i < 12) {
+				html += '<span class="month">'+dates[this.o.language].monthsShort[i++]+'</span>';
+			}
+			this.picker.find('.datepicker-months td').html(html);
+		},
+
+		setRange: function(range){
+			if (!range || !range.length)
+				delete this.range;
+			else
+				this.range = $.map(range, function(d){ return d.valueOf(); });
+			this.fill();
+		},
+
+		getClassNames: function(date){
+			var cls = [],
+				year = this.viewDate.getUTCFullYear(),
+				month = this.viewDate.getUTCMonth(),
+				currentDate = this.date.valueOf(),
+				today = new Date();
+			if (date.getUTCFullYear() < year || (date.getUTCFullYear() == year && date.getUTCMonth() < month)) {
+				cls.push('old');
+			} else if (date.getUTCFullYear() > year || (date.getUTCFullYear() == year && date.getUTCMonth() > month)) {
+				cls.push('new');
+			}
+			// Compare internal UTC date with local today, not UTC today
+			if (this.o.todayHighlight &&
+				date.getUTCFullYear() == today.getFullYear() &&
+				date.getUTCMonth() == today.getMonth() &&
+				date.getUTCDate() == today.getDate()) {
+				cls.push('today');
+			}
+			if (date.valueOf() == currentDate) {
+				cls.push('active');
+			}
+			if (date.valueOf() < this.o.startDate || date.valueOf() > this.o.endDate ||
+				$.inArray(date.getUTCDay(), this.o.daysOfWeekDisabled) !== -1) {
+				cls.push('disabled');
+			}
+			if (this.range){
+				if (date > this.range[0] && date < this.range[this.range.length-1]){
+					cls.push('range');
+				}
+				if ($.inArray(date.valueOf(), this.range) != -1){
+					cls.push('selected');
+				}
+			}
+			return cls;
+		},
+
+		fill: function() {
+			var d = new Date(this.viewDate),
+				year = d.getUTCFullYear(),
+				month = d.getUTCMonth(),
+				startYear = this.o.startDate !== -Infinity ? this.o.startDate.getUTCFullYear() : -Infinity,
+				startMonth = this.o.startDate !== -Infinity ? this.o.startDate.getUTCMonth() : -Infinity,
+				endYear = this.o.endDate !== Infinity ? this.o.endDate.getUTCFullYear() : Infinity,
+				endMonth = this.o.endDate !== Infinity ? this.o.endDate.getUTCMonth() : Infinity,
+				currentDate = this.date && this.date.valueOf(),
+				tooltip;
+			this.picker.find('.datepicker-days thead th.datepicker-switch')
+						.text(dates[this.o.language].months[month]+' '+year);
+			this.picker.find('tfoot th.today')
+						.text(dates[this.o.language].today)
+						.toggle(this.o.todayBtn !== false);
+			this.picker.find('tfoot th.clear')
+						.text(dates[this.o.language].clear)
+						.toggle(this.o.clearBtn !== false);
+			this.updateNavArrows();
+			this.fillMonths();
+			var prevMonth = UTCDate(year, month-1, 28,0,0,0,0),
+				day = DPGlobal.getDaysInMonth(prevMonth.getUTCFullYear(), prevMonth.getUTCMonth());
+			prevMonth.setUTCDate(day);
+			prevMonth.setUTCDate(day - (prevMonth.getUTCDay() - this.o.weekStart + 7)%7);
+			var nextMonth = new Date(prevMonth);
+			nextMonth.setUTCDate(nextMonth.getUTCDate() + 42);
+			nextMonth = nextMonth.valueOf();
+			var html = [];
+			var clsName;
+			while(prevMonth.valueOf() < nextMonth) {
+				if (prevMonth.getUTCDay() == this.o.weekStart) {
+					html.push('<tr>');
+					if(this.o.calendarWeeks){
+						// ISO 8601: First week contains first thursday.
+						// ISO also states week starts on Monday, but we can be more abstract here.
+						var
+							// Start of current week: based on weekstart/current date
+							ws = new Date(+prevMonth + (this.o.weekStart - prevMonth.getUTCDay() - 7) % 7 * 864e5),
+							// Thursday of this week
+							th = new Date(+ws + (7 + 4 - ws.getUTCDay()) % 7 * 864e5),
+							// First Thursday of year, year from thursday
+							yth = new Date(+(yth = UTCDate(th.getUTCFullYear(), 0, 1)) + (7 + 4 - yth.getUTCDay())%7*864e5),
+							// Calendar week: ms between thursdays, div ms per day, div 7 days
+							calWeek =  (th - yth) / 864e5 / 7 + 1;
+						html.push('<td class="cw">'+ calWeek +'</td>');
+
+					}
+				}
+				clsName = this.getClassNames(prevMonth);
+				clsName.push('day');
+
+				if (this.o.beforeShowDay !== $.noop){
+					var before = this.o.beforeShowDay(this._utc_to_local(prevMonth));
+					if (before === undefined)
+						before = {};
+					else if (typeof(before) === 'boolean')
+						before = {enabled: before};
+					else if (typeof(before) === 'string')
+						before = {classes: before};
+					if (before.enabled === false)
+						clsName.push('disabled');
+					if (before.classes)
+						clsName = clsName.concat(before.classes.split(/\s+/));
+					if (before.tooltip)
+						tooltip = before.tooltip;
+				}
+
+				clsName = $.unique(clsName);
+				html.push('<td class="'+clsName.join(' ')+'"' + (tooltip ? ' title="'+tooltip+'"' : '') + '>'+prevMonth.getUTCDate() + '</td>');
+				if (prevMonth.getUTCDay() == this.o.weekEnd) {
+					html.push('</tr>');
+				}
+				prevMonth.setUTCDate(prevMonth.getUTCDate()+1);
+			}
+			this.picker.find('.datepicker-days tbody').empty().append(html.join(''));
+			var currentYear = this.date && this.date.getUTCFullYear();
+
+			var months = this.picker.find('.datepicker-months')
+						.find('th:eq(1)')
+							.text(year)
+							.end()
+						.find('span').removeClass('active');
+			if (currentYear && currentYear == year) {
+				months.eq(this.date.getUTCMonth()).addClass('active');
+			}
+			if (year < startYear || year > endYear) {
+				months.addClass('disabled');
+			}
+			if (year == startYear) {
+				months.slice(0, startMonth).addClass('disabled');
+			}
+			if (year == endYear) {
+				months.slice(endMonth+1).addClass('disabled');
+			}
+
+			html = '';
+			year = parseInt(year/10, 10) * 10;
+			var yearCont = this.picker.find('.datepicker-years')
+								.find('th:eq(1)')
+									.text(year + '-' + (year + 9))
+									.end()
+								.find('td');
+			year -= 1;
+			for (var i = -1; i < 11; i++) {
+				html += '<span class="year'+(i == -1 ? ' old' : i == 10 ? ' new' : '')+(currentYear == year ? ' active' : '')+(year < startYear || year > endYear ? ' disabled' : '')+'">'+year+'</span>';
+				year += 1;
+			}
+			yearCont.html(html);
+		},
+
+		updateNavArrows: function() {
+			if (!this._allow_update) return;
+
+			var d = new Date(this.viewDate),
+				year = d.getUTCFullYear(),
+				month = d.getUTCMonth();
+			switch (this.viewMode) {
+				case 0:
+					if (this.o.startDate !== -Infinity && year <= this.o.startDate.getUTCFullYear() && month <= this.o.startDate.getUTCMonth()) {
+						this.picker.find('.prev').css({visibility: 'hidden'});
+					} else {
+						this.picker.find('.prev').css({visibility: 'visible'});
+					}
+					if (this.o.endDate !== Infinity && year >= this.o.endDate.getUTCFullYear() && month >= this.o.endDate.getUTCMonth()) {
+						this.picker.find('.next').css({visibility: 'hidden'});
+					} else {
+						this.picker.find('.next').css({visibility: 'visible'});
+					}
+					break;
+				case 1:
+				case 2:
+					if (this.o.startDate !== -Infinity && year <= this.o.startDate.getUTCFullYear()) {
+						this.picker.find('.prev').css({visibility: 'hidden'});
+					} else {
+						this.picker.find('.prev').css({visibility: 'visible'});
+					}
+					if (this.o.endDate !== Infinity && year >= this.o.endDate.getUTCFullYear()) {
+						this.picker.find('.next').css({visibility: 'hidden'});
+					} else {
+						this.picker.find('.next').css({visibility: 'visible'});
+					}
+					break;
+			}
+		},
+
+		click: function(e) {
+			e.preventDefault();
+			var target = $(e.target).closest('span, td, th');
+			if (target.length == 1) {
+				switch(target[0].nodeName.toLowerCase()) {
+					case 'th':
+						switch(target[0].className) {
+							case 'datepicker-switch':
+								this.showMode(1);
+								break;
+							case 'prev':
+							case 'next':
+								var dir = DPGlobal.modes[this.viewMode].navStep * (target[0].className == 'prev' ? -1 : 1);
+								switch(this.viewMode){
+									case 0:
+										this.viewDate = this.moveMonth(this.viewDate, dir);
+										this._trigger('changeMonth', this.viewDate);
+										break;
+									case 1:
+									case 2:
+										this.viewDate = this.moveYear(this.viewDate, dir);
+										if (this.viewMode === 1)
+											this._trigger('changeYear', this.viewDate);
+										break;
+								}
+								this.fill();
+								break;
+							case 'today':
+								var date = new Date();
+								date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0);
+
+								this.showMode(-2);
+								var which = this.o.todayBtn == 'linked' ? null : 'view';
+								this._setDate(date, which);
+								break;
+							case 'clear':
+								var element;
+								if (this.isInput)
+									element = this.element;
+								else if (this.component)
+									element = this.element.find('input');
+								if (element)
+									element.val("").change();
+								this._trigger('changeDate');
+								this.update();
+								if (this.o.autoclose)
+									this.hide();
+								break;
+						}
+						break;
+					case 'span':
+						if (!target.is('.disabled')) {
+							this.viewDate.setUTCDate(1);
+							if (target.is('.month')) {
+								var day = 1;
+								var month = target.parent().find('span').index(target);
+								var year = this.viewDate.getUTCFullYear();
+								this.viewDate.setUTCMonth(month);
+								this._trigger('changeMonth', this.viewDate);
+								if (this.o.minViewMode === 1) {
+									this._setDate(UTCDate(year, month, day,0,0,0,0));
+								}
+							} else {
+								var year = parseInt(target.text(), 10)||0;
+								var day = 1;
+								var month = 0;
+								this.viewDate.setUTCFullYear(year);
+								this._trigger('changeYear', this.viewDate);
+								if (this.o.minViewMode === 2) {
+									this._setDate(UTCDate(year, month, day,0,0,0,0));
+								}
+							}
+							this.showMode(-1);
+							this.fill();
+						}
+						break;
+					case 'td':
+						if (target.is('.day') && !target.is('.disabled')){
+							var day = parseInt(target.text(), 10)||1;
+							var year = this.viewDate.getUTCFullYear(),
+								month = this.viewDate.getUTCMonth();
+							if (target.is('.old')) {
+								if (month === 0) {
+									month = 11;
+									year -= 1;
+								} else {
+									month -= 1;
+								}
+							} else if (target.is('.new')) {
+								if (month == 11) {
+									month = 0;
+									year += 1;
+								} else {
+									month += 1;
+								}
+							}
+							this._setDate(UTCDate(year, month, day,0,0,0,0));
+						}
+						break;
+				}
+			}
+		},
+
+		_setDate: function(date, which){
+			if (!which || which == 'date')
+				this.date = new Date(date);
+			if (!which || which  == 'view')
+				this.viewDate = new Date(date);
+			this.fill();
+			this.setValue();
+			this._trigger('changeDate');
+			var element;
+			if (this.isInput) {
+				element = this.element;
+			} else if (this.component){
+				element = this.element.find('input');
+			}
+			if (element) {
+				element.change();
+			}
+			if (this.o.autoclose && (!which || which == 'date')) {
+				this.hide();
+			}
+		},
+
+		moveMonth: function(date, dir){
+			if (!dir) return date;
+			var new_date = new Date(date.valueOf()),
+				day = new_date.getUTCDate(),
+				month = new_date.getUTCMonth(),
+				mag = Math.abs(dir),
+				new_month, test;
+			dir = dir > 0 ? 1 : -1;
+			if (mag == 1){
+				test = dir == -1
+					// If going back one month, make sure month is not current month
+					// (eg, Mar 31 -> Feb 31 == Feb 28, not Mar 02)
+					? function(){ return new_date.getUTCMonth() == month; }
+					// If going forward one month, make sure month is as expected
+					// (eg, Jan 31 -> Feb 31 == Feb 28, not Mar 02)
+					: function(){ return new_date.getUTCMonth() != new_month; };
+				new_month = month + dir;
+				new_date.setUTCMonth(new_month);
+				// Dec -> Jan (12) or Jan -> Dec (-1) -- limit expected date to 0-11
+				if (new_month < 0 || new_month > 11)
+					new_month = (new_month + 12) % 12;
+			} else {
+				// For magnitudes >1, move one month at a time...
+				for (var i=0; i<mag; i++)
+					// ...which might decrease the day (eg, Jan 31 to Feb 28, etc)...
+					new_date = this.moveMonth(new_date, dir);
+				// ...then reset the day, keeping it in the new month
+				new_month = new_date.getUTCMonth();
+				new_date.setUTCDate(day);
+				test = function(){ return new_month != new_date.getUTCMonth(); };
+			}
+			// Common date-resetting loop -- if date is beyond end of month, make it
+			// end of month
+			while (test()){
+				new_date.setUTCDate(--day);
+				new_date.setUTCMonth(new_month);
+			}
+			return new_date;
+		},
+
+		moveYear: function(date, dir){
+			return this.moveMonth(date, dir*12);
+		},
+
+		dateWithinRange: function(date){
+			return date >= this.o.startDate && date <= this.o.endDate;
+		},
+
+		keydown: function(e){
+			if (this.picker.is(':not(:visible)')){
+				if (e.keyCode == 27) // allow escape to hide and re-show picker
+					this.show();
+				return;
+			}
+			var dateChanged = false,
+				dir, day, month,
+				newDate, newViewDate;
+			switch(e.keyCode){
+				case 27: // escape
+					this.hide();
+					e.preventDefault();
+					break;
+				case 37: // left
+				case 39: // right
+					if (!this.o.keyboardNavigation) break;
+					dir = e.keyCode == 37 ? -1 : 1;
+					if (e.ctrlKey){
+						newDate = this.moveYear(this.date, dir);
+						newViewDate = this.moveYear(this.viewDate, dir);
+						this._trigger('changeYear', this.viewDate);
+					} else if (e.shiftKey){
+						newDate = this.moveMonth(this.date, dir);
+						newViewDate = this.moveMonth(this.viewDate, dir);
+						this._trigger('changeMonth', this.viewDate);
+					} else {
+						newDate = new Date(this.date);
+						newDate.setUTCDate(this.date.getUTCDate() + dir);
+						newViewDate = new Date(this.viewDate);
+						newViewDate.setUTCDate(this.viewDate.getUTCDate() + dir);
+					}
+					if (this.dateWithinRange(newDate)){
+						this.date = newDate;
+						this.viewDate = newViewDate;
+						this.setValue();
+						this.update();
+						e.preventDefault();
+						dateChanged = true;
+					}
+					break;
+				case 38: // up
+				case 40: // down
+					if (!this.o.keyboardNavigation) break;
+					dir = e.keyCode == 38 ? -1 : 1;
+					if (e.ctrlKey){
+						newDate = this.moveYear(this.date, dir);
+						newViewDate = this.moveYear(this.viewDate, dir);
+						this._trigger('changeYear', this.viewDate);
+					} else if (e.shiftKey){
+						newDate = this.moveMonth(this.date, dir);
+						newViewDate = this.moveMonth(this.viewDate, dir);
+						this._trigger('changeMonth', this.viewDate);
+					} else {
+						newDate = new Date(this.date);
+						newDate.setUTCDate(this.date.getUTCDate() + dir * 7);
+						newViewDate = new Date(this.viewDate);
+						newViewDate.setUTCDate(this.viewDate.getUTCDate() + dir * 7);
+					}
+					if (this.dateWithinRange(newDate)){
+						this.date = newDate;
+						this.viewDate = newViewDate;
+						this.setValue();
+						this.update();
+						e.preventDefault();
+						dateChanged = true;
+					}
+					break;
+				case 13: // enter
+					this.hide();
+					e.preventDefault();
+					break;
+				case 9: // tab
+					this.hide();
+					break;
+			}
+			if (dateChanged){
+				this._trigger('changeDate');
+				var element;
+				if (this.isInput) {
+					element = this.element;
+				} else if (this.component){
+					element = this.element.find('input');
+				}
+				if (element) {
+					element.change();
+				}
+			}
+		},
+
+		showMode: function(dir) {
+			if (dir) {
+				this.viewMode = Math.max(this.o.minViewMode, Math.min(2, this.viewMode + dir));
+			}
+			/*
+				vitalets: fixing bug of very special conditions:
+				jquery 1.7.1 + webkit + show inline datepicker in bootstrap popover.
+				Method show() does not set display css correctly and datepicker is not shown.
+				Changed to .css('display', 'block') solve the problem.
+				See https://github.com/vitalets/x-editable/issues/37
+
+				In jquery 1.7.2+ everything works fine.
+			*/
+			//this.picker.find('>div').hide().filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName).show();
+			this.picker.find('>div').hide().filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName).css('display', 'block');
+			this.updateNavArrows();
+		}
+	};
+
+	var DateRangePicker = function(element, options){
+		this.element = $(element);
+		this.inputs = $.map(options.inputs, function(i){ return i.jquery ? i[0] : i; });
+		delete options.inputs;
+
+		$(this.inputs)
+			.datepicker(options)
+			.bind('changeDate', $.proxy(this.dateUpdated, this));
+
+		this.pickers = $.map(this.inputs, function(i){ return $(i).data('datepicker'); });
+		this.updateDates();
+	};
+	DateRangePicker.prototype = {
+		updateDates: function(){
+			this.dates = $.map(this.pickers, function(i){ return i.date; });
+			this.updateRanges();
+		},
+		updateRanges: function(){
+			var range = $.map(this.dates, function(d){ return d.valueOf(); });
+			$.each(this.pickers, function(i, p){
+				p.setRange(range);
+			});
+		},
+		dateUpdated: function(e){
+			var dp = $(e.target).data('datepicker'),
+				new_date = dp.getUTCDate(),
+				i = $.inArray(e.target, this.inputs),
+				l = this.inputs.length;
+			if (i == -1) return;
+
+			if (new_date < this.dates[i]){
+				// Date being moved earlier/left
+				while (i>=0 && new_date < this.dates[i]){
+					this.pickers[i--].setUTCDate(new_date);
+				}
+			}
+			else if (new_date > this.dates[i]){
+				// Date being moved later/right
+				while (i<l && new_date > this.dates[i]){
+					this.pickers[i++].setUTCDate(new_date);
+				}
+			}
+			this.updateDates();
+		},
+		remove: function(){
+			$.map(this.pickers, function(p){ p.remove(); });
+			delete this.element.data().datepicker;
+		}
+	};
+
+	function opts_from_el(el, prefix){
+		// Derive options from element data-attrs
+		var data = $(el).data(),
+			out = {}, inkey,
+			replace = new RegExp('^' + prefix.toLowerCase() + '([A-Z])'),
+			prefix = new RegExp('^' + prefix.toLowerCase());
+		for (var key in data)
+			if (prefix.test(key)){
+				inkey = key.replace(replace, function(_,a){ return a.toLowerCase(); });
+				out[inkey] = data[key];
+			}
+		return out;
+	}
+
+	function opts_from_locale(lang){
+		// Derive options from locale plugins
+		var out = {};
+		// Check if "de-DE" style date is available, if not language should
+		// fallback to 2 letter code eg "de"
+		if (!dates[lang]) {
+			lang = lang.split('-')[0]
+			if (!dates[lang])
+				return;
+		}
+		var d = dates[lang];
+		$.each(locale_opts, function(i,k){
+			if (k in d)
+				out[k] = d[k];
+		});
+		return out;
+	}
+
+	var old = $.fn.datepicker;
+	$.fn.datepicker = function ( option ) {
+		var args = Array.apply(null, arguments);
+		args.shift();
+		var internal_return,
+			this_return;
+		this.each(function () {
+			var $this = $(this),
+				data = $this.data('datepicker'),
+				options = typeof option == 'object' && option;
+			if (!data) {
+				var elopts = opts_from_el(this, 'date'),
+					// Preliminary otions
+					xopts = $.extend({}, defaults, elopts, options),
+					locopts = opts_from_locale(xopts.language),
+					// Options priority: js args, data-attrs, locales, defaults
+					opts = $.extend({}, defaults, locopts, elopts, options);
+				if ($this.is('.input-daterange') || opts.inputs){
+					var ropts = {
+						inputs: opts.inputs || $this.find('input').toArray()
+					};
+					$this.data('datepicker', (data = new DateRangePicker(this, $.extend(opts, ropts))));
+				}
+				else{
+					$this.data('datepicker', (data = new Datepicker(this, opts)));
+				}
+			}
+			if (typeof option == 'string' && typeof data[option] == 'function') {
+				internal_return = data[option].apply(data, args);
+				if (internal_return !== undefined)
+					return false;
+			}
+		});
+		if (internal_return !== undefined)
+			return internal_return;
+		else
+			return this;
+	};
+
+	var defaults = $.fn.datepicker.defaults = {
+		autoclose: false,
+		beforeShowDay: $.noop,
+		calendarWeeks: false,
+		clearBtn: false,
+		daysOfWeekDisabled: [],
+		endDate: Infinity,
+		forceParse: true,
+		format: 'mm/dd/yyyy',
+		keyboardNavigation: true,
+		language: 'en',
+		minViewMode: 0,
+		orientation: "auto",
+		rtl: false,
+		startDate: -Infinity,
+		startView: 0,
+		todayBtn: false,
+		todayHighlight: false,
+		weekStart: 0
+	};
+	var locale_opts = $.fn.datepicker.locale_opts = [
+		'format',
+		'rtl',
+		'weekStart'
+	];
+	$.fn.datepicker.Constructor = Datepicker;
+	var dates = $.fn.datepicker.dates = {
+		en: {
+			days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
+			daysShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
+			daysMin: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"],
+			months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
+			monthsShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
+			today: "Today",
+			clear: "Clear"
+		}
+	};
+
+	var DPGlobal = {
+		modes: [
+			{
+				clsName: 'days',
+				navFnc: 'Month',
+				navStep: 1
+			},
+			{
+				clsName: 'months',
+				navFnc: 'FullYear',
+				navStep: 1
+			},
+			{
+				clsName: 'years',
+				navFnc: 'FullYear',
+				navStep: 10
+		}],
+		isLeapYear: function (year) {
+			return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0));
+		},
+		getDaysInMonth: function (year, month) {
+			return [31, (DPGlobal.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month];
+		},
+		validParts: /dd?|DD?|mm?|MM?|yy(?:yy)?/g,
+		nonpunctuation: /[^ -\/:-@\[\u3400-\u9fff-`{-~\t\n\r]+/g,
+		parseFormat: function(format){
+			// IE treats \0 as a string end in inputs (truncating the value),
+			// so it's a bad format delimiter, anyway
+			var separators = format.replace(this.validParts, '\0').split('\0'),
+				parts = format.match(this.validParts);
+			if (!separators || !separators.length || !parts || parts.length === 0){
+				throw new Error("Invalid date format.");
+			}
+			return {separators: separators, parts: parts};
+		},
+		parseDate: function(date, format, language) {
+			if (date instanceof Date) return date;
+			if (typeof format === 'string')
+				format = DPGlobal.parseFormat(format);
+			if (/^[\-+]\d+[dmwy]([\s,]+[\-+]\d+[dmwy])*$/.test(date)) {
+				var part_re = /([\-+]\d+)([dmwy])/,
+					parts = date.match(/([\-+]\d+)([dmwy])/g),
+					part, dir;
+				date = new Date();
+				for (var i=0; i<parts.length; i++) {
+					part = part_re.exec(parts[i]);
+					dir = parseInt(part[1]);
+					switch(part[2]){
+						case 'd':
+							date.setUTCDate(date.getUTCDate() + dir);
+							break;
+						case 'm':
+							date = Datepicker.prototype.moveMonth.call(Datepicker.prototype, date, dir);
+							break;
+						case 'w':
+							date.setUTCDate(date.getUTCDate() + dir * 7);
+							break;
+						case 'y':
+							date = Datepicker.prototype.moveYear.call(Datepicker.prototype, date, dir);
+							break;
+					}
+				}
+				return UTCDate(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), 0, 0, 0);
+			}
+			var parts = date && date.match(this.nonpunctuation) || [],
+				date = new Date(),
+				parsed = {},
+				setters_order = ['yyyy', 'yy', 'M', 'MM', 'm', 'mm', 'd', 'dd'],
+				setters_map = {
+					yyyy: function(d,v){ return d.setUTCFullYear(v); },
+					yy: function(d,v){ return d.setUTCFullYear(2000+v); },
+					m: function(d,v){
+						if (isNaN(d))
+							return d;
+						v -= 1;
+						while (v<0) v += 12;
+						v %= 12;
+						d.setUTCMonth(v);
+						while (d.getUTCMonth() != v)
+							d.setUTCDate(d.getUTCDate()-1);
+						return d;
+					},
+					d: function(d,v){ return d.setUTCDate(v); }
+				},
+				val, filtered, part;
+			setters_map['M'] = setters_map['MM'] = setters_map['mm'] = setters_map['m'];
+			setters_map['dd'] = setters_map['d'];
+			date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0);
+			var fparts = format.parts.slice();
+			// Remove noop parts
+			if (parts.length != fparts.length) {
+				fparts = $(fparts).filter(function(i,p){
+					return $.inArray(p, setters_order) !== -1;
+				}).toArray();
+			}
+			// Process remainder
+			if (parts.length == fparts.length) {
+				for (var i=0, cnt = fparts.length; i < cnt; i++) {
+					val = parseInt(parts[i], 10);
+					part = fparts[i];
+					if (isNaN(val)) {
+						switch(part) {
+							case 'MM':
+								filtered = $(dates[language].months).filter(function(){
+									var m = this.slice(0, parts[i].length),
+										p = parts[i].slice(0, m.length);
+									return m == p;
+								});
+								val = $.inArray(filtered[0], dates[language].months) + 1;
+								break;
+							case 'M':
+								filtered = $(dates[language].monthsShort).filter(function(){
+									var m = this.slice(0, parts[i].length),
+										p = parts[i].slice(0, m.length);
+									return m == p;
+								});
+								val = $.inArray(filtered[0], dates[language].monthsShort) + 1;
+								break;
+						}
+					}
+					parsed[part] = val;
+				}
+				for (var i=0, _date, s; i<setters_order.length; i++){
+					s = setters_order[i];
+					if (s in parsed && !isNaN(parsed[s])){
+						_date = new Date(date);
+						setters_map[s](_date, parsed[s]);
+						if (!isNaN(_date))
+							date = _date;
+					}
+				}
+			}
+			return date;
+		},
+		formatDate: function(date, format, language){
+			if (typeof format === 'string')
+				format = DPGlobal.parseFormat(format);
+			var val = {
+				d: date.getUTCDate(),
+				D: dates[language].daysShort[date.getUTCDay()],
+				DD: dates[language].days[date.getUTCDay()],
+				m: date.getUTCMonth() + 1,
+				M: dates[language].monthsShort[date.getUTCMonth()],
+				MM: dates[language].months[date.getUTCMonth()],
+				yy: date.getUTCFullYear().toString().substring(2),
+				yyyy: date.getUTCFullYear()
+			};
+			val.dd = (val.d < 10 ? '0' : '') + val.d;
+			val.mm = (val.m < 10 ? '0' : '') + val.m;
+			var date = [],
+				seps = $.extend([], format.separators);
+			for (var i=0, cnt = format.parts.length; i <= cnt; i++) {
+				if (seps.length)
+					date.push(seps.shift());
+				date.push(val[format.parts[i]]);
+			}
+			return date.join('');
+		},
+		headTemplate: '<thead>'+
+							'<tr>'+
+								'<th class="prev">&laquo;</th>'+
+								'<th colspan="5" class="datepicker-switch"></th>'+
+								'<th class="next">&raquo;</th>'+
+							'</tr>'+
+						'</thead>',
+		contTemplate: '<tbody><tr><td colspan="7"></td></tr></tbody>',
+		footTemplate: '<tfoot><tr><th colspan="7" class="today"></th></tr><tr><th colspan="7" class="clear"></th></tr></tfoot>'
+	};
+	DPGlobal.template = '<div class="datepicker">'+
+							'<div class="datepicker-days">'+
+								'<table class=" table-condensed">'+
+									DPGlobal.headTemplate+
+									'<tbody></tbody>'+
+									DPGlobal.footTemplate+
+								'</table>'+
+							'</div>'+
+							'<div class="datepicker-months">'+
+								'<table class="table-condensed">'+
+									DPGlobal.headTemplate+
+									DPGlobal.contTemplate+
+									DPGlobal.footTemplate+
+								'</table>'+
+							'</div>'+
+							'<div class="datepicker-years">'+
+								'<table class="table-condensed">'+
+									DPGlobal.headTemplate+
+									DPGlobal.contTemplate+
+									DPGlobal.footTemplate+
+								'</table>'+
+							'</div>'+
+						'</div>';
+
+	$.fn.datepicker.DPGlobal = DPGlobal;
+
+
+	/* DATEPICKER NO CONFLICT
+	* =================== */
+
+	$.fn.datepicker.noConflict = function(){
+		$.fn.datepicker = old;
+		return this;
+	};
+
+
+	/* DATEPICKER DATA-API
+	* ================== */
+
+	$(document).on(
+		'focus.datepicker.data-api click.datepicker.data-api',
+		'[data-provide="datepicker"]',
+		function(e){
+			var $this = $(this);
+			if ($this.data('datepicker')) return;
+			e.preventDefault();
+			// component click requires us to explicitly show it
+			$this.datepicker('show');
+		}
+	);
+	$(function(){
+		$('[data-provide="datepicker-inline"]').datepicker();
+	});
+
+}( window.jQuery ));

+ 15 - 0
static/cranefly/js/locales/bootstrap-datepicker.ar.js

@@ -0,0 +1,15 @@
+/**
+ * Arabic translation for bootstrap-datepicker
+ * Mohammed Alshehri <alshehri866@gmail.com>
+ */
+;(function($){
+    $.fn.datepicker.dates['ar'] = {
+        days: ["الأحد", "الاثنين", "الثلاثاء", "الأربعاء", "الخميس", "الجمعة", "السبت", "الأحد"],
+        daysShort: ["أحد", "اثنين", "ثلاثاء", "أربعاء", "خميس", "جمعة", "سبت", "أحد"],
+        daysMin: ["ح", "ن", "ث", "ع", "خ", "ج", "س", "ح"],
+        months: ["يناير", "فبراير", "مارس", "أبريل", "مايو", "يونيو", "يوليو", "أغسطس", "سبتمبر", "أكتوبر", "نوفمبر", "ديسمبر"],
+        monthsShort: ["يناير", "فبراير", "مارس", "أبريل", "مايو", "يونيو", "يوليو", "أغسطس", "سبتمبر", "أكتوبر", "نوفمبر", "ديسمبر"],
+        today: "هذا اليوم",
+        rtl: true
+    };
+}(jQuery));

+ 14 - 0
static/cranefly/js/locales/bootstrap-datepicker.bg.js

@@ -0,0 +1,14 @@
+/**
+ * Bulgarian translation for bootstrap-datepicker
+ * Apostol Apostolov <apostol.s.apostolov@gmail.com>
+ */
+;(function($){
+	$.fn.datepicker.dates['bg'] = {
+		days: ["Неделя", "Понеделник", "Вторник", "Сряда", "Четвъртък", "Петък", "Събота", "Неделя"],
+		daysShort: ["Нед", "Пон", "Вто", "Сря", "Чет", "Пет", "Съб", "Нед"],
+		daysMin: ["Н", "П", "В", "С", "Ч", "П", "С", "Н"],
+		months: ["Януари", "Февруари", "Март", "Април", "Май", "Юни", "Юли", "Август", "Септември", "Октомври", "Ноември", "Декември"],
+		monthsShort: ["Ян", "Фев", "Мар", "Апр", "Май", "Юни", "Юли", "Авг", "Сеп", "Окт", "Ное", "Дек"],
+		today: "днес"
+	};
+}(jQuery));

+ 14 - 0
static/cranefly/js/locales/bootstrap-datepicker.ca.js

@@ -0,0 +1,14 @@
+/**
+ * Catalan translation for bootstrap-datepicker
+ * J. Garcia <jogaco.en@gmail.com>
+ */
+;(function($){
+	$.fn.datepicker.dates['ca'] = {
+		days: ["Diumenge", "Dilluns", "Dimarts", "Dimecres", "Dijous", "Divendres", "Dissabte", "Diumenge"],
+		daysShort: ["Diu",  "Dil", "Dmt", "Dmc", "Dij", "Div", "Dis", "Diu"],
+		daysMin: ["dg", "dl", "dt", "dc", "dj", "dv", "ds", "dg"],
+		months: ["Gener", "Febrer", "Març", "Abril", "Maig", "Juny", "Juliol", "Agost", "Setembre", "Octubre", "Novembre", "Desembre"],
+		monthsShort: ["Gen", "Feb", "Mar", "Abr", "Mai", "Jun", "Jul", "Ago", "Set", "Oct", "Nov", "Des"],
+		today: "Avui"
+	};
+}(jQuery));

+ 15 - 0
static/cranefly/js/locales/bootstrap-datepicker.cs.js

@@ -0,0 +1,15 @@
+/**
+ * Czech translation for bootstrap-datepicker
+ * Matěj Koubík <matej@koubik.name>
+ * Fixes by Michal Remiš <michal.remis@gmail.com>
+ */
+;(function($){
+	$.fn.datepicker.dates['cs'] = {
+		days: ["Neděle", "Pondělí", "Úterý", "Středa", "Čtvrtek", "Pátek", "Sobota", "Neděle"],
+		daysShort: ["Ned", "Pon", "Úte", "Stř", "Čtv", "Pát", "Sob", "Ned"],
+		daysMin: ["Ne", "Po", "Út", "St", "Čt", "Pá", "So", "Ne"],
+		months: ["Leden", "Únor", "Březen", "Duben", "Květen", "Červen", "Červenec", "Srpen", "Září", "Říjen", "Listopad", "Prosinec"],
+		monthsShort: ["Led", "Úno", "Bře", "Dub", "Kvě", "Čer", "Čnc", "Srp", "Zář", "Říj", "Lis", "Pro"],
+		today: "Dnes"
+	};
+}(jQuery));

+ 14 - 0
static/cranefly/js/locales/bootstrap-datepicker.cy.js

@@ -0,0 +1,14 @@
+/**
+ * Welsh translation for bootstrap-datepicker
+ * S. Morris <s.morris@bangor.ac.uk>
+ */
+;(function($){
+	$.fn.datepicker.dates['cy'] = {
+		days: ["Sul", "Llun", "Mawrth", "Mercher", "Iau", "Gwener", "Sadwrn", "Sul"],
+		daysShort: ["Sul", "Llu", "Maw", "Mer", "Iau", "Gwe", "Sad", "Sul"],
+		daysMin: ["Su", "Ll", "Ma", "Me", "Ia", "Gwe", "Sa", "Su"],
+		months: ["Ionawr", "Chewfror", "Mawrth", "Ebrill", "Mai", "Mehefin", "Gorfennaf", "Awst", "Medi", "Hydref", "Tachwedd", "Rhagfyr"],
+		monthsShort: ["Ion", "Chw", "Maw", "Ebr", "Mai", "Meh", "Gor", "Aws", "Med", "Hyd", "Tach", "Rha"],
+		today: "Heddiw"
+	};
+}(jQuery));

+ 14 - 0
static/cranefly/js/locales/bootstrap-datepicker.da.js

@@ -0,0 +1,14 @@
+/**
+ * Danish translation for bootstrap-datepicker
+ * Christian Pedersen <http://github.com/chripede>
+ */
+;(function($){
+	$.fn.datepicker.dates['da'] = {
+		days: ["Søndag", "Mandag", "Tirsdag", "Onsdag", "Torsdag", "Fredag", "Lørdag", "Søndag"],
+		daysShort: ["Søn", "Man", "Tir", "Ons", "Tor", "Fre", "Lør", "Søn"],
+		daysMin: ["Sø", "Ma", "Ti", "On", "To", "Fr", "Lø", "Sø"],
+		months: ["Januar", "Februar", "Marts", "April", "Maj", "Juni", "Juli", "August", "September", "Oktober", "November", "December"],
+		monthsShort: ["Jan", "Feb", "Mar", "Apr", "Maj", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dec"],
+		today: "I Dag"
+	};
+}(jQuery));

+ 17 - 0
static/cranefly/js/locales/bootstrap-datepicker.de.js

@@ -0,0 +1,17 @@
+/**
+ * German translation for bootstrap-datepicker
+ * Sam Zurcher <sam@orelias.ch>
+ */
+;(function($){
+	$.fn.datepicker.dates['de'] = {
+		days: ["Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag", "Sonntag"],
+		daysShort: ["Son", "Mon", "Die", "Mit", "Don", "Fre", "Sam", "Son"],
+		daysMin: ["So", "Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"],
+		months: ["Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"],
+		monthsShort: ["Jan", "Feb", "Mär", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez"],
+		today: "Heute",
+		clear: "Löschen",
+		weekStart: 1,
+		format: "dd.mm.yyyy"
+	};
+}(jQuery));

+ 13 - 0
static/cranefly/js/locales/bootstrap-datepicker.el.js

@@ -0,0 +1,13 @@
+/**
+* Greek translation for bootstrap-datepicker
+*/
+;(function($){
+  $.fn.datepicker.dates['el'] = {
+    days: ["Κυριακή", "Δευτέρα", "Τρίτη", "Τετάρτη", "Πέμπτη", "Παρασκευή", "Σάββατο", "Κυριακή"],
+    daysShort: ["Κυρ", "Δευ", "Τρι", "Τετ", "Πεμ", "Παρ", "Σαβ", "Κυρ"],
+    daysMin: ["Κυ", "Δε", "Τρ", "Τε", "Πε", "Πα", "Σα", "Κυ"],
+    months: ["Ιανουάριος", "Φεβρουάριος", "Μάρτιος", "Απρίλιος", "Μάιος", "Ιούνιος", "Ιούλιος", "Αύγουστος", "Σεπτέμβριος", "Οκτώβριος", "Νοέμβριος", "Δεκέμβριος"],
+    monthsShort: ["Ιαν", "Φεβ", "Μαρ", "Απρ", "Μάι", "Ιουν", "Ιουλ", "Αυγ", "Σεπ", "Οκτ", "Νοε", "Δεκ"],
+    today: "Σήμερα"
+  };
+}(jQuery));

+ 14 - 0
static/cranefly/js/locales/bootstrap-datepicker.es.js

@@ -0,0 +1,14 @@
+/**
+ * Spanish translation for bootstrap-datepicker
+ * Bruno Bonamin <bruno.bonamin@gmail.com>
+ */
+;(function($){
+	$.fn.datepicker.dates['es'] = {
+		days: ["Domingo", "Lunes", "Martes", "Miércoles", "Jueves", "Viernes", "Sábado", "Domingo"],
+		daysShort: ["Dom", "Lun", "Mar", "Mié", "Jue", "Vie", "Sáb", "Dom"],
+		daysMin: ["Do", "Lu", "Ma", "Mi", "Ju", "Vi", "Sa", "Do"],
+		months: ["Enero", "Febrero", "Marzo", "Abril", "Mayo", "Junio", "Julio", "Agosto", "Septiembre", "Octubre", "Noviembre", "Diciembre"],
+		monthsShort: ["Ene", "Feb", "Mar", "Abr", "May", "Jun", "Jul", "Ago", "Sep", "Oct", "Nov", "Dic"],
+		today: "Hoy"
+	};
+}(jQuery));

+ 14 - 0
static/cranefly/js/locales/bootstrap-datepicker.et.js

@@ -0,0 +1,14 @@
+/**
+ * Estonian translation for bootstrap-datepicker
+ * Ando Roots <https://github.com/anroots>
+ */
+;(function($){
+	$.fn.datepicker.dates['et'] = {
+		days: ["Pühapäev", "Esmaspäev", "Teisipäev", "Kolmapäev", "Neljapäev", "Reede", "Laupäev", "Pühapäev"],
+		daysShort: ["Püh", "Esm", "Tei", "Kol", "Nel", "Ree", "Lau", "Sun"],
+		daysMin: ["P", "E", "T", "K", "N", "R", "L", "P"],
+		months: ["Jaanuar", "Veebruar", "Märts", "Aprill", "Mai", "Juuni", "Juuli", "August", "September", "Oktoober", "November", "Detsember"],
+		monthsShort: ["Jaan", "Veeb", "Märts", "Apr", "Mai", "Juuni", "Juuli", "Aug", "Sept", "Okt", "Nov", "Dets"],
+		today: "Täna"
+	};
+}(jQuery));

+ 16 - 0
static/cranefly/js/locales/bootstrap-datepicker.fi.js

@@ -0,0 +1,16 @@
+/**
+ * Finnish translation for bootstrap-datepicker
+ * Jaakko Salonen <https://github.com/jsalonen>
+ */
+;(function($){
+	$.fn.datepicker.dates['fi'] = {
+		days: ["sunnuntai", "maanantai", "tiistai", "keskiviikko", "torstai", "perjantai", "lauantai", "sunnuntai"],
+		daysShort: ["sun", "maa", "tii", "kes", "tor", "per", "lau", "sun"],
+		daysMin: ["su", "ma", "ti", "ke", "to", "pe", "la", "su"],
+		months: ["tammikuu", "helmikuu", "maaliskuu", "huhtikuu", "toukokuu", "kesäkuu", "heinäkuu", "elokuu", "syyskuu", "lokakuu", "marraskuu", "joulukuu"],
+		monthsShort: ["tam", "hel", "maa", "huh", "tou", "kes", "hei", "elo", "syy", "lok", "mar", "jou"],
+		today: "tänään",
+		weekStart: 1,
+		format: "d.m.yyyy"
+	};
+}(jQuery));

+ 17 - 0
static/cranefly/js/locales/bootstrap-datepicker.fr.js

@@ -0,0 +1,17 @@
+/**
+ * French translation for bootstrap-datepicker
+ * Nico Mollet <nico.mollet@gmail.com>
+ */
+;(function($){
+	$.fn.datepicker.dates['fr'] = {
+		days: ["Dimanche", "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi", "Dimanche"],
+		daysShort: ["Dim", "Lun", "Mar", "Mer", "Jeu", "Ven", "Sam", "Dim"],
+		daysMin: ["D", "L", "Ma", "Me", "J", "V", "S", "D"],
+		months: ["Janvier", "Février", "Mars", "Avril", "Mai", "Juin", "Juillet", "Août", "Septembre", "Octobre", "Novembre", "Décembre"],
+		monthsShort: ["Jan", "Fev", "Mar", "Avr", "Mai", "Jui", "Jul", "Aou", "Sep", "Oct", "Nov", "Dec"],
+		today: "Aujourd'hui",
+		clear: "Effacer",
+		weekStart: 1,
+		format: "dd/mm/yyyy"
+	};
+}(jQuery));

+ 15 - 0
static/cranefly/js/locales/bootstrap-datepicker.he.js

@@ -0,0 +1,15 @@
+/**
+ * Hebrew translation for bootstrap-datepicker
+ * Sagie Maoz <sagie@maoz.info>
+ */
+;(function($){
+  $.fn.datepicker.dates['he'] = {
+      days: ["ראשון", "שני", "שלישי", "רביעי", "חמישי", "שישי", "שבת", "ראשון"],
+      daysShort: ["א", "ב", "ג", "ד", "ה", "ו", "ש", "א"],
+      daysMin: ["א", "ב", "ג", "ד", "ה", "ו", "ש", "א"],
+      months: ["ינואר", "פברואר", "מרץ", "אפריל", "מאי", "יוני", "יולי", "אוגוסט", "ספטמבר", "אוקטובר", "נובמבר", "דצמבר"],
+      monthsShort: ["ינו", "פבר", "מרץ", "אפר", "מאי", "יונ", "יול", "אוג", "ספט", "אוק", "נוב", "דצמ"],
+      today: "היום",
+      rtl: true
+  };
+}(jQuery));

+ 13 - 0
static/cranefly/js/locales/bootstrap-datepicker.hr.js

@@ -0,0 +1,13 @@
+/**
+ * Croatian localisation
+ */
+;(function($){
+	$.fn.datepicker.dates['hr'] = {
+		days: ["Nedjelja", "Ponedjelja", "Utorak", "Srijeda", "Četrtak", "Petak", "Subota", "Nedjelja"],
+		daysShort: ["Ned", "Pon", "Uto", "Srr", "Čet", "Pet", "Sub", "Ned"],
+		daysMin: ["Ne", "Po", "Ut", "Sr", "Če", "Pe", "Su", "Ne"],
+		months: ["Siječanj", "Veljača", "Ožujak", "Travanj", "Svibanj", "Lipanj", "Srpanj", "Kolovoz", "Rujan", "Listopad", "Studeni", "Prosinac"],
+		monthsShort: ["Sije", "Velj", "Ožu", "Tra", "Svi", "Lip", "Jul", "Kol", "Ruj", "Lis", "Stu", "Pro"],
+		today: "Danas"
+	};
+}(jQuery));

+ 16 - 0
static/cranefly/js/locales/bootstrap-datepicker.hu.js

@@ -0,0 +1,16 @@
+/**
+ * Hungarian translation for bootstrap-datepicker
+ * Sotus László <lacisan@gmail.com>
+ */
+;(function($){
+  $.fn.datepicker.dates['hu'] = {
+		days: ["Vasárnap", "Hétfő", "Kedd", "Szerda", "Csütörtök", "Péntek", "Szombat", "Vasárnap"],
+		daysShort: ["Vas", "Hét", "Ked", "Sze", "Csü", "Pén", "Szo", "Vas"],
+		daysMin: ["Va", "Hé", "Ke", "Sz", "Cs", "Pé", "Sz", "Va"],
+		months: ["Január", "Február", "Március", "Április", "Május", "Június", "Július", "Augusztus", "Szeptember", "Október", "November", "December"],
+		monthsShort: ["Jan", "Feb", "Már", "Ápr", "Máj", "Jún", "Júl", "Aug", "Sze", "Okt", "Nov", "Dec"],
+		today: "Ma",
+		weekStart: 1,
+		format: "yyyy.mm.dd"
+	};
+}(jQuery));

+ 15 - 0
static/cranefly/js/locales/bootstrap-datepicker.id.js

@@ -0,0 +1,15 @@
+/**
+ * Bahasa translation for bootstrap-datepicker
+ * Azwar Akbar <azwar.akbar@gmail.com>
+ */
+;(function($){
+	$.fn.datepicker.dates['id'] = {
+		days: ["Minggu", "Senin", "Selasa", "Rabu", "Kamis", "Jumat", "Sabtu", "Minggu"],
+		daysShort: ["Mgu", "Sen", "Sel", "Rab", "Kam", "Jum", "Sab", "Mgu"],
+		daysMin: ["Mg", "Sn", "Sl", "Ra", "Ka", "Ju", "Sa", "Mg"],
+		months: ["Januari", "Februari", "Maret", "April", "Mei", "Juni", "Juli", "Agustus", "September", "Oktober", "November", "Desember"],
+		monthsShort: ["Jan", "Feb", "Mar", "Apr", "Mei", "Jun", "Jul", "Ags", "Sep", "Okt", "Nov", "Des"],
+		today: "Hari Ini",
+		clear: "Kosongkan"
+	};
+}(jQuery));

+ 14 - 0
static/cranefly/js/locales/bootstrap-datepicker.is.js

@@ -0,0 +1,14 @@
+/**
+ * Icelandic translation for bootstrap-datepicker
+ * Hinrik Örn Sigurðsson <hinrik.sig@gmail.com>
+ */
+;(function($){
+	$.fn.datepicker.dates['is'] = {
+		days: ["Sunnudagur", "Mánudagur", "Þriðjudagur", "Miðvikudagur", "Fimmtudagur", "Föstudagur", "Laugardagur", "Sunnudagur"],
+		daysShort: ["Sun", "Mán", "Þri", "Mið", "Fim", "Fös", "Lau", "Sun"],
+		daysMin: ["Su", "Má", "Þr", "Mi", "Fi", "Fö", "La", "Su"],
+		months: ["Janúar", "Febrúar", "Mars", "Apríl", "Maí", "Júní", "Júlí", "Ágúst", "September", "Október", "Nóvember", "Desember"],
+		monthsShort: ["Jan", "Feb", "Mar", "Apr", "Maí", "Jún", "Júl", "Ágú", "Sep", "Okt", "Nóv", "Des"],
+		today: "Í Dag"
+	};
+}(jQuery));

+ 16 - 0
static/cranefly/js/locales/bootstrap-datepicker.it.js

@@ -0,0 +1,16 @@
+/**
+ * Italian translation for bootstrap-datepicker
+ * Enrico Rubboli <rubboli@gmail.com>
+ */
+;(function($){
+	$.fn.datepicker.dates['it'] = {
+		days: ["Domenica", "Lunedì", "Martedì", "Mercoledì", "Giovedì", "Venerdì", "Sabato", "Domenica"],
+		daysShort: ["Dom", "Lun", "Mar", "Mer", "Gio", "Ven", "Sab", "Dom"],
+		daysMin: ["Do", "Lu", "Ma", "Me", "Gi", "Ve", "Sa", "Do"],
+		months: ["Gennaio", "Febbraio", "Marzo", "Aprile", "Maggio", "Giugno", "Luglio", "Agosto", "Settembre", "Ottobre", "Novembre", "Dicembre"],
+		monthsShort: ["Gen", "Feb", "Mar", "Apr", "Mag", "Giu", "Lug", "Ago", "Set", "Ott", "Nov", "Dic"],
+		today: "Oggi",
+		weekStart: 1,
+		format: "dd/mm/yyyy"
+	};
+}(jQuery));

+ 15 - 0
static/cranefly/js/locales/bootstrap-datepicker.ja.js

@@ -0,0 +1,15 @@
+/**
+ * Japanese translation for bootstrap-datepicker
+ * Norio Suzuki <https://github.com/suzuki/>
+ */
+;(function($){
+	$.fn.datepicker.dates['ja'] = {
+		days: ["日曜", "月曜", "火曜", "水曜", "木曜", "金曜", "土曜", "日曜"],
+		daysShort: ["日", "月", "火", "水", "木", "金", "土", "日"],
+		daysMin: ["日", "月", "火", "水", "木", "金", "土", "日"],
+		months: ["1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月"],
+		monthsShort: ["1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月"],
+		today: "今日",
+		format: "yyyy/mm/dd"
+	};
+}(jQuery));

+ 15 - 0
static/cranefly/js/locales/bootstrap-datepicker.ka.js

@@ -0,0 +1,15 @@
+/**
+ * Georgian translation for bootstrap-datepicker
+ * Levan Melikishvili <levani0101@yahoo.com>
+ */
+;(function($){
+    $.fn.datepicker.dates['ka'] = {
+        days: ["კვირა", "ორშაბათი", "სამშაბათი", "ოთხშაბათი", "ხუთშაბათი", "პარასკევი", "შაბათი", "კვირა"],
+        daysShort: ["კვი", "ორშ", "სამ", "ოთხ", "ხუთ", "პარ", "შაბ", "კვი"],
+        daysMin: ["კვ", "ორ", "სა", "ოთ", "ხუ", "პა", "შა", "კვ"],
+        months: ["იანვარი", "თებერვალი", "მარტი", "აპრილი", "მაისი", "ივნისი", "ივლისი", "აგვისტო", "სექტემბერი", "ოქტომები", "ნოემბერი", "დეკემბერი"],
+        monthsShort: ["იან", "თებ", "მარ", "აპრ", "მაი", "ივნ", "ივლ", "აგვ", "სექ", "ოქტ", "ნოე", "დეკ"],
+        today: "დღეს",
+        clear: "გასუფთავება"
+    };
+}(jQuery));

+ 13 - 0
static/cranefly/js/locales/bootstrap-datepicker.kr.js

@@ -0,0 +1,13 @@
+/**
+ * Korean translation for bootstrap-datepicker
+ * Gu Youn <http://github.com/guyoun>
+ */
+;(function($){
+	$.fn.datepicker.dates['kr'] = {
+		days: ["일요일", "월요일", "화요일", "수요일", "목요일", "금요일", "토요일", "일요일"],
+		daysShort: ["일", "월", "화", "수", "목", "금", "토", "일"],
+		daysMin: ["일", "월", "화", "수", "목", "금", "토", "일"],
+		months: ["1월", "2월", "3월", "4월", "5월", "6월", "7월", "8월", "9월", "10월", "11월", "12월"],
+		monthsShort: ["1월", "2월", "3월", "4월", "5월", "6월", "7월", "8월", "9월", "10월", "11월", "12월"]
+	};
+}(jQuery));

+ 16 - 0
static/cranefly/js/locales/bootstrap-datepicker.lt.js

@@ -0,0 +1,16 @@
+/**
+ * Lithuanian translation for bootstrap-datepicker
+ * Šarūnas Gliebus <ssharunas@yahoo.co.uk>
+ */
+
+;(function($){
+    $.fn.datepicker.dates['lt'] = {
+        days: ["Sekmadienis", "Pirmadienis", "Antradienis", "Trečiadienis", "Ketvirtadienis", "Penktadienis", "Šeštadienis", "Sekmadienis"],
+        daysShort: ["S", "Pr", "A", "T", "K", "Pn", "Š", "S"],
+        daysMin: ["Sk", "Pr", "An", "Tr", "Ke", "Pn", "Št", "Sk"],
+        months: ["Sausis", "Vasaris", "Kovas", "Balandis", "Gegužė", "Birželis", "Liepa", "Rugpjūtis", "Rugsėjis", "Spalis", "Lapkritis", "Gruodis"],
+        monthsShort: ["Sau", "Vas", "Kov", "Bal", "Geg", "Bir", "Lie", "Rugp", "Rugs", "Spa", "Lap", "Gru"],
+        today: "Šiandien",
+        weekStart: 1
+    };
+}(jQuery));

+ 16 - 0
static/cranefly/js/locales/bootstrap-datepicker.lv.js

@@ -0,0 +1,16 @@
+/**
+ * Latvian translation for bootstrap-datepicker
+ * Artis Avotins <artis@apit.lv>
+ */
+
+;(function($){
+    $.fn.datepicker.dates['lv'] = {
+        days: ["Svētdiena", "Pirmdiena", "Otrdiena", "Trešdiena", "Ceturtdiena", "Piektdiena", "Sestdiena", "Svētdiena"],
+        daysShort: ["Sv", "P", "O", "T", "C", "Pk", "S", "Sv"],
+        daysMin: ["Sv", "Pr", "Ot", "Tr", "Ce", "Pk", "St", "Sv"],
+        months: ["Janvāris", "Februāris", "Marts", "Aprīlis", "Maijs", "Jūnijs", "Jūlijs", "Augusts", "Septembris", "Oktobris", "Novembris", "Decembris"],
+        monthsShort: ["Jan", "Feb", "Mar", "Apr", "Mai", "Jūn", "Jūl", "Aug", "Sep", "Okt", "Nov", "Dec."],
+        today: "Šodien",
+        weekStart: 1
+    };
+}(jQuery));

+ 14 - 0
static/cranefly/js/locales/bootstrap-datepicker.mk.js

@@ -0,0 +1,14 @@
+/**
+ * Macedonian translation for bootstrap-datepicker
+ * Marko Aleksic <psybaron@gmail.com>
+ */
+;(function($){
+    $.fn.datepicker.dates['mk'] = {
+        days: ["Недела", "Понеделник", "Вторник", "Среда", "Четврток", "Петок", "Сабота", "Недела"],
+        daysShort: ["Нед", "Пон", "Вто", "Сре", "Чет", "Пет", "Саб", "Нед"],
+        daysMin: ["Не", "По", "Вт", "Ср", "Че", "Пе", "Са", "Не"],
+        months: ["Јануари", "Февруари", "Март", "Април", "Мај", "Јуни", "Јули", "Август", "Септември", "Октомври", "Ноември", "Декември"],
+        monthsShort: ["Јан", "Фев", "Мар", "Апр", "Мај", "Јун", "Јул", "Авг", "Сеп", "Окт", "Ное", "Дек"],
+        today: "Денес"
+    };
+}(jQuery));

+ 14 - 0
static/cranefly/js/locales/bootstrap-datepicker.ms.js

@@ -0,0 +1,14 @@
+/**
+ * Malay translation for bootstrap-datepicker
+ * Ateman Faiz <noorulfaiz@gmail.com>
+ */
+;(function($){
+	$.fn.datepicker.dates['ms'] = {
+		days: ["Ahad", "Isnin", "Selasa", "Rabu", "Khamis", "Jumaat", "Sabtu", "Ahad"],
+		daysShort: ["Aha", "Isn", "Sel", "Rab", "Kha", "Jum", "Sab", "Aha"],
+		daysMin: ["Ah", "Is", "Se", "Ra", "Kh", "Ju", "Sa", "Ah"],
+		months: ["Januari", "Februari", "Mac", "April", "Mei", "Jun", "Julai", "Ogos", "September", "Oktober", "November", "Disember"],
+		monthsShort: ["Jan", "Feb", "Mar", "Apr", "Mei", "Jun", "Jul", "Ogo", "Sep", "Okt", "Nov", "Dis"],
+		today: "Hari Ini"
+	};
+}(jQuery));

+ 14 - 0
static/cranefly/js/locales/bootstrap-datepicker.nb.js

@@ -0,0 +1,14 @@
+/**
+ * Norwegian (bokmål) translation for bootstrap-datepicker
+ * Fredrik Sundmyhr <http://github.com/fsundmyhr>
+ */
+;(function($){
+	$.fn.datepicker.dates['nb'] = {
+		days: ["Søndag", "Mandag", "Tirsdag", "Onsdag", "Torsdag", "Fredag", "Lørdag", "Søndag"],
+		daysShort: ["Søn", "Man", "Tir", "Ons", "Tor", "Fre", "Lør", "Søn"],
+		daysMin: ["Sø", "Ma", "Ti", "On", "To", "Fr", "Lø", "Sø"],
+		months: ["Januar", "Februar", "Mars", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Desember"],
+		monthsShort: ["Jan", "Feb", "Mar", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Des"],
+		today: "I Dag"
+	};
+}(jQuery));

+ 16 - 0
static/cranefly/js/locales/bootstrap-datepicker.nl-BE.js

@@ -0,0 +1,16 @@
+/**
+ * Belgium-Dutch translation for bootstrap-datepicker
+ * Julien Poulin <poulin_julien@hotmail.com>
+ */
+; (function ($) {
+  $.fn.datepicker.dates['nl-BE'] = {
+    days: ["Zondag", "Maandag", "Dinsdag", "Woensdag", "Donderdag", "Vrijdag", "Zaterdag", "Zondag"],
+    daysShort: ["Zo", "Ma", "Di", "Wo", "Do", "Vr", "Za", "Zo"],
+    daysMin: ["Zo", "Ma", "Di", "Wo", "Do", "Vr", "Za", "Zo"],
+    months: ["Januari", "Februari", "Maart", "April", "Mei", "Juni", "Juli", "Augustus", "September", "Oktober", "November", "December"],
+    monthsShort: ["Jan", "Feb", "Mrt", "Apr", "Mei", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dec"],
+    today: "Vandaag",
+    weekStart: 1,
+    format: "dd/mm/yyyy"
+  };
+}(jQuery));

+ 14 - 0
static/cranefly/js/locales/bootstrap-datepicker.nl.js

@@ -0,0 +1,14 @@
+/**
+ * Dutch translation for bootstrap-datepicker
+ * Reinier Goltstein <mrgoltstein@gmail.com>
+ */
+;(function($){
+	$.fn.datepicker.dates['nl'] = {
+		days: ["Zondag", "Maandag", "Dinsdag", "Woensdag", "Donderdag", "Vrijdag", "Zaterdag", "Zondag"],
+		daysShort: ["Zo", "Ma", "Di", "Wo", "Do", "Vr", "Za", "Zo"],
+		daysMin: ["Zo", "Ma", "Di", "Wo", "Do", "Vr", "Za", "Zo"],
+		months: ["Januari", "Februari", "Maart", "April", "Mei", "Juni", "Juli", "Augustus", "September", "Oktober", "November", "December"],
+		monthsShort: ["Jan", "Feb", "Mrt", "Apr", "Mei", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dec"],
+		today: "Vandaag"
+	};
+}(jQuery));

+ 15 - 0
static/cranefly/js/locales/bootstrap-datepicker.no.js

@@ -0,0 +1,15 @@
+/**
+ *  Norwegian translation for bootstrap-datepicker
+ **/
+;(function($){
+  $.fn.datepicker.dates['no'] = {
+    days: ['Søndag','Mandag','Tirsdag','Onsdag','Torsdag','Fredag','Lørdag'],
+    daysShort: ['Søn','Man','Tir','Ons','Tor','Fre','Lør'],
+    daysMin: ['Sø','Ma','Ti','On','To','Fr','Lø'],
+    months: ['Januar','Februar','Mars','April','Mai','Juni','Juli','August','September','Oktober','November','Desember'],
+    monthsShort: ['Jan','Feb','Mar','Apr','Mai','Jun','Jul','Aug','Sep','Okt','Nov','Des'],
+    today: 'I dag',
+    clear: 'Nullstill',
+    weekStart: 0
+  };
+}(jQuery));

+ 15 - 0
static/cranefly/js/locales/bootstrap-datepicker.pl.js

@@ -0,0 +1,15 @@
+/**
+ * Polish translation for bootstrap-datepicker
+ * Robert <rtpm@gazeta.pl>
+ */
+;(function($){
+        $.fn.datepicker.dates['pl'] = {
+                days: ["Niedziela", "Poniedziałek", "Wtorek", "Środa", "Czwartek", "Piątek", "Sobota", "Niedziela"],
+                daysShort: ["Nie", "Pn", "Wt", "Śr", "Czw", "Pt", "So", "Nie"],
+                daysMin: ["N", "Pn", "Wt", "Śr", "Cz", "Pt", "So", "N"],
+                months: ["Styczeń", "Luty", "Marzec", "Kwiecień", "Maj", "Czerwiec", "Lipiec", "Sierpień", "Wrzesień", "Październik", "Listopad", "Grudzień"],
+                monthsShort: ["Sty", "Lu", "Mar", "Kw", "Maj", "Cze", "Lip", "Sie", "Wrz", "Pa", "Lis", "Gru"],
+                today: "Dzisiaj",
+                weekStart: 1
+        };
+}(jQuery));

+ 15 - 0
static/cranefly/js/locales/bootstrap-datepicker.pt-BR.js

@@ -0,0 +1,15 @@
+/**
+ * Brazilian translation for bootstrap-datepicker
+ * Cauan Cabral <cauan@radig.com.br>
+ */
+;(function($){
+	$.fn.datepicker.dates['pt-BR'] = {
+		days: ["Domingo", "Segunda", "Terça", "Quarta", "Quinta", "Sexta", "Sábado", "Domingo"],
+		daysShort: ["Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sáb", "Dom"],
+		daysMin: ["Do", "Se", "Te", "Qu", "Qu", "Se", "Sa", "Do"],
+		months: ["Janeiro", "Fevereiro", "Março", "Abril", "Maio", "Junho", "Julho", "Agosto", "Setembro", "Outubro", "Novembro", "Dezembro"],
+		monthsShort: ["Jan", "Fev", "Mar", "Abr", "Mai", "Jun", "Jul", "Ago", "Set", "Out", "Nov", "Dez"],
+		today: "Hoje",
+		clear: "Limpar"
+	};
+}(jQuery));

+ 16 - 0
static/cranefly/js/locales/bootstrap-datepicker.pt.js

@@ -0,0 +1,16 @@
+/**
+ * Portuguese translation for bootstrap-datepicker
+ * Original code: Cauan Cabral <cauan@radig.com.br>
+ * Tiago Melo <tiago.blackcode@gmail.com>
+ */
+;(function($){
+	$.fn.datepicker.dates['pt'] = {
+		days: ["Domingo", "Segunda", "Terça", "Quarta", "Quinta", "Sexta", "Sábado", "Domingo"],
+		daysShort: ["Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sáb", "Dom"],
+		daysMin: ["Do", "Se", "Te", "Qu", "Qu", "Se", "Sa", "Do"],
+		months: ["Janeiro", "Fevereiro", "Março", "Abril", "Maio", "Junho", "Julho", "Agosto", "Setembro", "Outubro", "Novembro", "Dezembro"],
+		monthsShort: ["Jan", "Fev", "Mar", "Abr", "Mai", "Jun", "Jul", "Ago", "Set", "Out", "Nov", "Dez"],
+		today: "Hoje",
+		clear: "Limpar"
+	};
+}(jQuery));

+ 15 - 0
static/cranefly/js/locales/bootstrap-datepicker.ro.js

@@ -0,0 +1,15 @@
+/**
+ * Romanian translation for bootstrap-datepicker
+ * Cristian Vasile <cristi.mie@gmail.com>
+ */
+;(function($){
+	$.fn.datepicker.dates['ro'] = {
+		days: ["Duminică", "Luni", "Marţi", "Miercuri", "Joi", "Vineri", "Sâmbătă", "Duminică"],
+		daysShort: ["Dum", "Lun", "Mar", "Mie", "Joi", "Vin", "Sâm", "Dum"],
+		daysMin: ["Du", "Lu", "Ma", "Mi", "Jo", "Vi", "Sâ", "Du"],
+		months: ["Ianuarie", "Februarie", "Martie", "Aprilie", "Mai", "Iunie", "Iulie", "August", "Septembrie", "Octombrie", "Noiembrie", "Decembrie"],
+		monthsShort: ["Ian", "Feb", "Mar", "Apr", "Mai", "Iun", "Iul", "Aug", "Sep", "Oct", "Nov", "Dec"],
+		today: "Astăzi",
+		weekStart: 1
+	};
+}(jQuery));

+ 14 - 0
static/cranefly/js/locales/bootstrap-datepicker.rs-latin.js

@@ -0,0 +1,14 @@
+/**
+ * Serbian latin translation for bootstrap-datepicker
+ * Bojan Milosavlević <milboj@gmail.com>
+ */
+;(function($){
+	$.fn.datepicker.dates['rs-latin'] = {
+		days: ["Nedelja","Ponedeljak", "Utorak", "Sreda", "Četvrtak", "Petak", "Subota", "Nedelja"],
+		daysShort: ["Ned", "Pon", "Uto", "Sre", "Čet", "Pet", "Sub", "Ned"],
+		daysMin: ["N", "Po", "U", "Sr", "Č", "Pe", "Su", "N"],
+		months: ["Januar", "Februar", "Mart", "April", "Maj", "Jun", "Jul", "Avgust", "Septembar", "Oktobar", "Novembar", "Decembar"],
+		monthsShort: ["Jan", "Feb", "Mar", "Apr", "Maj", "Jun", "Jul", "Avg", "Sep", "Okt", "Nov", "Dec"],
+		today: "Danas"
+	};
+}(jQuery));

+ 14 - 0
static/cranefly/js/locales/bootstrap-datepicker.rs.js

@@ -0,0 +1,14 @@
+/**
+ * Serbian cyrillic translation for bootstrap-datepicker
+ * Bojan Milosavlević <milboj@gmail.com>
+ */
+;(function($){
+	$.fn.datepicker.dates['rs'] = {
+		days: ["Недеља","Понедељак", "Уторак", "Среда", "Четвртак", "Петак", "Субота", "Недеља"],
+		daysShort: ["Нед", "Пон", "Уто", "Сре", "Чет", "Пет", "Суб", "Нед"],
+		daysMin: ["Н", "По", "У", "Ср", "Ч", "Пе", "Су", "Н"],
+		months: ["Јануар", "Фебруар", "Март", "Април", "Мај", "Јун", "Јул", "Август", "Септембар", "Октобар", "Новембар", "Децембар"],
+		monthsShort: ["Јан", "Феб", "Мар", "Апр", "Мај", "Јун", "Јул", "Авг", "Сеп", "Окт", "Нов", "Дец"],
+		today: "Данас"
+	};
+}(jQuery));

+ 15 - 0
static/cranefly/js/locales/bootstrap-datepicker.ru.js

@@ -0,0 +1,15 @@
+/**
+ * Russian translation for bootstrap-datepicker
+ * Victor Taranenko <darwin@snowdale.com>
+ */
+;(function($){
+	$.fn.datepicker.dates['ru'] = {
+		days: ["Воскресенье", "Понедельник", "Вторник", "Среда", "Четверг", "Пятница", "Суббота", "Воскресенье"],
+		daysShort: ["Вск", "Пнд", "Втр", "Срд", "Чтв", "Птн", "Суб", "Вск"],
+		daysMin: ["Вс", "Пн", "Вт", "Ср", "Чт", "Пт", "Сб", "Вс"],
+		months: ["Январь", "Февраль", "Март", "Апрель", "Май", "Июнь", "Июль", "Август", "Сентябрь", "Октябрь", "Ноябрь", "Декабрь"],
+		monthsShort: ["Янв", "Фев", "Мар", "Апр", "Май", "Июн", "Июл", "Авг", "Сен", "Окт", "Ноя", "Дек"],
+		today: "Сегодня",
+		weekStart: 1
+	};
+}(jQuery));

+ 15 - 0
static/cranefly/js/locales/bootstrap-datepicker.sk.js

@@ -0,0 +1,15 @@
+/**
+ * Slovak translation for bootstrap-datepicker
+ * Marek Lichtner <marek@licht.sk>
+ * Fixes by Michal Remiš <michal.remis@gmail.com>
+ */
+;(function($){
+	$.fn.datepicker.dates["sk"] = {
+		days: ["Nedeľa", "Pondelok", "Utorok", "Streda", "Štvrtok", "Piatok", "Sobota", "Nedeľa"],
+		daysShort: ["Ned", "Pon", "Uto", "Str", "Štv", "Pia", "Sob", "Ned"],
+		daysMin: ["Ne", "Po", "Ut", "St", "Št", "Pia", "So", "Ne"],
+		months: ["Január", "Február", "Marec", "Apríl", "Máj", "Jún", "Júl", "August", "September", "Október", "November", "December"],
+		monthsShort: ["Jan", "Feb", "Mar", "Apr", "Máj", "Jún", "Júl", "Aug", "Sep", "Okt", "Nov", "Dec"],
+		today: "Dnes"
+	};
+}(jQuery));

+ 14 - 0
static/cranefly/js/locales/bootstrap-datepicker.sl.js

@@ -0,0 +1,14 @@
+/**
+ * Slovene translation for bootstrap-datepicker
+ * Gregor Rudolf <gregor.rudolf@gmail.com>
+ */
+;(function($){
+	$.fn.datepicker.dates['sl'] = {
+		days: ["Nedelja", "Ponedeljek", "Torek", "Sreda", "Četrtek", "Petek", "Sobota", "Nedelja"],
+		daysShort: ["Ned", "Pon", "Tor", "Sre", "Čet", "Pet", "Sob", "Ned"],
+		daysMin: ["Ne", "Po", "To", "Sr", "Če", "Pe", "So", "Ne"],
+		months: ["Januar", "Februar", "Marec", "April", "Maj", "Junij", "Julij", "Avgust", "September", "Oktober", "November", "December"],
+		monthsShort: ["Jan", "Feb", "Mar", "Apr", "Maj", "Jun", "Jul", "Avg", "Sep", "Okt", "Nov", "Dec"],
+		today: "Danes"
+	};
+}(jQuery));

+ 15 - 0
static/cranefly/js/locales/bootstrap-datepicker.sq.js

@@ -0,0 +1,15 @@
+/**
+ * Albanian translation for bootstrap-datepicker
+ * Tomor Pupovci <http://www.github.com/ttomor>
+ */
+;(function($){
+	$.fn.datepicker.dates['sq'] = {
+		days: ["E Diel", "E Hënë", "E martē", "E mërkurë", "E Enjte", "E Premte", "E Shtunë", "E Diel"],
+		daysShort: ["Die", "Hën", "Mar", "Mër", "Enj", "Pre", "Shtu", "Die"],
+		daysMin: ["Di", "Hë", "Ma", "Më", "En", "Pr", "Sht", "Di"],
+		months: ["Janar", "Shkurt", "Mars", "Prill", "Maj", "Qershor", "Korrik", "Gusht", "Shtator", "Tetor", "Nëntor", "Dhjetor"],
+		monthsShort: ["Jan", "Shk", "Mar", "Pri", "Maj", "Qer", "Korr", "Gu", "Sht", "Tet", "Nën", "Dhjet"],
+		today: "Sot"
+	};
+}(jQuery));
+

+ 16 - 0
static/cranefly/js/locales/bootstrap-datepicker.sv.js

@@ -0,0 +1,16 @@
+/**
+ * Swedish translation for bootstrap-datepicker
+ * Patrik Ragnarsson <patrik@starkast.net>
+ */
+;(function($){
+	$.fn.datepicker.dates['sv'] = {
+		days: ["Söndag", "Måndag", "Tisdag", "Onsdag", "Torsdag", "Fredag", "Lördag", "Söndag"],
+		daysShort: ["Sön", "Mån", "Tis", "Ons", "Tor", "Fre", "Lör", "Sön"],
+		daysMin: ["Sö", "Må", "Ti", "On", "To", "Fr", "Lö", "Sö"],
+		months: ["Januari", "Februari", "Mars", "April", "Maj", "Juni", "Juli", "Augusti", "September", "Oktober", "November", "December"],
+		monthsShort: ["Jan", "Feb", "Mar", "Apr", "Maj", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dec"],
+		today: "I Dag",
+		format: "yyyy-mm-dd",
+		weekStart: 1
+	};
+}(jQuery));

+ 15 - 0
static/cranefly/js/locales/bootstrap-datepicker.sw.js

@@ -0,0 +1,15 @@
+/**
+ * Swahili translation for bootstrap-datepicker
+ * Edwin Mugendi <https://github.com/edwinmugendi>
+ * Source: http://scriptsource.org/cms/scripts/page.php?item_id=entry_detail&uid=xnfaqyzcku
+ */
+;(function($){
+    $.fn.datepicker.dates['sw'] = {
+        days: ["Jumapili", "Jumatatu", "Jumanne", "Jumatano", "Alhamisi", "Ijumaa", "Jumamosi", "Jumapili"],
+        daysShort: ["J2", "J3", "J4", "J5", "Alh", "Ij", "J1", "J2"],
+        daysMin: ["2", "3", "4", "5", "A", "I", "1", "2"],
+        months: ["Januari", "Februari", "Machi", "Aprili", "Mei", "Juni", "Julai", "Agosti", "Septemba", "Oktoba", "Novemba", "Desemba"],
+        monthsShort: ["Jan", "Feb", "Mac", "Apr", "Mei", "Jun", "Jul", "Ago", "Sep", "Okt", "Nov", "Des"],
+        today: "Leo"
+    };
+}(jQuery));

+ 14 - 0
static/cranefly/js/locales/bootstrap-datepicker.th.js

@@ -0,0 +1,14 @@
+/**
+ * Thai translation for bootstrap-datepicker
+ * Suchau Jiraprapot <seroz24@gmail.com>
+ */
+;(function($){
+	$.fn.datepicker.dates['th'] = {
+		days: ["อาทิตย์", "จันทร์", "อังคาร", "พุธ", "พฤหัส", "ศุกร์", "เสาร์", "อาทิตย์"],
+		daysShort: ["อา", "จ", "อ", "พ", "พฤ", "ศ", "ส", "อา"],
+		daysMin: ["อา", "จ", "อ", "พ", "พฤ", "ศ", "ส", "อา"],
+		months: ["มกราคม", "กุมภาพันธ์", "มีนาคม", "เมษายน", "พฤษภาคม", "มิถุนายน", "กรกฎาคม", "สิงหาคม", "กันยายน", "ตุลาคม", "พฤศจิกายน", "ธันวาคม"],
+		monthsShort: ["ม.ค.", "ก.พ.", "มี.ค.", "เม.ย.", "พ.ค.", "มิ.ย.", "ก.ค.", "ส.ค.", "ก.ย.", "ต.ค.", "พ.ย.", "ธ.ค."],
+		today: "วันนี้"
+	};
+}(jQuery));

+ 16 - 0
static/cranefly/js/locales/bootstrap-datepicker.tr.js

@@ -0,0 +1,16 @@
+/**
+ * Turkish translation for bootstrap-datepicker
+ * Serkan Algur <kaisercrazy_2@hotmail.com>
+ */
+;(function($){
+	$.fn.datepicker.dates['tr'] = {
+		days: ["Pazar", "Pazartesi", "Salı", "Çarşamba", "Perşembe", "Cuma", "Cumartesi", "Pazar"],
+		daysShort: ["Pz", "Pzt", "Sal", "Çrş", "Prş", "Cu", "Cts", "Pz"],
+		daysMin: ["Pz", "Pzt", "Sa", "Çr", "Pr", "Cu", "Ct", "Pz"],
+		months: ["Ocak", "Şubat", "Mart", "Nisan", "Mayıs", "Haziran", "Temmuz", "Ağustos", "Eylül", "Ekim", "Kasım", "Aralık"],
+		monthsShort: ["Oca", "Şub", "Mar", "Nis", "May", "Haz", "Tem", "Ağu", "Eyl", "Eki", "Kas", "Ara"],
+		today: "Bugün",
+		format: "dd.mm.yyyy"
+	};
+}(jQuery));
+

+ 15 - 0
static/cranefly/js/locales/bootstrap-datepicker.ua.js

@@ -0,0 +1,15 @@
+/**
+ * Ukrainian translation for bootstrap-datepicker
+ * Igor Polynets
+ */
+;(function($){
+	$.fn.datepicker.dates['ua'] = {
+		days: ["Неділя", "Понеділок", "Вівторок", "Середа", "Четверг", "П'ятница", "Субота", "Неділя"],
+		daysShort: ["Нед", "Пнд", "Втр", "Срд", "Чтв", "Птн", "Суб", "Нед"],
+		daysMin: ["Нд", "Пн", "Вт", "Ср", "Чт", "Пт", "Сб", "Нд"],
+		months: ["Cічень", "Лютий", "Березень", "Квітень", "Травень", "Червень", "Липень", "Серпень", "Вересень", "Жовтень", "Листопад", "Грудень"],
+		monthsShort: ["Січ", "Лют", "Бер", "Квт", "Трв", "Чер", "Лип", "Сер", "Вер", "Жов", "Лис", "Грд"],
+		today: "Сьогодні",
+		weekStart: 1
+	};
+}(jQuery));

+ 14 - 0
static/cranefly/js/locales/bootstrap-datepicker.uk.js

@@ -0,0 +1,14 @@
+/**
+ * Ukrainian translation for bootstrap-datepicker
+ * Andrey Vityuk <andrey [dot] vityuk [at] gmail.com>
+ */
+;(function($){
+	$.fn.datepicker.dates['uk'] = {
+		days: ["Неділя", "Понеділок", "Вівторок", "Середа", "Четвер", "П'ятниця", "Субота", "Неділя"],
+		daysShort: ["Нед", "Пнд", "Втр", "Срд", "Чтв", "Птн", "Суб", "Нед"],
+		daysMin: ["Нд", "Пн", "Вт", "Ср", "Чт", "Пт", "Сб", "Нд"],
+		months: ["Січень", "Лютий", "Березень", "Квітень", "Травень", "Червень", "Липень", "Серпень", "Вересень", "Жовтень", "Листопад", "Грудень"],
+		monthsShort: ["Січ", "Лют", "Бер", "Кві", "Тра", "Чер", "Лип", "Сер", "Вер", "Жов", "Лис", "Гру"],
+		today: "Сьогодні"
+	};
+}(jQuery));

+ 16 - 0
static/cranefly/js/locales/bootstrap-datepicker.zh-CN.js

@@ -0,0 +1,16 @@
+/**
+ * Simplified Chinese translation for bootstrap-datepicker
+ * Yuan Cheung <advanimal@gmail.com>
+ */
+;(function($){
+	$.fn.datepicker.dates['zh-CN'] = {
+		days: ["星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"],
+		daysShort: ["周日", "周一", "周二", "周三", "周四", "周五", "周六", "周日"],
+		daysMin:  ["日", "一", "二", "三", "四", "五", "六", "日"],
+		months: ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"],
+		monthsShort: ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"],
+		today: "今日",
+		format: "yyyy年mm月dd日",
+		weekStart: 1
+	};
+}(jQuery));

+ 17 - 0
static/cranefly/js/locales/bootstrap-datepicker.zh-TW.js

@@ -0,0 +1,17 @@
+/**
+ * Traditional Chinese translation for bootstrap-datepicker
+ * Rung-Sheng Jang <daniel@i-trend.co.cc>
+ * FrankWu  <frankwu100@gmail.com> Fix more appropriate use of Traditional Chinese habit
+ */
+;(function($){
+	$.fn.datepicker.dates['zh-TW'] = {
+		days: ["星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"],
+		daysShort: ["週日", "週一", "週二", "週三", "週四", "週五", "週六", "週日"],
+		daysMin:  ["日", "一", "二", "三", "四", "五", "六", "日"],
+		months: ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"],
+		monthsShort: ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"],
+		today: "今天",
+		format: "yyyy年mm月dd日",
+		weekStart: 1
+	};
+}(jQuery));

+ 6 - 6
templates/cranefly/layout.html

@@ -11,12 +11,12 @@
       <div class="container">
       <div class="container">
         <a href="{{ url('index') }}" class="brand">{% if settings.board_header %}{{ settings.board_header }}{% else %}{{ settings.board_name }}{% endif %}</a>
         <a href="{{ url('index') }}" class="brand">{% if settings.board_header %}{{ settings.board_header }}{% else %}{{ settings.board_name }}{% endif %}</a>
         {% if acl.search.can_search() and not user.is_crawler() %}
         {% if acl.search.can_search() and not user.is_crawler() %}
-        <form action="{{ url('search') }}" method="post" class="navbar-form pull-left">
+        <form action="{{ url('search_quick') }}" method="post" class="navbar-form pull-left">
           <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
           <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
           {% if thread is defined and thread %}
           {% if thread is defined and thread %}
-          <input type="hidden" name="search_thread" value="{{ thread.pk }}">
+          <input type="hidden" name="search_thread" value="{{ request_path }}">
           {% elif search_thread is defined and search_thread %}
           {% elif search_thread is defined and search_thread %}
-          <input type="hidden" name="search_thread" value="{{ search_thread.pk }}">
+          <input type="hidden" name="search_thread" value="{{ request_path }}">
           {% endif %}
           {% endif %}
           <div class="navbar-search-form">
           <div class="navbar-search-form">
             <div id="navbar-search" class="navbar-search-border">
             <div id="navbar-search" class="navbar-search-border">
@@ -29,7 +29,7 @@
                   <div class="control">
                   <div class="control">
                     <label>{% trans %}Search in{% endtrans %}:</label>
                     <label>{% trans %}Search in{% endtrans %}:</label>
                     <select name="search_in">
                     <select name="search_in">
-                      <option value="threads"{% if not search_in is defined or search_in == 'threads' %} selected="selected"{% endif %}>{% trans %}Forums{% endtrans%}</option>
+                      <option value="forums"{% if not search_in is defined or search_in == 'threads' %} selected="selected"{% endif %}>{% trans %}Forums{% endtrans%}</option>
                       {% if settings.enable_private_threads and acl.private_threads.can_participate()%}
                       {% if settings.enable_private_threads and acl.private_threads.can_participate()%}
                       <option value="private_threads"{% if search_in == 'private_threads' %} selected="selected"{% endif %}>{% trans %}Private Threads{% endtrans %}</option>
                       <option value="private_threads"{% if search_in == 'private_threads' %} selected="selected"{% endif %}>{% trans %}Private Threads{% endtrans %}</option>
                       {% endif %}
                       {% endif %}
@@ -55,7 +55,7 @@
                 </div>
                 </div>
                 <div class="form-actions">
                 <div class="form-actions">
                   <button type="submit" class="btn btn-primary"><i class="icon-search"></i> {% trans %}Search{% endtrans%}</button>
                   <button type="submit" class="btn btn-primary"><i class="icon-search"></i> {% trans %}Search{% endtrans%}</button>
-                  <a href="{{ url('search') }}">{% trans %}Advanced Search{% endtrans %}</a>
+                  <a href="{{ url('search_forums') }}">{% trans %}Advanced Search{% endtrans %}</a>
                 </div>
                 </div>
               </div>
               </div>
             </div>
             </div>
@@ -68,7 +68,7 @@
           <li><a href="{{ url('popular_threads') }}" title="{% trans %}Popular Threads{% endtrans %}" class="hot tooltip-bottom"><i class="icon-fire"></i></a></li>
           <li><a href="{{ url('popular_threads') }}" title="{% trans %}Popular Threads{% endtrans %}" class="hot tooltip-bottom"><i class="icon-fire"></i></a></li>
           <li><a href="{{ url('new_threads') }}" title="{% trans %}New Threads{% endtrans %}" class="fresh tooltip-bottom"><i class="icon-leaf"></i></a></li>{% if not user.crawler %}
           <li><a href="{{ url('new_threads') }}" title="{% trans %}New Threads{% endtrans %}" class="fresh tooltip-bottom"><i class="icon-leaf"></i></a></li>{% if not user.crawler %}
           {% if acl.search.can_search() and not user.is_crawler() %}
           {% if acl.search.can_search() and not user.is_crawler() %}
-          <li><a href="{{ url('search') }}" title="{% trans %}Search Forums{% endtrans %}" class="tooltip-bottom"><i class="icon-search"></i></a></li>{% endif %}
+          <li><a href="{{ url('search_forums') }}" title="{% trans %}Search Forums{% endtrans %}" class="tooltip-bottom"><i class="icon-search"></i></a></li>{% endif %}
           {% endif %}
           {% endif %}
           <li><a href="{{ url('users') }}" title="{% trans %}Browse Users{% endtrans %}" class="tooltip-bottom"><i class="icon-group"></i></a></li>
           <li><a href="{{ url('users') }}" title="{% trans %}Browse Users{% endtrans %}" class="tooltip-bottom"><i class="icon-group"></i></a></li>
           {% if settings.tos_url or settings.tos_content %}<li><a href="{% if settings.tos_url %}{{ settings.tos_url }}{% else %}{{ url('tos') }}{% endif %}" title="{% if settings.tos_title %}{{ settings.tos_title }}{% else %}{% trans %}Forum Terms of Service{% endtrans %}{% endif %}" class="tooltip-bottom"><i class="icon-certificate"></i></a></li>{% endif %}
           {% if settings.tos_url or settings.tos_content %}<li><a href="{% if settings.tos_url %}{{ settings.tos_url }}{% else %}{{ url('tos') }}{% endif %}" title="{% if settings.tos_title %}{{ settings.tos_title }}{% else %}{% trans %}Forum Terms of Service{% endtrans %}{% endif %}" class="tooltip-bottom"><i class="icon-certificate"></i></a></li>{% endif %}

+ 0 - 26
templates/cranefly/macros.html

@@ -53,30 +53,4 @@ itemprop="breadcrumb"
 
 
 {% macro wrap(item, wrap, extra='') -%}
 {% macro wrap(item, wrap, extra='') -%}
 <{{ wrap }}{% if extra %} {{ extra|safe }}{% endif %}>{{ item }}</{{ wrap }}>
 <{{ wrap }}{% if extra %} {{ extra|safe }}{% endif %}>{{ item }}</{{ wrap }}>
-{%- endmacro %}
-
-{% macro thread_flags(thread) -%}
-<ul class="unstyled thread-flags">
-  {% if thread.replies_reported and ((forum is defined and acl.threads.can_mod_posts(forum)) or acl.threads.can_mod_posts(thread.forum)) %}
-  <li class="flag-reported"><i class="icon-warning-sign tooltip-top" title="{% trans %}This thread has reported replies{% endtrans %}"></i></li>
-  {% endif %}
-  {% if thread.replies_moderated %}
-  <li class="flag-notreviewed"><i class="icon-question-sign tooltip-top" title="{% trans %}This thread has unreviewed replies{% endtrans %}"></i></li>
-  {% endif %}
-  {% if thread.weight == 2 %}
-  <li class="flag-announcement"><i class="icon-star tooltip-top" title="{% trans %}This thread is an annoucement{% endtrans %}"></i></li>
-  {% endif %}
-  {% if thread.weight == 1 %}
-  <li class="flag-sticky"><i class="icon-star-empty tooltip-top" title="{% trans %}This thread is sticky{% endtrans %}"></i></li>
-  {% endif %}
-  {% if thread.moderated  %}
-  <li class="flag-notreviewed"><i class="icon-eye-close tooltip-top" title="{% trans %}This thread awaits review{% endtrans %}"></i></li>
-  {% endif %}
-  {% if thread.deleted %}
-  <li class="flag-deleted"><i class="icon-trash tooltip-top" title="{% trans %}This thread is deleted{% endtrans %}"></i></li>
-  {% endif %}
-  {% if thread.closed %}
-  <li class="flag-closed"><i class="icon-lock tooltip-top" title="{% trans %}This thread is closed{% endtrans %}"></i></li>
-  {% endif %}
-</ul>
 {%- endmacro %}
 {%- endmacro %}

+ 0 - 17
templates/cranefly/search/home.html

@@ -1,17 +0,0 @@
-{% extends "cranefly/search/layout.html" %}
-{% import "cranefly/macros.html" as macros with context %}
-
-{% block action %}
-{% if search_result %}
-<div class="search-resume">
-  <p class="lead muted">{% trans search_query=style_query(search_result.search_query) %}Your search results for query "{{ search_query }}" are still available.{% endtrans %}</p>
-  <p class="lead muted">{% trans %}To discard it and start new search, simply perform new search using form above.{% endtrans %}</p>
-</div>
-{% else %}
-<p class="lead">{% trans %}To search forums, enter phrases you want to find in text field above and press search button.{% endtrans %}</p>
-{% endif %}
-{% endblock %}
-
-{% macro style_query(query) -%}
-<a href="{{ url('search_results') }}">{{ query }}</a>
-{%- endmacro %}

+ 15 - 0
templates/cranefly/search/layout.html

@@ -8,6 +8,21 @@
 <div class="page-header header-primary header-search">
 <div class="page-header header-primary header-search">
   <div class="container">
   <div class="container">
     <h1>{% trans %}Search Community{% endtrans %}</h1>
     <h1>{% trans %}Search Community{% endtrans %}</h1>
+    <ul class="nav nav-tabs header-tabs">
+      <li{% if search_route == 'search_forums' %} class="active"{% endif %}>
+        <a href="{{ url('search_forums') }}">{% trans %}Search Forums{% endtrans %}</a>
+      </li>
+      {% if acl.private_threads.can_participate() %}
+      <li{% if search_route == 'search_private_threads' %} class="active"{% endif %}>
+        <a href="{{ url('search_private_threads') }}">{% trans %}Search Private Threads{% endtrans %}</a>
+      </li>
+      {% endif %}
+      {% if acl.reports.can_handle() %}
+      <li{% if search_route == 'search_reports' %} class="active"{% endif %}>
+        <a href="{{ url('search_reports') }}">{% trans %}Search Reports{% endtrans %}</a>
+      </li>
+      {% endif %}
+    </ul>
   </div>
   </div>
 </div>
 </div>
 
 

+ 10 - 2
templates/cranefly/search/results.html

@@ -1,12 +1,15 @@
 {% extends "cranefly/search/layout.html" %}
 {% extends "cranefly/search/layout.html" %}
 {% import "cranefly/macros.html" as macros with context %}
 {% import "cranefly/macros.html" as macros with context %}
 
 
-{% block title %}{{ macros.page_title(title=_("Results"),parent=_('Search Community')) }}{% endblock %}
+{% block title %}{{ macros.page_title(title=_("Search Results"),parent=_('Search Community')) }}{% endblock %}
 
 
 {% block action %}
 {% block action %}
   <div class="search-results">
   <div class="search-results">
     {% if results %}
     {% if results %}
-    <h2>{% trans count=results|length, results=results|length|intcomma %}Search has returned one result:{% pluralize %}Search has returned {{ results }} results:{% endtrans %}</h2>
+    <h2>
+      {% trans count=results|length, results=results|length|intcomma, query=query_string(search_query) %}Search for "{{ query }}" has returned one result:{% pluralize %}Search for "{{ query }}" has returned {{ results }} results:{% endtrans %}
+      <a href="{{ url(search_route) }}" class="btn btn-inverse pull-right"><i class="icon-search"></i> {% trans %}New Search{% endtrans %}</a>
+    </h2>
     <div class="results-list">
     <div class="results-list">
       {% for result in results %}
       {% for result in results %}
       <div class="result">
       <div class="result">
@@ -32,6 +35,11 @@
   </div>
   </div>
 {% endblock %}
 {% endblock %}
 
 
+
+{% macro query_string(query) -%}
+<strong>{{ query }}</strong>
+{%- endmacro %}
+
 {% macro forum(forum) -%}
 {% macro forum(forum) -%}
 <a href="{{ url('forum', forum=forum.pk, slug=forum.slug) }}" class="forum-link">{{ forum.name }}</a>
 <a href="{{ url('forum', forum=forum.pk, slug=forum.slug) }}" class="forum-link">{{ forum.name }}</a>
 {%- endmacro %}
 {%- endmacro %}

+ 37 - 0
templates/cranefly/search/search_form.html

@@ -0,0 +1,37 @@
+{% extends "cranefly/search/layout.html" %}
+{% import "cranefly/macros.html" as macros with context %}
+
+{% block action %}
+<div class="row">
+  <div class="span8 offset2">
+    {% if search_result and search_result.search_route == search_route %}
+    <div class="search-resume">
+      <p class="lead"><a href="{{ url('search_results') }}">{% trans search_query=style_query(search_result.search_query) %}Your search results for query "{{ search_query }}" are still available.{% endtrans %}</a></p>
+    </div>
+    {% endif %}
+    {% block form %}{% endblock %}
+  </div>
+</div>
+{% endblock %}
+
+{% block javascripts %}
+{{ super() }}
+<script src="{{ STATIC_URL }}cranefly/js/bootstrap-datepicker.js"></script>
+<script type="text/javascript">
+  $(function () {
+    $('.form-datepicker').datepicker({
+        format: 'yyyy-mm-dd',
+        language: '{{ LANGUAGE_CODE|replace('_', '-') }}'
+    });
+  });
+</script>
+{% endblock %}
+
+
+{% macro style_query(query) -%}
+<strong>{{ query }}</strong>
+{%- endmacro %}
+
+{% macro lang_search_thread_titles() -%}
+{% trans %}Search only in threads titles.{% endtrans %}
+{%- endmacro %}

+ 47 - 0
templates/cranefly/search/search_forums.html

@@ -0,0 +1,47 @@
+{% extends "cranefly/search/search_form.html" %}
+{% import "forms.html" as form_theme with context %}
+{% import "cranefly/macros.html" as macros with context %}
+
+{% block title %}{{ macros.page_title(title=_('Search Forums'), parent=_("Search Community")) }}{% endblock %}
+
+{% block form %}
+<div class="form-container">
+
+  <div class="form-header">
+    <h1>{% trans %}Search Forums{% endtrans %}</h1>
+  </div>
+
+  {% if message %}
+  <div class="messages-list">
+    {{ macros.draw_message(message) }}
+  </div>
+  {% endif %}
+
+  <form action="{{ url(search_route) }}" method="post">
+    {{ form_theme.hidden_fields(form) }}
+    <div class="form-fields">
+      <fieldset class="first last">
+        {{ form_theme.row(form.search_query, attrs={'class': 'span8'}) }}
+        {{ form_theme.row(form.search_thread_titles, attrs={'inline': lang_search_thread_titles()}) }}
+        {{ form_theme.row(form.search_thread, attrs={'class': 'span8'}) }}
+        {{ form_theme.row(form.search_author, attrs={'class': 'span8'}) }}
+        <hr>
+        {{ form_theme.row(form.search_forums, attrs={'class': 'span8'}) }}
+        {{ form_theme.row(form.search_forums_childs, attrs={'inline': lang_search_forum_children()}) }}
+        <hr>
+        {{ form_theme.row(form.search_before, attrs={'class': 'span8 form-datepicker'}) }}
+        {{ form_theme.row(form.search_after, attrs={'class': 'span8 form-datepicker'}) }}
+      </fieldset>
+    </div>
+    <div class="form-actions">
+      <button type="submit" class="btn btn-primary">{% trans %}Search{% endtrans %}</button>
+    </div>
+  </form>
+
+</div>
+{% endblock %}
+
+
+{% macro lang_search_forum_children() -%}
+{% trans %}Include Children Forums in Search{% endtrans %}
+{%- endmacro %}

+ 39 - 0
templates/cranefly/search/search_private_threads.html

@@ -0,0 +1,39 @@
+{% extends "cranefly/search/search_form.html" %}
+{% import "forms.html" as form_theme with context %}
+{% import "cranefly/macros.html" as macros with context %}
+
+{% block title %}{{ macros.page_title(title=_('Search Private Threads'), parent=_("Search Community")) }}{% endblock %}
+
+{% block form %}
+<div class="form-container">
+
+  <div class="form-header">
+    <h1>{% trans %}Search Private Threads{% endtrans %}</h1>
+  </div>
+
+  {% if message %}
+  <div class="messages-list">
+    {{ macros.draw_message(message) }}
+  </div>
+  {% endif %}
+
+  <form action="{{ url(search_route) }}" method="post">
+    {{ form_theme.hidden_fields(form) }}
+    <div class="form-fields">
+      <fieldset class="first last">
+        {{ form_theme.row(form.search_query, attrs={'class': 'span8'}) }}
+        {{ form_theme.row(form.search_thread_titles, attrs={'inline': lang_search_thread_titles()}) }}
+        {{ form_theme.row(form.search_thread, attrs={'class': 'span8'}) }}
+        {{ form_theme.row(form.search_author, attrs={'class': 'span8'}) }}
+        <hr>
+        {{ form_theme.row(form.search_before, attrs={'class': 'span8 form-datepicker'}) }}
+        {{ form_theme.row(form.search_after, attrs={'class': 'span8 form-datepicker'}) }}
+      </fieldset>
+    </div>
+    <div class="form-actions">
+      <button type="submit" class="btn btn-primary">{% trans %}Search{% endtrans %}</button>
+    </div>
+  </form>
+
+</div>
+{% endblock %}

+ 41 - 0
templates/cranefly/search/search_reports.html

@@ -0,0 +1,41 @@
+{% extends "cranefly/search/search_form.html" %}
+{% import "forms.html" as form_theme with context %}
+{% import "cranefly/macros.html" as macros with context %}
+
+{% block title %}{{ macros.page_title(title=_('Search Reports'), parent=_("Search Community")) }}{% endblock %}
+
+{% block form %}
+<div class="form-container">
+
+  <div class="form-header">
+    <h1>{% trans %}Search Reports{% endtrans %}</h1>
+  </div>
+
+  {% if message %}
+  <div class="messages-list">
+    {{ macros.draw_message(message) }}
+  </div>
+  {% endif %}
+
+  <form action="{{ url(search_route) }}" method="post">
+    {{ form_theme.hidden_fields(form) }}
+    <div class="form-fields">
+      <fieldset class="first last">
+        {{ form_theme.row(form.search_query, attrs={'class': 'span8'}) }}
+        {{ form_theme.row(form.search_thread_titles, attrs={'inline': lang_search_thread_titles()}) }}
+        {{ form_theme.row(form.search_thread, attrs={'class': 'span8'}) }}
+        {{ form_theme.row(form.search_author, attrs={'class': 'span8'}) }}
+        <hr>
+        {{ form_theme.row(form.search_weight, attrs={'class': 'span8'}) }}
+        <hr>
+        {{ form_theme.row(form.search_before, attrs={'class': 'span8 form-datepicker'}) }}
+        {{ form_theme.row(form.search_after, attrs={'class': 'span8 form-datepicker'}) }}
+      </fieldset>
+    </div>
+    <div class="form-actions">
+      <button type="submit" class="btn btn-primary">{% trans %}Search{% endtrans %}</button>
+    </div>
+  </form>
+
+</div>
+{% endblock %}

+ 6 - 0
templates/forms.html

@@ -99,6 +99,8 @@
 {{ _yesno(_field, context) }}
 {{ _yesno(_field, context) }}
 {% elif widget == 'Select' %}
 {% elif widget == 'Select' %}
 {{ _select(_field, context) }}
 {{ _select(_field, context) }}
+{% elif widget == 'SelectDateWidget' %}
+{{ _select_date(_field, context) }}
 {% elif widget == 'RadioSelect' %}
 {% elif widget == 'RadioSelect' %}
 {{ _radio_select(_field, context) }}
 {{ _radio_select(_field, context) }}
 {% elif widget == 'CheckboxSelectMultiple' %}
 {% elif widget == 'CheckboxSelectMultiple' %}
@@ -146,6 +148,10 @@
 </select>
 </select>
 {%- endmacro %}
 {%- endmacro %}
 
 
+{% macro _select_date(_field, context) -%}
+{{ context }}
+{%- endmacro %}
+
 {% macro _radio_select(_field, context) -%}
 {% macro _radio_select(_field, context) -%}
 <div class="radio-group">
 <div class="radio-group">
   {% for option in context['optgroups'][0][1] %}
   {% for option in context['optgroups'][0][1] %}