Merge branch 'production' into pretty
authorRadek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>
Fri, 13 Jan 2012 16:05:36 +0000 (17:05 +0100)
committerRadek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>
Tue, 17 Jan 2012 14:17:44 +0000 (15:17 +0100)
Conflicts:
apps/catalogue/models.py
wolnelektury/settings.py
wolnelektury/templates/catalogue/main_page.html
wolnelektury/urls.py

191 files changed:
.gitignore
apps/ajaxable/__init__.py [new file with mode: 0644]
apps/ajaxable/models.py [new file with mode: 0644]
apps/ajaxable/templates/ajaxable/form.html [new file with mode: 0755]
apps/ajaxable/templates/ajaxable/form_on_page.html [new file with mode: 0755]
apps/ajaxable/utils.py [new file with mode: 0755]
apps/api/handlers.py
apps/api/management/commands/mobileinit.py
apps/api/tests.py
apps/api/urls.py
apps/catalogue/admin.py
apps/catalogue/fields.py
apps/catalogue/forms.py
apps/catalogue/import_utils.py [new file with mode: 0755]
apps/catalogue/locale/pl/LC_MESSAGES/django.mo
apps/catalogue/locale/pl/LC_MESSAGES/django.po
apps/catalogue/management/commands/__init__.py
apps/catalogue/management/commands/importbooks.py
apps/catalogue/management/commands/pack.py
apps/catalogue/migrations/0017_auto__add_collection.py [deleted file]
apps/catalogue/migrations/0017_auto__add_field_book_language__del_unique_book_slug__add_unique_book_s.py [new file with mode: 0644]
apps/catalogue/migrations/0018_auto__del_filerecord.py [new file with mode: 0644]
apps/catalogue/migrations/0019_auto__add_field_book_cover.py [new file with mode: 0644]
apps/catalogue/migrations/0020_auto__del_field_tag_main_page.py [new file with mode: 0644]
apps/catalogue/migrations/0021_build_covers.py [new file with mode: 0644]
apps/catalogue/migrations/0022_auto__add_field_book_common_slug__add_unique_book_slug__del_unique_boo.py [new file with mode: 0644]
apps/catalogue/migrations/0023_common_slug.py [new file with mode: 0644]
apps/catalogue/migrations/0024_auto__add_collection.py [new file with mode: 0644]
apps/catalogue/models.py
apps/catalogue/tasks.py [new file with mode: 0755]
apps/catalogue/templatetags/catalogue_tags.py
apps/catalogue/test_utils.py
apps/catalogue/tests/book_import.py
apps/catalogue/tests/bookmedia.py
apps/catalogue/tests/files/fraszki.xml
apps/catalogue/tests/tags.py
apps/catalogue/urls.py
apps/catalogue/utils.py
apps/catalogue/views.py
apps/dictionary/templates/dictionary/note_list.html
apps/dictionary/views.py
apps/infopages/admin.py
apps/infopages/migrations/0002_auto__del_field_infopage_page_title__del_field_infopage_page_title_en_.py [new file with mode: 0644]
apps/infopages/models.py
apps/infopages/templates/infopages/infopage.html [new file with mode: 0755]
apps/infopages/templates/infopages/on_main.html [new file with mode: 0755]
apps/infopages/templatetags/__init__.py [new file with mode: 0644]
apps/infopages/templatetags/infopages_tags.py [new file with mode: 0755]
apps/infopages/urls.py [new file with mode: 0755]
apps/infopages/views.py
apps/lesmianator/urls.py
apps/lesmianator/views.py
apps/lessons/urls.py
apps/lessons/views.py
apps/opds/views.py
apps/pdcounter/urls.py [deleted file]
apps/pdcounter/views.py
apps/picture/__init__.py [new file with mode: 0644]
apps/picture/admin.py [new file with mode: 0644]
apps/picture/forms.py [new file with mode: 0644]
apps/picture/models.py [new file with mode: 0644]
apps/picture/templates/admin/picture/picture/change_list.html [new file with mode: 0755]
apps/picture/tests/__init__.py [new file with mode: 0644]
apps/picture/tests/files/kandinsky-composition-viii.png [new file with mode: 0644]
apps/picture/tests/files/kandinsky-composition-viii.xml [new file with mode: 0644]
apps/picture/tests/picture_import.py [new file with mode: 0644]
apps/picture/views.py [new file with mode: 0644]
apps/reporting/templates/reporting/catalogue.texml
apps/reporting/templates/reporting/main.html
apps/reporting/templatetags/reporting_stats.py
apps/search/__init__.py [new file with mode: 0644]
apps/search/context_processors.py [new file with mode: 0644]
apps/search/index.py [new file with mode: 0644]
apps/search/management/__init__.py [new file with mode: 0644]
apps/search/management/commands/__init__.py [new file with mode: 0644]
apps/search/management/commands/checkindex.py [new file with mode: 0644]
apps/search/management/commands/optimizeindex.py [new file with mode: 0644]
apps/search/management/commands/reindex.py [new file with mode: 0755]
apps/search/tests/__init__.py [new file with mode: 0644]
apps/search/tests/files/fraszka-do-anusie.xml [new file with mode: 0755]
apps/search/tests/files/fraszki.xml [new file with mode: 0755]
apps/search/tests/index.py [new file with mode: 0644]
apps/search/urls.py [new file with mode: 0644]
apps/search/views.py [new file with mode: 0644]
apps/sponsors/models.py
apps/sponsors/processors.py [deleted file]
apps/sponsors/static/sponsors/css/footer_admin.css
apps/sponsors/widgets.py
apps/suggest/forms.py
apps/suggest/templates/publishing_suggest.html
apps/suggest/templates/publishing_suggest_full.html [deleted file]
apps/suggest/templates/suggest.html [deleted file]
apps/suggest/urls.py
apps/suggest/views.py
lib/librarian
requirements.txt
scripts/make-tags
scripts/setmainpage.py [deleted file]
wolnelektury/locale/pl/LC_MESSAGES/django.mo
wolnelektury/locale/pl/LC_MESSAGES/django.po
wolnelektury/settings.py
wolnelektury/static/css/base.css [new file with mode: 0755]
wolnelektury/static/css/book_box.css [new file with mode: 0755]
wolnelektury/static/css/catalogue.css [new file with mode: 0755]
wolnelektury/static/css/dialogs.css [new file with mode: 0755]
wolnelektury/static/css/header.css [new file with mode: 0755]
wolnelektury/static/css/main_page.css [new file with mode: 0755]
wolnelektury/static/css/master.css
wolnelektury/static/css/picture_box.css [new file with mode: 0755]
wolnelektury/static/css/sponsors.css
wolnelektury/static/css/ui-lightness/images/ui-bg_diagonals-thick_18_b81900_40x40.png [new file with mode: 0644]
wolnelektury/static/css/ui-lightness/images/ui-bg_diagonals-thick_20_666666_40x40.png [new file with mode: 0644]
wolnelektury/static/css/ui-lightness/images/ui-bg_flat_10_000000_40x100.png [new file with mode: 0644]
wolnelektury/static/css/ui-lightness/images/ui-bg_glass_100_f6f6f6_1x400.png [new file with mode: 0644]
wolnelektury/static/css/ui-lightness/images/ui-bg_glass_100_fdf5ce_1x400.png [new file with mode: 0644]
wolnelektury/static/css/ui-lightness/images/ui-bg_glass_65_ffffff_1x400.png [new file with mode: 0644]
wolnelektury/static/css/ui-lightness/images/ui-bg_gloss-wave_35_f6a828_500x100.png [new file with mode: 0644]
wolnelektury/static/css/ui-lightness/images/ui-bg_highlight-soft_100_eeeeee_1x100.png [new file with mode: 0644]
wolnelektury/static/css/ui-lightness/images/ui-bg_highlight-soft_75_ffe45c_1x100.png [new file with mode: 0644]
wolnelektury/static/css/ui-lightness/images/ui-icons_222222_256x240.png [new file with mode: 0644]
wolnelektury/static/css/ui-lightness/images/ui-icons_228ef1_256x240.png [new file with mode: 0644]
wolnelektury/static/css/ui-lightness/images/ui-icons_ef8c08_256x240.png [new file with mode: 0644]
wolnelektury/static/css/ui-lightness/images/ui-icons_ffd27a_256x240.png [new file with mode: 0644]
wolnelektury/static/css/ui-lightness/images/ui-icons_ffffff_256x240.png [new file with mode: 0644]
wolnelektury/static/css/ui-lightness/jquery-ui-1.8.16.custom.css [new file with mode: 0644]
wolnelektury/static/fonts/WL-Nav.ttf [new file with mode: 0644]
wolnelektury/static/fonts/WL.eot [new file with mode: 0644]
wolnelektury/static/fonts/WL.ttf [new file with mode: 0644]
wolnelektury/static/img/bg-header.png [new file with mode: 0644]
wolnelektury/static/jplayer/Jplayer.swf [new file with mode: 0644]
wolnelektury/static/jplayer/jplayer.blue.monday.css [new file with mode: 0644]
wolnelektury/static/jplayer/jplayer.blue.monday.jpg [new file with mode: 0644]
wolnelektury/static/jplayer/jplayer.blue.monday.seeking.gif [new file with mode: 0644]
wolnelektury/static/jplayer/jplayer.blue.monday.video.play.png [new file with mode: 0644]
wolnelektury/static/jplayer/jplayer.playlist.min.js [new file with mode: 0644]
wolnelektury/static/jplayer/jquery.jplayer.min.js [new file with mode: 0644]
wolnelektury/static/js/base.js [new file with mode: 0755]
wolnelektury/static/js/catalogue.js
wolnelektury/static/js/dialogs.js [new file with mode: 0755]
wolnelektury/static/js/jquery-ui-1.8.16.custom.min.js [new file with mode: 0644]
wolnelektury/static/js/jquery.jqmodal.js
wolnelektury/static/js/locale.js [new file with mode: 0755]
wolnelektury/static/js/pdcounter.js [new file with mode: 0755]
wolnelektury/static/js/player.js [new file with mode: 0755]
wolnelektury/static/js/search.js [new file with mode: 0644]
wolnelektury/static/js/sponsors.js [new file with mode: 0755]
wolnelektury/static/opensearch.xml
wolnelektury/static/sponsors/css/footer_admin.css
wolnelektury/templates/404.html
wolnelektury/templates/auth/login.html [deleted file]
wolnelektury/templates/base.html
wolnelektury/templates/catalogue/audiobook_list.html
wolnelektury/templates/catalogue/book_detail.html
wolnelektury/templates/catalogue/book_fragments.html
wolnelektury/templates/catalogue/book_list.html
wolnelektury/templates/catalogue/book_mini_box.html [new file with mode: 0755]
wolnelektury/templates/catalogue/book_short.html
wolnelektury/templates/catalogue/book_text.html
wolnelektury/templates/catalogue/book_wide.html [new file with mode: 0644]
wolnelektury/templates/catalogue/breadcrumbs.html [deleted file]
wolnelektury/templates/catalogue/catalogue.html [new file with mode: 0644]
wolnelektury/templates/catalogue/collection.html
wolnelektury/templates/catalogue/daisy_list.html
wolnelektury/templates/catalogue/differentiate_tags.html
wolnelektury/templates/catalogue/folded_tag_list.html [deleted file]
wolnelektury/templates/catalogue/inline_tag_list.html [new file with mode: 0755]
wolnelektury/templates/catalogue/main_page.html [deleted file]
wolnelektury/templates/catalogue/picture_detail.html [new file with mode: 0644]
wolnelektury/templates/catalogue/picture_list.html [new file with mode: 0644]
wolnelektury/templates/catalogue/player.html [new file with mode: 0755]
wolnelektury/templates/catalogue/search_form.html [deleted file]
wolnelektury/templates/catalogue/search_multiple_hits.html
wolnelektury/templates/catalogue/search_no_hits.html
wolnelektury/templates/catalogue/search_too_short.html
wolnelektury/templates/catalogue/tag_list.html
wolnelektury/templates/catalogue/tagged_object_list.html
wolnelektury/templates/catalogue/user_shelves.html
wolnelektury/templates/info/base.html [deleted file]
wolnelektury/templates/info/join_us.html
wolnelektury/templates/lesmianator/lesmianator.html
wolnelektury/templates/lessons/document_list.html
wolnelektury/templates/main_page.html [new file with mode: 0755]
wolnelektury/templates/newsearch/search.html [new file with mode: 0644]
wolnelektury/templates/pdcounter/author_detail.html
wolnelektury/templates/pdcounter/book_stub_detail.html
wolnelektury/templates/pdcounter/pd_counter.html [deleted file]
wolnelektury/templates/picture/picture_short.html [new file with mode: 0644]
wolnelektury/templates/publish_plan.html [new file with mode: 0755]
wolnelektury/translation.py
wolnelektury/urls.py
wolnelektury/views.py [new file with mode: 0755]

index c375441..230a876 100644 (file)
@@ -33,3 +33,5 @@ thumbs.db
 
 # Tags file
 TAGS
+
+media
diff --git a/apps/ajaxable/__init__.py b/apps/ajaxable/__init__.py
new file mode 100644 (file)
index 0000000..a010543
--- /dev/null
@@ -0,0 +1,4 @@
+"""
+Provides a way to create forms behaving correctly as AJAX forms
+as well as standalone forms without any Javascript.
+"""
diff --git a/apps/ajaxable/models.py b/apps/ajaxable/models.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/apps/ajaxable/templates/ajaxable/form.html b/apps/ajaxable/templates/ajaxable/form.html
new file mode 100755 (executable)
index 0000000..ba79e4b
--- /dev/null
@@ -0,0 +1,10 @@
+{% load i18n %}
+<h1>{{ title }}</h1>
+
+<form action="{{ request.get_full_path }}" method="post" accept-charset="utf-8" class="cuteform">
+<ol>
+    <div id="id___all__"></div>
+    {{ form.as_ul }}
+    <li><input type="submit" value="{{ submit }}"/></li>
+</ol>
+</form>
\ No newline at end of file
diff --git a/apps/ajaxable/templates/ajaxable/form_on_page.html b/apps/ajaxable/templates/ajaxable/form_on_page.html
new file mode 100755 (executable)
index 0000000..61175d5
--- /dev/null
@@ -0,0 +1,14 @@
+{% extends "base.html" %}
+{% load i18n %}
+
+{% block titleextra %}{{ title }}{% endblock %}
+
+{% block body %}
+
+    {% include ajax_template %}
+
+    {% if response_data.message %}
+        <p>{{ response_data.message }}</p>
+    {% endif %}
+
+{% endblock %}
diff --git a/apps/ajaxable/utils.py b/apps/ajaxable/utils.py
new file mode 100755 (executable)
index 0000000..d6f7050
--- /dev/null
@@ -0,0 +1,82 @@
+from django.http import HttpResponse, HttpResponseRedirect
+from django.shortcuts import render_to_response
+from django.template import RequestContext
+from django.utils.encoding import force_unicode
+from django.utils.functional import Promise
+from django.utils.http import urlquote_plus
+from django.utils import simplejson
+from django.utils.translation import ugettext_lazy as _
+
+
+class LazyEncoder(simplejson.JSONEncoder):
+    def default(self, obj):
+        if isinstance(obj, Promise):
+            return force_unicode(obj)
+        return obj
+
+# shortcut for JSON reponses
+class JSONResponse(HttpResponse):
+    def __init__(self, data={}, callback=None, **kwargs):
+        # get rid of mimetype
+        kwargs.pop('mimetype', None)
+        data = simplejson.dumps(data)
+        if callback:
+            data = callback + "(" + data + ");" 
+        super(JSONResponse, self).__init__(data, mimetype="application/json", **kwargs)
+
+
+
+class AjaxableFormView(object):
+    """Subclass this to create an ajaxable view for any form.
+
+    In the subclass, provide at least form_class.
+
+    """
+    form_class = None
+    # override to customize form look
+    template = "ajaxable/form.html"
+    submit = _('Send')
+    
+    title = ''
+    success_message = ''
+    formname = "form"
+    full_template = "ajaxable/form_on_page.html"
+
+    def __call__(self, request):
+        """A view displaying a form, or JSON if `ajax' GET param is set."""
+        ajax = request.GET.get('ajax', False)
+        if request.method == "POST":
+            form = self.form_class(data=request.POST)
+            if form.is_valid():
+                self.success(form, request)
+                redirect = request.GET.get('next')
+                if not ajax and redirect:
+                    return HttpResponseRedirect(urlquote_plus(
+                            redirect, safe='/?=&'))
+                response_data = {'success': True, 
+                    'message': self.success_message, 'redirect': redirect}
+            else:
+                response_data = {'success': False, 'errors': form.errors}
+            if ajax:
+                return HttpResponse(LazyEncoder(ensure_ascii=False).encode(response_data))
+        else:
+            form = self.form_class()
+            response_data = None
+
+        template = self.template if ajax else self.full_template
+        return render_to_response(template, {
+                self.formname: form, 
+                "title": self.title,
+                "submit": self.submit,
+                "response_data": response_data,
+                "ajax_template": self.template,
+            },
+            context_instance=RequestContext(request))
+
+    def success(self, form, request):
+        """What to do when the form is valid.
+        
+        By default, just save the form.
+
+        """
+        return form.save(request)
index eb15a4e..fddff74 100644 (file)
@@ -16,6 +16,8 @@ from api.helpers import timestamp
 from api.models import Deleted
 from catalogue.forms import BookImportForm
 from catalogue.models import Book, Tag, BookMedia, Fragment
+from picture.models import Picture
+from picture.forms import PictureImportForm
 
 from stats.utils import piwik_track
 
@@ -94,13 +96,12 @@ class BookDetailHandler(BaseHandler):
 
     """
     allowed_methods = ['GET']
-    fields = ['title', 'parent'] + Book.file_types + [
+    fields = ['title', 'parent'] + Book.formats + [
         'media', 'url'] + category_singular.keys()
 
     @piwik_track
     def read(self, request, slug):
-        """ Returns details of a book, identified by a slug. """
-
+        """ Returns details of a book, identified by a slug and lang. """
         try:
             return Book.objects.get(slug=slug)
         except Book.DoesNotExist:
@@ -203,7 +204,7 @@ def _file_getter(format):
         else:
             return ''
     return get_file
-for format in Book.file_types:
+for format in Book.formats:
     setattr(BooksHandler, format, _file_getter(format))
 
 
@@ -247,9 +248,8 @@ class TagsHandler(BaseHandler):
         except KeyError, e:
             return rc.NOT_FOUND
 
-        tags = Tag.objects.filter(category=category_sng)
-        tags = [t for t in tags if t.get_count() > 0]
-        if tags:
+        tags = Tag.objects.filter(category=category_sng).exclude(book_count=0)
+        if tags.exists():
             return tags
         else:
             return rc.NOT_FOUND
@@ -268,7 +268,6 @@ class FragmentDetailHandler(BaseHandler):
     @piwik_track
     def read(self, request, slug, anchor):
         """ Returns details of a fragment, identified by book slug and anchor. """
-
         try:
             return Fragment.objects.get(book__slug=slug, anchor=anchor)
         except Fragment.DoesNotExist:
@@ -307,7 +306,8 @@ class FragmentsHandler(BaseHandler):
     def href(cls, fragment):
         """ Returns URI in the API for the fragment. """
 
-        return API_BASE + reverse("api_fragment", args=[fragment.book.slug, fragment.anchor])
+        return API_BASE + reverse("api_fragment", 
+            args=[fragment.book.slug, fragment.anchor])
 
     @classmethod
     def url(cls, fragment):
@@ -355,8 +355,7 @@ class CatalogueHandler(BaseHandler):
     def book_dict(book, fields=None):
         all_fields = ['url', 'title', 'description',
                       'gazeta_link', 'wiki_link',
-                      ] + Book.file_types + [
-                      'mp3', 'ogg', 'daisy',
+                      ] + Book.formats + BookMedia.formats + [
                       'parent', 'parent_number',
                       'tags',
                       'license', 'license_description', 'source_name',
@@ -373,7 +372,7 @@ class CatalogueHandler(BaseHandler):
         obj = {}
         for field in fields:
 
-            if field in Book.file_types:
+            if field in Book.formats:
                 f = getattr(book, field+'_file')
                 if f:
                     obj[field] = {
@@ -381,7 +380,7 @@ class CatalogueHandler(BaseHandler):
                         'size': f.size,
                     }
 
-            elif field in ('mp3', 'ogg', 'daisy'):
+            elif field in BookMedia.formats:
                 media = []
                 for m in book.media.filter(type=field):
                     media.append({
@@ -510,7 +509,7 @@ class CatalogueHandler(BaseHandler):
                     changed_at__gte=since,
                     changed_at__lt=until):
             # only serve non-empty tags
-            if tag.get_count():
+            if tag.book_count:
                 tag_d = cls.tag_dict(tag, fields)
                 updated.append(tag_d)
             elif tag.created_at < since:
@@ -587,3 +586,21 @@ class ChangesHandler(CatalogueHandler):
     @piwik_track
     def read(self, request, since):
         return self.changes(request, since)
+
+
+class PictureHandler(BaseHandler):
+    model = Picture
+    fields = ('slug', 'title')
+    allowed_methods = ('POST',)
+
+    def create(self, request):
+        if not request.user.has_perm('picture.add_picture'):
+            return rc.FORBIDDEN
+
+        data = json.loads(request.POST.get('data'))
+        form = PictureImportForm(data)
+        if form.is_valid():
+            form.save()
+            return rc.CREATED
+        else:
+            return rc.NOT_FOUND
index 658b177..2abbfb3 100755 (executable)
@@ -23,10 +23,10 @@ class Command(BaseCommand):
         db = init_db(last_checked)
         for b in Book.objects.all():
             add_book(db, b)
-        for t in Tag.objects.exclude(category__in=('book', 'set', 'theme')):
+        for t in Tag.objects.exclude(
+                category__in=('book', 'set', 'theme')).exclude(book_count=0):
             # only add non-empty tags
-            if t.get_count():
-                add_tag(db, t)
+            add_tag(db, t)
         db.commit()
         db.close()
         current(last_checked)
index 2c2e51c..5a981a2 100644 (file)
@@ -8,6 +8,14 @@ from django.conf import settings
 
 from api.helpers import timestamp
 from catalogue.models import Book, Tag
+from picture.tests.utils import RequestFactory
+from picture.forms import PictureImportForm
+from picture.models import Picture, picture_storage
+import picture.tests
+from django.core.files.uploadedfile import SimpleUploadedFile
+
+from os import path
 
 
 class ApiTest(TestCase):
@@ -135,3 +143,22 @@ class TagTests(TestCase):
         tag = json.loads(self.client.get('/api/authors/joe/').content)
         self.assertEqual(tag['name'], self.tag.name,
                         'Wrong tag details.')
+
+
+class PictureTests(ApiTest):
+    def test_publish(self):
+        slug = "kandinsky-composition-viii"
+        xml = SimpleUploadedFile('composition8.xml', open(path.join(picture.tests.__path__[0], "files", slug + ".xml")).read())
+        img = SimpleUploadedFile('kompozycja-8.png', open(path.join(picture.tests.__path__[0], "files", slug + ".png")).read())
+
+        import_form = PictureImportForm({}, {
+            'picture_xml_file': xml,
+            'picture_image_file': img
+            })
+
+        assert import_form.is_valid()
+        if import_form.is_valid():
+            import_form.save()
+
+        pic = Picture.objects.get(slug=slug)
+
index 2d92eba..fd97d63 100644 (file)
@@ -4,7 +4,7 @@ from piston.authentication import OAuthAuthentication
 from piston.resource import Resource
 
 from api import handlers
-
+from catalogue.models import Book
 
 auth = OAuthAuthentication(realm="Wolne Lektury")
 
@@ -22,6 +22,7 @@ tag_resource = Resource(handler=handlers.TagDetailHandler)
 fragment_resource = Resource(handler=handlers.FragmentDetailHandler)
 fragment_list_resource = Resource(handler=handlers.FragmentsHandler)
 
+picture_resource = Resource(handler=handlers.PictureHandler, authentication=auth)
 
 urlpatterns = patterns(
     'piston.authentication',
@@ -46,16 +47,18 @@ urlpatterns = patterns(
 
 
     # objects details
-    url(r'^books/(?P<slug>[a-z0-9-]+)/$', book_resource, name="api_book"),
+    url(r'^books/(?P<book>[a-z0-9-]+)/$', book_resource, name="api_book"),
     url(r'^(?P<category>[a-z0-9-]+)/(?P<slug>[a-z0-9-]+)/$',
         tag_resource, name="api_tag"),
-    url(r'^books/(?P<slug>[a-z0-9-]+)/fragments/(?P<anchor>[a-z0-9-]+)/$',
+    url(r'^books/(?P<book>[a-z0-9-]+)/fragments/(?P<anchor>[a-z0-9-]+)/$',
         fragment_resource, name="api_fragment"),
 
     # books by tags
     url(r'^(?P<tags>(?:(?:[a-z0-9-]+/){2}){0,6})books/$', book_list_resource),
     url(r'^(?P<tags>(?:(?:[a-z0-9-]+/){2}){0,6})parent_books/$', book_list_resource, {"top_level": True}),
 
+    url(r'^pictures/$', picture_resource),
+
     # fragments by book, tags, themes
     # this should be paged
     url(r'^(?P<tags>(?:(?:[a-z0-9-]+/){2}){1,6})fragments/$', fragment_list_resource),
index 61db42f..87ab727 100644 (file)
@@ -10,7 +10,7 @@ from catalogue.models import Tag, Book, Fragment, BookMedia, Collection
 
 
 class TagAdmin(admin.ModelAdmin):
-    list_display = ('name', 'slug', 'sort_key', 'category', 'has_description', 'main_page',)
+    list_display = ('name', 'slug', 'sort_key', 'category', 'has_description',)
     list_filter = ('category',)
     search_fields = ('name',)
     ordering = ('name',)
index 0488244..e19df9d 100644 (file)
@@ -73,20 +73,12 @@ class JSONField(models.TextField):
 
 
 class JQueryAutoCompleteWidget(forms.TextInput):
-    def __init__(self, source, options=None, *args, **kwargs):
-        self.source = source
-        self.options = None
-        if options:
-            self.options = dumps(options)
+    def __init__(self, options, *args, **kwargs):
+        self.options = dumps(options)
         super(JQueryAutoCompleteWidget, self).__init__(*args, **kwargs)
 
-    def render_js(self, field_id):
-        source = "'%s'" % escape(self.source)
-        options = ''
-        if self.options:
-            options += ', %s' % self.options
-
-        return u'$(\'#%s\').autocomplete(%s%s).result(autocomplete_result_handler);' % (field_id, source, options)
+    def render_js(self, field_id, options):
+        return u'$(\'#%s\').autocomplete(%s).result(autocomplete_result_handler);' % (field_id, options)
 
     def render(self, name, value=None, attrs=None):
         final_attrs = self.build_attrs(attrs, name=name)
@@ -100,21 +92,38 @@ class JQueryAutoCompleteWidget(forms.TextInput):
             <script type="text/javascript">//<!--
             %(js)s//--></script>
             ''' % {
-                'attrs' : flatatt(final_attrs),
-                'js' : self.render_js(final_attrs['id']),
+                'attrs': flatatt(final_attrs),
+                'js' : self.render_js(final_attrs['id'], self.options),
             }
 
         return mark_safe(html)
 
 
+class JQueryAutoCompleteSearchWidget(JQueryAutoCompleteWidget):
+    def __init__(self, *args, **kwargs):
+        super(JQueryAutoCompleteSearchWidget, self).__init__(*args, **kwargs)
+
+    def render_js(self, field_id, options):
+        return u""
+    
+
 class JQueryAutoCompleteField(forms.CharField):
-    def __init__(self, source, options=None, *args, **kwargs):
+    def __init__(self, source, options={}, *args, **kwargs):
         if 'widget' not in kwargs:
-            kwargs['widget'] = JQueryAutoCompleteWidget(source, options)
+            options['source'] = source
+            kwargs['widget'] = JQueryAutoCompleteWidget(options)
 
         super(JQueryAutoCompleteField, self).__init__(*args, **kwargs)
 
 
+class JQueryAutoCompleteSearchField(forms.CharField):
+    def __init__(self, options={}, *args, **kwargs):
+        if 'widget' not in kwargs:
+            kwargs['widget'] = JQueryAutoCompleteSearchWidget(options)
+
+        super(JQueryAutoCompleteSearchField, self).__init__(*args, **kwargs)
+
+
 class OverwritingFieldFile(FieldFile):
     """
         Deletes the old file before saving the new one.
index 391e3e4..655f1ec 100644 (file)
@@ -7,7 +7,7 @@ from django.utils.translation import ugettext_lazy as _
 from slughifi import slughifi
 
 from catalogue.models import Tag, Book
-from catalogue.fields import JQueryAutoCompleteField
+from catalogue.fields import JQueryAutoCompleteSearchField
 from catalogue import utils
 
 
@@ -31,15 +31,16 @@ class BookImportForm(forms.Form):
 
 
 class SearchForm(forms.Form):
-    q = JQueryAutoCompleteField('/katalog/tags/', {'minChars': 2, 'selectFirst': True, 'cacheLength': 50, 'matchContains': "word"})
-    tags = forms.CharField(widget=forms.HiddenInput, required=False)
+    q = JQueryAutoCompleteSearchField()  # {'minChars': 2, 'selectFirst': True, 'cacheLength': 50, 'matchContains': "word"})
 
-    def __init__(self, *args, **kwargs):
-        tags = kwargs.pop('tags', [])
+    def __init__(self, source, *args, **kwargs):
+        kwargs['auto_id'] = False
         super(SearchForm, self).__init__(*args, **kwargs)
-        self.fields['q'].widget.attrs['title'] = _('title, author, theme/topic, epoch, kind, genre')
-           #self.fields['q'].widget.attrs['style'] = ''
-        self.fields['tags'].initial = '/'.join(tag.url_chunk for tag in Tag.get_tag_list(tags))
+        self.fields['q'].widget.attrs['id'] = _('search')
+        self.fields['q'].widget.attrs['autocomplete'] = _('off')
+        self.fields['q'].widget.attrs['data-source'] = _(source)
+        if not 'q' in self.data:
+            self.fields['q'].widget.attrs['title'] = _('title, author, theme/topic, epoch, kind, genre, phrase')
 
 
 class UserSetsForm(forms.Form):
@@ -56,7 +57,7 @@ class ObjectSetsForm(forms.Form):
         self.fields['set_ids'] = forms.MultipleChoiceField(
             label=_('Shelves'),
             required=False,
-            choices=[(tag.id, "%s (%s)" % (tag.name, tag.get_count())) for tag in Tag.objects.filter(category='set', user=user)],
+            choices=[(tag.id, "%s (%s)" % (tag.name, tag.book_count)) for tag in Tag.objects.filter(category='set', user=user)],
             initial=[tag.id for tag in obj.tags.filter(category='set', user=user)],
             widget=forms.CheckboxSelectMultiple
         )
@@ -78,21 +79,62 @@ class NewSetForm(forms.Form):
         return new_set
 
 
-FORMATS = (
-    ('mp3', 'MP3'),
-    ('ogg', 'OGG'),
-    ('pdf', 'PDF'),
-    ('odt', 'ODT'),
-    ('txt', 'TXT'),
-    ('epub', 'EPUB'),
-    ('daisy', 'DAISY'),
-    ('mobi', 'MOBI'),
-)
+FORMATS = [(f, f.upper()) for f in Book.ebook_formats]
 
 
 class DownloadFormatsForm(forms.Form):
-    formats = forms.MultipleChoiceField(required=False, choices=FORMATS, widget=forms.CheckboxSelectMultiple)
+    formats = forms.MultipleChoiceField(required=False, choices=FORMATS,
+            widget=forms.CheckboxSelectMultiple)
 
     def __init__(self, *args, **kwargs):
          super(DownloadFormatsForm, self).__init__(*args, **kwargs)
 
+
+PDF_PAGE_SIZES = (
+    ('a4paper', _('A4')),
+    ('a5paper', _('A5')),
+)
+
+
+PDF_LEADINGS = (
+    ('', _('Normal leading')),
+    ('onehalfleading', _('One and a half leading')),
+    ('doubleleading', _('Double leading')),
+    )
+
+PDF_FONT_SIZES = (
+    ('11pt', _('Default')),
+    ('13pt', _('Big'))
+    )
+
+
+class CustomPDFForm(forms.Form):
+    nofootnotes = forms.BooleanField(required=False, label=_("Don't show footnotes"))
+    nothemes = forms.BooleanField(required=False, label=_("Don't disply themes"))
+    nowlfont = forms.BooleanField(required=False, label=_("Don't use our custom font"))
+    ##    pagesize = forms.ChoiceField(PDF_PAGE_SIZES, required=True, label=_("Paper size"))
+    leading = forms.ChoiceField(PDF_LEADINGS, required=False, label=_("Leading"))
+    fontsize = forms.ChoiceField(PDF_FONT_SIZES, required=True, label=_("Font size"))
+
+    @property
+    def customizations(self):
+        c = []
+        if self.cleaned_data['nofootnotes']:
+            c.append('nofootnotes')
+            
+        if self.cleaned_data['nothemes']:
+            c.append('nothemes')
+            
+        if self.cleaned_data['nowlfont']:
+            c.append('nowlfont')
+        
+            ##  c.append(self.cleaned_data['pagesize'])
+        c.append(self.cleaned_data['fontsize'])
+
+        if self.cleaned_data['leading']:
+            c.append(self.cleaned_data['leading'])
+
+        c.sort()
+
+        return c
+
diff --git a/apps/catalogue/import_utils.py b/apps/catalogue/import_utils.py
new file mode 100755 (executable)
index 0000000..bf36ea5
--- /dev/null
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
+from librarian import DocProvider
+
+class ORMDocProvider(DocProvider):
+    """Used for getting books' children."""
+
+    def __init__(self, book):
+        self.book = book
+
+    def by_slug(self, slug):
+        if slug == self.book.slug:
+            return open(self.book.xml_file.path)
+        else:
+            return type(self.book).objects.get(slug=slug).xml_file
\ No newline at end of file
index 6a4a559..3184920 100644 (file)
Binary files a/apps/catalogue/locale/pl/LC_MESSAGES/django.mo and b/apps/catalogue/locale/pl/LC_MESSAGES/django.mo differ
index 648a1ab..ca4f243 100644 (file)
@@ -7,8 +7,8 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2012-01-10 13:04+0100\n"
-"PO-Revision-Date: 2012-01-10 13:05+0100\n"
+"POT-Creation-Date: 2011-10-11 15:44+0200\n"
+"PO-Revision-Date: 2011-10-11 15:44+0100\n"
 "Last-Translator: Radek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
 "Language: \n"
@@ -22,226 +22,218 @@ msgstr ""
 msgid "Enter a valid JSON value. Error: %s"
 msgstr "Wprowadź prawidłową wartość JSON. Błąd: %s"
 
-#: forms.py:26
+#: forms.py:25
 msgid "Please supply an XML."
 msgstr "Proszę podać XML."
 
-#: forms.py:40
+#: forms.py:39
 msgid "title, author, theme/topic, epoch, kind, genre"
 msgstr "tytuł, autor, motyw/temat, epoka, rodzaj, gatunek"
 
-#: forms.py:57
+#: forms.py:56
 msgid "Shelves"
 msgstr "Półki"
 
-#: forms.py:70
+#: forms.py:69
 msgid "Name of the new shelf"
 msgstr "nazwa nowej półki"
 
-#: models.py:29
+#: models.py:35
 msgid "author"
 msgstr "autor"
 
-#: models.py:30
+#: models.py:36
 msgid "epoch"
 msgstr "epoka"
 
-#: models.py:31
+#: models.py:37
 msgid "kind"
 msgstr "rodzaj"
 
-#: models.py:32
+#: models.py:38
 msgid "genre"
 msgstr "gatunek"
 
-#: models.py:33
+#: models.py:39
 msgid "theme"
 msgstr "motyw"
 
-#: models.py:34
+#: models.py:40
 msgid "set"
 msgstr "półka"
 
-#: models.py:35
-#: models.py:319
+#: models.py:41 models.py:314
 msgid "book"
 msgstr "książka"
 
-#: models.py:39
+#: models.py:45
 msgid "ODT file"
 msgstr "Plik ODT"
 
-#: models.py:40
+#: models.py:46
 msgid "MP3 file"
 msgstr "Plik MP3"
 
-#: models.py:41
+#: models.py:47
 msgid "OGG file"
 msgstr "Plik OGG"
 
-#: models.py:42
+#: models.py:48
 msgid "DAISY file"
 msgstr "Plik DAISY"
 
-#: models.py:59
-#: models.py:194
+#: models.py:65 models.py:198
 msgid "name"
 msgstr "nazwa"
 
-#: models.py:60
-#: models.py:294
-#: models.py:951
-#: models.py:968
-#: models.py:971
+#: models.py:66 models.py:287 models.py:876
 msgid "slug"
 msgstr "slug"
 
-#: models.py:61
-#: models.py:293
+#: models.py:67 models.py:286
 msgid "sort key"
 msgstr "klucz sortowania"
 
-#: models.py:62
+#: models.py:68
 msgid "category"
 msgstr "kategoria"
 
-#: models.py:64
-#: models.py:106
-#: models.py:295
-#: models.py:444
-#: models.py:969
+#: models.py:70 models.py:112 models.py:288 models.py:469
 msgid "description"
 msgstr "opis"
 
-#: models.py:68
+#: models.py:71
+msgid "main page"
+msgstr "strona główna"
+
+#: models.py:71
+msgid "Show tag on main page"
+msgstr "Pokazuj tag na stronie głównej"
+
+#: models.py:74
 msgid "book count"
 msgstr "liczba książek"
 
-#: models.py:72
-#: models.py:73
-#: models.py:196
-#: models.py:296
-#: models.py:297
+#: models.py:78 models.py:79 models.py:200 models.py:289 models.py:290
 msgid "creation date"
 msgstr "data utworzenia"
 
-#: models.py:90
+#: models.py:96
 msgid "tag"
 msgstr "tag"
 
-#: models.py:91
+#: models.py:97
 msgid "tags"
 msgstr "tagi"
 
-#: models.py:193
-#: models.py:952
+#: models.py:197 models.py:877
 msgid "type"
 msgstr "typ"
 
-#: models.py:195
+#: models.py:199
 msgid "file"
 msgstr "plik"
 
-#: models.py:197
-#: models.py:299
+#: models.py:201 models.py:292
 msgid "extra information"
 msgstr "dodatkowe informacje"
 
-#: models.py:206
-#: models.py:207
+#: models.py:210 models.py:211
 msgid "book media"
 msgstr "media książki"
 
-#: models.py:292
-#: models.py:967
+#: models.py:285
 msgid "title"
 msgstr "tytuł"
 
-#: models.py:298
+#: models.py:291
 msgid "parent number"
 msgstr "numer rodzica"
 
-#: models.py:320
+#: models.py:296
+msgid "XML file"
+msgstr "Plik XML"
+
+#: models.py:297
+msgid "HTML file"
+msgstr "Plik HTML"
+
+#: models.py:298
+msgid "PDF file"
+msgstr "Plik PDF"
+
+#: models.py:299
+msgid "EPUB file"
+msgstr "Plik EPUB"
+
+#: models.py:300
+msgid "TXT file"
+msgstr "Plik TXT"
+
+#: models.py:315
 msgid "books"
 msgstr "książki"
 
-#: models.py:408
+#: models.py:435
 msgid "Read online"
 msgstr "Czytaj online"
 
-#: models.py:672
+#: models.py:656
 #, python-format
 msgid "Book with slug = \"%s\" does not exist."
 msgstr "Książki ο slug = \"%s\" nie istnieje."
 
-#: models.py:685
+#: models.py:669
 #, python-format
 msgid "Book %s already exists"
 msgstr "Książka %s już istnieje"
 
-#: models.py:900
-#, python-format
-msgid "%s file"
-msgstr "plik %s"
-
-#: models.py:919
+#: models.py:844
 msgid "fragment"
 msgstr "fragment"
 
-#: models.py:920
+#: models.py:845
 msgid "fragments"
 msgstr "fragmenty"
 
-#: models.py:953
+#: models.py:878
 msgid "sha-1 hash"
 msgstr "hash sha-1"
 
-#: models.py:954
+#: models.py:879
 msgid "time"
 msgstr "czas"
 
-#: models.py:958
+#: models.py:883
 msgid "file record"
 msgstr ""
 
-#: models.py:959
+#: models.py:884
 msgid "file records"
 msgstr ""
 
-#: models.py:972
-msgid "book slugs"
-msgstr "slugi książek"
-
-#: models.py:976
-msgid "collection"
-msgstr "kolekcja"
-
-#: models.py:977
-msgid "collections"
-msgstr "kolekcje"
-
-#: views.py:509
+#: views.py:531
 msgid "<p>To maintain your shelves you need to be logged in.</p>"
 msgstr "<p>Aby zarządzać swoimi półkami, musisz się zalogować.</p>"
 
-#: views.py:531
+#: views.py:553
 msgid "<p>Shelves were sucessfully saved.</p>"
 msgstr "<p>Półki zostały zapisane.</p>"
 
-#: views.py:555
+#: views.py:577
 msgid "Book was successfully removed from the shelf"
 msgstr "Usunięto"
 
-#: views.py:557
+#: views.py:579
 msgid "This book is not on the shelf"
 msgstr "Książki nie ma na półce"
 
-#: views.py:676
+#: views.py:689
 #, python-format
 msgid "<p>Shelf <strong>%s</strong> was successfully removed</p>"
 msgstr "<p>Półka <strong>%s</strong> została usunięta</p>"
 
-#: views.py:738
+#: views.py:748
 #, python-format
 msgid ""
 "An error occurred: %(exception)s\n"
@@ -252,36 +244,15 @@ msgstr ""
 "\n"
 "%(tb)s"
 
-#: views.py:739
+#: views.py:749
 msgid "Book imported successfully"
 msgstr "Książka zaimportowana"
 
-#: views.py:741
+#: views.py:751
 #, python-format
 msgid "Error importing file: %r"
 msgstr "Błąd podczas importowania pliku: %r"
 
-#~ msgid "main page"
-#~ msgstr "strona główna"
-
-#~ msgid "Show tag on main page"
-#~ msgstr "Pokazuj tag na stronie głównej"
-
-#~ msgid "XML file"
-#~ msgstr "Plik XML"
-
-#~ msgid "HTML file"
-#~ msgstr "Plik HTML"
-
-#~ msgid "PDF file"
-#~ msgstr "Plik PDF"
-
-#~ msgid "EPUB file"
-#~ msgstr "Plik EPUB"
-
-#~ msgid "TXT file"
-#~ msgstr "Plik TXT"
-
 #, fuzzy
 #~ msgid "sort_key"
 #~ msgstr "klucz sortowania"
@@ -304,5 +275,8 @@ msgstr "Błąd podczas importowania pliku: %r"
 #~ msgid "book stub"
 #~ msgstr "zapowiedź książki"
 
+#~ msgid "book stubs"
+#~ msgstr "zapowiedzi książek"
+
 #~ msgid "<p>Shelf <strong>%s</strong> was successfully created</p>"
 #~ msgstr "<p>Półka <strong>%s</strong> została utworzona</p>"
index ecd3fcc..5b4d499 100644 (file)
@@ -12,6 +12,7 @@ from django.core.management.color import color_style
 from django.core.files import File
 
 from catalogue.models import Book
+from picture.models import Picture
 
 
 class Command(BaseCommand):
@@ -28,12 +29,39 @@ class Command(BaseCommand):
             help='Don\'t build TXT file'),
         make_option('-P', '--no-build-pdf', action='store_false', dest='build_pdf', default=True,
             help='Don\'t build PDF file'),
+        make_option('-S', '--no-search-index', action='store_false', dest='search_index', default=True,
+            help='Don\'t build PDF file'),
         make_option('-w', '--wait-until', dest='wait_until', metavar='TIME',
             help='Wait until specified time (Y-M-D h:m:s)'),
+        make_option('-p', '--picture', action='store_true', dest='import_picture', default=False,
+            help='Import pictures'),
     )
     help = 'Imports books from the specified directories.'
     args = 'directory [directory ...]'
 
+    def import_book(self, file_path, options):
+        verbose = options.get('verbose')
+        file_base, ext = os.path.splitext(file_path)
+        book = Book.from_xml_file(file_path, overwrite=options.get('force'),
+                                                    build_epub=options.get('build_epub'),
+                                                    build_txt=options.get('build_txt'),
+                                                    build_pdf=options.get('build_pdf'),
+                                                    build_mobi=options.get('build_mobi'),
+                                                    search_index=options.get('search_index'))
+        for ebook_format in Book.ebook_formats:
+            if os.path.isfile(file_base + '.' + ebook_format):
+                getattr(book, '%s_file' % ebook_format).save(
+                    '%s.%s' % (book.slug, ebook_format), 
+                    File(file(file_base + '.' + ebook_format)))
+                if verbose:
+                    print "Importing %s.%s" % (file_base, ebook_format)
+
+        book.save()
+
+    def import_picture(self, file_path, options):
+        picture = Picture.from_xml_file(file_path, overwrite=options.get('force'))
+        return picture
+
     def handle(self, *directories, **options):
         from django.db import transaction
 
@@ -42,13 +70,14 @@ class Command(BaseCommand):
         verbose = options.get('verbose')
         force = options.get('force')
         show_traceback = options.get('traceback', False)
+        import_picture = options.get('import_picture')
 
         wait_until = None
         if options.get('wait_until'):
             wait_until = time.mktime(time.strptime(options.get('wait_until'), '%Y-%m-%d %H:%M:%S'))
             if verbose > 0:
                 print "Will wait until %s; it's %f seconds from now" % (
-                    time.strftime('%Y-%m-%d %H:%M:%S', 
+                    time.strftime('%Y-%m-%d %H:%M:%S',
                     time.localtime(wait_until)), wait_until - time.time())
 
         # Start transaction management.
@@ -83,34 +112,14 @@ class Command(BaseCommand):
 
                     # Import book files
                     try:
-                        book = Book.from_xml_file(file_path, overwrite=force, 
-                                                  build_epub=options.get('build_epub'),
-                                                  build_txt=options.get('build_txt'),
-                                                  build_pdf=options.get('build_pdf'),
-                                                  build_mobi=options.get('build_mobi'))
+                        if import_picture:
+                            self.import_picture(file_path, options)
+                        else:
+                            self.import_book(file_path, options)
                         files_imported += 1
 
-                        if os.path.isfile(file_base + '.pdf'):
-                            book.pdf_file.save('%s.pdf' % book.slug, File(file(file_base + '.pdf')))
-                            if verbose:
-                                print "Importing %s.pdf" % file_base
-                        if os.path.isfile(file_base + '.mobi'):
-                            book.mobi_file.save('%s.mobi' % book.slug, File(file(file_base + '.mobi')))
-                            if verbose:
-                                print "Importing %s.mobi" % file_base
-                        if os.path.isfile(file_base + '.epub'):
-                            book.epub_file.save('%s.epub' % book.slug, File(file(file_base + '.epub')))
-                            if verbose:
-                                print "Importing %s.epub" % file_base
-                        if os.path.isfile(file_base + '.txt'):
-                            book.txt_file.save('%s.txt' % book.slug, File(file(file_base + '.txt')))
-                            if verbose:
-                                print "Importing %s.txt" % file_base
-
-                        book.save()
-
-                    except Book.AlreadyExists, msg:
-                        print self.style.ERROR('%s: Book already imported. Skipping. To overwrite use --force.' %
+                    except (Book.AlreadyExists, Picture.AlreadyExists):
+                        print self.style.ERROR('%s: Book or Picture already imported. Skipping. To overwrite use --force.' %
                             file_path)
                         files_skipped += 1
 
index c75f092..280c0f6 100755 (executable)
@@ -23,7 +23,7 @@ class Command(BaseCommand):
         make_option('-e', '--exclude', dest='exclude', metavar='SLUG,...',
             help='Exclude specific books by slug')
     )
-    help = 'Prepare data for Lesmianator.'
+    help = 'Prepare ZIP package with files of given type.'
     args = '[%s] output_path.zip' % '|'.join(ftypes)
 
     def handle(self, ftype, path, **options):
@@ -33,7 +33,7 @@ class Command(BaseCommand):
         include = options.get('include')
         exclude = options.get('exclude')
 
-        if ftype in Book.file_types:
+        if ftype in Book.formats:
             field = "%s_file" % ftype
         else:
             print self.style.ERROR('Unknown file type.')
diff --git a/apps/catalogue/migrations/0017_auto__add_collection.py b/apps/catalogue/migrations/0017_auto__add_collection.py
deleted file mode 100644 (file)
index 2e9ca41..0000000
+++ /dev/null
@@ -1,147 +0,0 @@
-# encoding: utf-8
-import datetime
-from south.db import db
-from south.v2 import SchemaMigration
-from django.db import models
-
-class Migration(SchemaMigration):
-
-    def forwards(self, orm):
-        
-        # Adding model 'Collection'
-        db.create_table('catalogue_collection', (
-            ('title', self.gf('django.db.models.fields.CharField')(max_length=120, db_index=True)),
-            ('slug', self.gf('django.db.models.fields.SlugField')(max_length=120, primary_key=True, db_index=True)),
-            ('description', self.gf('django.db.models.fields.TextField')(null=True, blank=True)),
-            ('book_slugs', self.gf('django.db.models.fields.TextField')()),
-        ))
-        db.send_create_signal('catalogue', ['Collection'])
-
-        if not db.dry_run:
-            from django.core.management import call_command
-            call_command("loaddata", "collection-boy.json")
-
-    def backwards(self, orm):
-        
-        # Deleting model 'Collection'
-        db.delete_table('catalogue_collection')
-
-
-    models = {
-        'auth.group': {
-            'Meta': {'object_name': 'Group'},
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
-            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
-        },
-        'auth.permission': {
-            'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
-            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
-            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
-        },
-        'auth.user': {
-            'Meta': {'object_name': 'User'},
-            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
-            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
-            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
-            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
-            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
-            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
-            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
-            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
-            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
-            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
-            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
-        },
-        'catalogue.book': {
-            'Meta': {'ordering': "('sort_key',)", 'object_name': 'Book'},
-            'changed_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
-            'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
-            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
-            'epub_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
-            'extra_info': ('catalogue.fields.JSONField', [], {'default': "'{}'"}),
-            'gazeta_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}),
-            'html_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'mobi_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
-            'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['catalogue.Book']"}),
-            'parent_number': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
-            'pdf_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
-            'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '120', 'db_index': 'True'}),
-            'sort_key': ('django.db.models.fields.CharField', [], {'max_length': '120', 'db_index': 'True'}),
-            'title': ('django.db.models.fields.CharField', [], {'max_length': '120'}),
-            'txt_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
-            'wiki_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}),
-            'xml_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'})
-        },
-        'catalogue.bookmedia': {
-            'Meta': {'ordering': "('type', 'name')", 'object_name': 'BookMedia'},
-            'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'media'", 'to': "orm['catalogue.Book']"}),
-            'extra_info': ('catalogue.fields.JSONField', [], {'default': "'{}'"}),
-            'file': ('catalogue.fields.OverwritingFileField', [], {'max_length': '100'}),
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'name': ('django.db.models.fields.CharField', [], {'max_length': "'100'"}),
-            'source_sha1': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}),
-            'type': ('django.db.models.fields.CharField', [], {'max_length': "'100'"}),
-            'uploaded_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'})
-        },
-        'catalogue.collection': {
-            'Meta': {'ordering': "('title',)", 'object_name': 'Collection'},
-            'book_slugs': ('django.db.models.fields.TextField', [], {}),
-            'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
-            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '120', 'primary_key': 'True', 'db_index': 'True'}),
-            'title': ('django.db.models.fields.CharField', [], {'max_length': '120', 'db_index': 'True'})
-        },
-        'catalogue.filerecord': {
-            'Meta': {'ordering': "('-time', '-slug', '-type')", 'object_name': 'FileRecord'},
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'sha1': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
-            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '120', 'db_index': 'True'}),
-            'time': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
-            'type': ('django.db.models.fields.CharField', [], {'max_length': '20', 'db_index': 'True'})
-        },
-        'catalogue.fragment': {
-            'Meta': {'ordering': "('book', 'anchor')", 'object_name': 'Fragment'},
-            'anchor': ('django.db.models.fields.CharField', [], {'max_length': '120'}),
-            'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'fragments'", 'to': "orm['catalogue.Book']"}),
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'short_text': ('django.db.models.fields.TextField', [], {}),
-            'text': ('django.db.models.fields.TextField', [], {})
-        },
-        'catalogue.tag': {
-            'Meta': {'ordering': "('sort_key',)", 'unique_together': "(('slug', 'category'),)", 'object_name': 'Tag'},
-            'book_count': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
-            'category': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}),
-            'changed_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
-            'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
-            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
-            'gazeta_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}),
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'main_page': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}),
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}),
-            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '120', 'db_index': 'True'}),
-            'sort_key': ('django.db.models.fields.CharField', [], {'max_length': '120', 'db_index': 'True'}),
-            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
-            'wiki_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'})
-        },
-        'catalogue.tagrelation': {
-            'Meta': {'unique_together': "(('tag', 'content_type', 'object_id'),)", 'object_name': 'TagRelation', 'db_table': "'catalogue_tag_relation'"},
-            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'object_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
-            'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'items'", 'to': "orm['catalogue.Tag']"})
-        },
-        'contenttypes.contenttype': {
-            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
-            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
-        }
-    }
-
-    complete_apps = ['catalogue']
diff --git a/apps/catalogue/migrations/0017_auto__add_field_book_language__del_unique_book_slug__add_unique_book_s.py b/apps/catalogue/migrations/0017_auto__add_field_book_language__del_unique_book_slug__add_unique_book_s.py
new file mode 100644 (file)
index 0000000..6d1edcf
--- /dev/null
@@ -0,0 +1,144 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        
+        # Removing unique constraint on 'Book', fields ['slug']
+        db.delete_unique('catalogue_book', ['slug'])
+
+        # Adding field 'Book.language'
+        db.add_column('catalogue_book', 'language', self.gf('django.db.models.fields.CharField')(default='pol', max_length=3, db_index=True), keep_default=False)
+
+        # Adding unique constraint on 'Book', fields ['slug', 'language']
+        db.create_unique('catalogue_book', ['slug', 'language'])
+
+
+    def backwards(self, orm):
+        
+        # Removing unique constraint on 'Book', fields ['slug', 'language']
+        db.delete_unique('catalogue_book', ['slug', 'language'])
+
+        # Deleting field 'Book.language'
+        db.delete_column('catalogue_book', 'language')
+
+        # Adding unique constraint on 'Book', fields ['slug']
+        db.create_unique('catalogue_book', ['slug'])
+
+
+    models = {
+        'auth.group': {
+            'Meta': {'object_name': 'Group'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        'auth.permission': {
+            'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        'auth.user': {
+            'Meta': {'object_name': 'User'},
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+        },
+        'catalogue.book': {
+            'Meta': {'ordering': "('sort_key',)", 'unique_together': "[['slug', 'language']]", 'object_name': 'Book'},
+            'changed_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'epub_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+            'extra_info': ('catalogue.fields.JSONField', [], {'default': "'{}'"}),
+            'gazeta_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}),
+            'html_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'language': ('django.db.models.fields.CharField', [], {'default': "'pol'", 'max_length': '3', 'db_index': 'True'}),
+            'mobi_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+            'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['catalogue.Book']"}),
+            'parent_number': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'pdf_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '120', 'db_index': 'True'}),
+            'sort_key': ('django.db.models.fields.CharField', [], {'max_length': '120', 'db_index': 'True'}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '120'}),
+            'txt_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+            'wiki_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}),
+            'xml_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'})
+        },
+        'catalogue.bookmedia': {
+            'Meta': {'ordering': "('type', 'name')", 'object_name': 'BookMedia'},
+            'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'media'", 'to': "orm['catalogue.Book']"}),
+            'extra_info': ('catalogue.fields.JSONField', [], {'default': "'{}'"}),
+            'file': ('catalogue.fields.OverwritingFileField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': "'100'"}),
+            'source_sha1': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}),
+            'type': ('django.db.models.fields.CharField', [], {'max_length': "'100'"}),
+            'uploaded_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'})
+        },
+        'catalogue.filerecord': {
+            'Meta': {'ordering': "('-time', '-slug', '-type')", 'object_name': 'FileRecord'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'sha1': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '120', 'db_index': 'True'}),
+            'time': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'type': ('django.db.models.fields.CharField', [], {'max_length': '20', 'db_index': 'True'})
+        },
+        'catalogue.fragment': {
+            'Meta': {'ordering': "('book', 'anchor')", 'object_name': 'Fragment'},
+            'anchor': ('django.db.models.fields.CharField', [], {'max_length': '120'}),
+            'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'fragments'", 'to': "orm['catalogue.Book']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'short_text': ('django.db.models.fields.TextField', [], {}),
+            'text': ('django.db.models.fields.TextField', [], {})
+        },
+        'catalogue.tag': {
+            'Meta': {'ordering': "('sort_key',)", 'unique_together': "(('slug', 'category'),)", 'object_name': 'Tag'},
+            'book_count': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'category': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}),
+            'changed_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'gazeta_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'main_page': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '120', 'db_index': 'True'}),
+            'sort_key': ('django.db.models.fields.CharField', [], {'max_length': '120', 'db_index': 'True'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
+            'wiki_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'})
+        },
+        'catalogue.tagrelation': {
+            'Meta': {'unique_together': "(('tag', 'content_type', 'object_id'),)", 'object_name': 'TagRelation', 'db_table': "'catalogue_tag_relation'"},
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'object_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+            'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'items'", 'to': "orm['catalogue.Tag']"})
+        },
+        'contenttypes.contenttype': {
+            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        }
+    }
+
+    complete_apps = ['catalogue']
diff --git a/apps/catalogue/migrations/0018_auto__del_filerecord.py b/apps/catalogue/migrations/0018_auto__del_filerecord.py
new file mode 100644 (file)
index 0000000..66a6542
--- /dev/null
@@ -0,0 +1,131 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        
+        # Deleting model 'FileRecord'
+        db.delete_table('catalogue_filerecord')
+
+
+    def backwards(self, orm):
+        
+        # Adding model 'FileRecord'
+        db.create_table('catalogue_filerecord', (
+            ('sha1', self.gf('django.db.models.fields.CharField')(max_length=40)),
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+            ('time', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
+            ('type', self.gf('django.db.models.fields.CharField')(max_length=20, db_index=True)),
+            ('slug', self.gf('django.db.models.fields.SlugField')(max_length=120, db_index=True)),
+        ))
+        db.send_create_signal('catalogue', ['FileRecord'])
+
+
+    models = {
+        'auth.group': {
+            'Meta': {'object_name': 'Group'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        'auth.permission': {
+            'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        'auth.user': {
+            'Meta': {'object_name': 'User'},
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+        },
+        'catalogue.book': {
+            'Meta': {'ordering': "('sort_key',)", 'unique_together': "[['slug', 'language']]", 'object_name': 'Book'},
+            'changed_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'epub_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+            'extra_info': ('catalogue.fields.JSONField', [], {'default': "'{}'"}),
+            'gazeta_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}),
+            'html_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'language': ('django.db.models.fields.CharField', [], {'default': "'pol'", 'max_length': '3', 'db_index': 'True'}),
+            'mobi_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+            'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['catalogue.Book']"}),
+            'parent_number': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'pdf_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '120', 'db_index': 'True'}),
+            'sort_key': ('django.db.models.fields.CharField', [], {'max_length': '120', 'db_index': 'True'}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '120'}),
+            'txt_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+            'wiki_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}),
+            'xml_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'})
+        },
+        'catalogue.bookmedia': {
+            'Meta': {'ordering': "('type', 'name')", 'object_name': 'BookMedia'},
+            'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'media'", 'to': "orm['catalogue.Book']"}),
+            'extra_info': ('catalogue.fields.JSONField', [], {'default': "'{}'"}),
+            'file': ('catalogue.fields.OverwritingFileField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': "'100'"}),
+            'source_sha1': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}),
+            'type': ('django.db.models.fields.CharField', [], {'max_length': "'100'"}),
+            'uploaded_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'})
+        },
+        'catalogue.fragment': {
+            'Meta': {'ordering': "('book', 'anchor')", 'object_name': 'Fragment'},
+            'anchor': ('django.db.models.fields.CharField', [], {'max_length': '120'}),
+            'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'fragments'", 'to': "orm['catalogue.Book']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'short_text': ('django.db.models.fields.TextField', [], {}),
+            'text': ('django.db.models.fields.TextField', [], {})
+        },
+        'catalogue.tag': {
+            'Meta': {'ordering': "('sort_key',)", 'unique_together': "(('slug', 'category'),)", 'object_name': 'Tag'},
+            'book_count': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'category': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}),
+            'changed_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'gazeta_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'main_page': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '120', 'db_index': 'True'}),
+            'sort_key': ('django.db.models.fields.CharField', [], {'max_length': '120', 'db_index': 'True'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
+            'wiki_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'})
+        },
+        'catalogue.tagrelation': {
+            'Meta': {'unique_together': "(('tag', 'content_type', 'object_id'),)", 'object_name': 'TagRelation', 'db_table': "'catalogue_tag_relation'"},
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'object_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+            'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'items'", 'to': "orm['catalogue.Tag']"})
+        },
+        'contenttypes.contenttype': {
+            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        }
+    }
+
+    complete_apps = ['catalogue']
diff --git a/apps/catalogue/migrations/0019_auto__add_field_book_cover.py b/apps/catalogue/migrations/0019_auto__add_field_book_cover.py
new file mode 100644 (file)
index 0000000..259d935
--- /dev/null
@@ -0,0 +1,125 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        
+        # Adding field 'Book.cover'
+        db.add_column('catalogue_book', 'cover', self.gf('django.db.models.fields.files.FileField')(max_length=100, null=True, blank=True), keep_default=False)
+
+
+    def backwards(self, orm):
+        
+        # Deleting field 'Book.cover'
+        db.delete_column('catalogue_book', 'cover')
+
+
+    models = {
+        'auth.group': {
+            'Meta': {'object_name': 'Group'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        'auth.permission': {
+            'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        'auth.user': {
+            'Meta': {'object_name': 'User'},
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+        },
+        'catalogue.book': {
+            'Meta': {'ordering': "('sort_key',)", 'unique_together': "[['slug', 'language']]", 'object_name': 'Book'},
+            'changed_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'cover': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+            'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'epub_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+            'extra_info': ('catalogue.fields.JSONField', [], {'default': "'{}'"}),
+            'gazeta_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}),
+            'html_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'language': ('django.db.models.fields.CharField', [], {'default': "'pol'", 'max_length': '3', 'db_index': 'True'}),
+            'mobi_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+            'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['catalogue.Book']"}),
+            'parent_number': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'pdf_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '120', 'db_index': 'True'}),
+            'sort_key': ('django.db.models.fields.CharField', [], {'max_length': '120', 'db_index': 'True'}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '120'}),
+            'txt_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+            'wiki_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}),
+            'xml_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'})
+        },
+        'catalogue.bookmedia': {
+            'Meta': {'ordering': "('type', 'name')", 'object_name': 'BookMedia'},
+            'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'media'", 'to': "orm['catalogue.Book']"}),
+            'extra_info': ('catalogue.fields.JSONField', [], {'default': "'{}'"}),
+            'file': ('catalogue.fields.OverwritingFileField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': "'100'"}),
+            'source_sha1': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}),
+            'type': ('django.db.models.fields.CharField', [], {'max_length': "'100'"}),
+            'uploaded_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'})
+        },
+        'catalogue.fragment': {
+            'Meta': {'ordering': "('book', 'anchor')", 'object_name': 'Fragment'},
+            'anchor': ('django.db.models.fields.CharField', [], {'max_length': '120'}),
+            'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'fragments'", 'to': "orm['catalogue.Book']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'short_text': ('django.db.models.fields.TextField', [], {}),
+            'text': ('django.db.models.fields.TextField', [], {})
+        },
+        'catalogue.tag': {
+            'Meta': {'ordering': "('sort_key',)", 'unique_together': "(('slug', 'category'),)", 'object_name': 'Tag'},
+            'book_count': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'category': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}),
+            'changed_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'gazeta_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'main_page': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '120', 'db_index': 'True'}),
+            'sort_key': ('django.db.models.fields.CharField', [], {'max_length': '120', 'db_index': 'True'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
+            'wiki_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'})
+        },
+        'catalogue.tagrelation': {
+            'Meta': {'unique_together': "(('tag', 'content_type', 'object_id'),)", 'object_name': 'TagRelation', 'db_table': "'catalogue_tag_relation'"},
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'object_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+            'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'items'", 'to': "orm['catalogue.Tag']"})
+        },
+        'contenttypes.contenttype': {
+            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        }
+    }
+
+    complete_apps = ['catalogue']
diff --git a/apps/catalogue/migrations/0020_auto__del_field_tag_main_page.py b/apps/catalogue/migrations/0020_auto__del_field_tag_main_page.py
new file mode 100644 (file)
index 0000000..e9f7794
--- /dev/null
@@ -0,0 +1,124 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        
+        # Deleting field 'Tag.main_page'
+        db.delete_column('catalogue_tag', 'main_page')
+
+
+    def backwards(self, orm):
+        
+        # Adding field 'Tag.main_page'
+        db.add_column('catalogue_tag', 'main_page', self.gf('django.db.models.fields.BooleanField')(default=False, db_index=True), keep_default=False)
+
+
+    models = {
+        'auth.group': {
+            'Meta': {'object_name': 'Group'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        'auth.permission': {
+            'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        'auth.user': {
+            'Meta': {'object_name': 'User'},
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+        },
+        'catalogue.book': {
+            'Meta': {'ordering': "('sort_key',)", 'unique_together': "[['slug', 'language']]", 'object_name': 'Book'},
+            'changed_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'cover': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+            'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'epub_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+            'extra_info': ('catalogue.fields.JSONField', [], {'default': "'{}'"}),
+            'gazeta_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}),
+            'html_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'language': ('django.db.models.fields.CharField', [], {'default': "'pol'", 'max_length': '3', 'db_index': 'True'}),
+            'mobi_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+            'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['catalogue.Book']"}),
+            'parent_number': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'pdf_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '120', 'db_index': 'True'}),
+            'sort_key': ('django.db.models.fields.CharField', [], {'max_length': '120', 'db_index': 'True'}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '120'}),
+            'txt_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+            'wiki_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}),
+            'xml_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'})
+        },
+        'catalogue.bookmedia': {
+            'Meta': {'ordering': "('type', 'name')", 'object_name': 'BookMedia'},
+            'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'media'", 'to': "orm['catalogue.Book']"}),
+            'extra_info': ('catalogue.fields.JSONField', [], {'default': "'{}'"}),
+            'file': ('catalogue.fields.OverwritingFileField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': "'100'"}),
+            'source_sha1': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}),
+            'type': ('django.db.models.fields.CharField', [], {'max_length': "'100'"}),
+            'uploaded_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'})
+        },
+        'catalogue.fragment': {
+            'Meta': {'ordering': "('book', 'anchor')", 'object_name': 'Fragment'},
+            'anchor': ('django.db.models.fields.CharField', [], {'max_length': '120'}),
+            'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'fragments'", 'to': "orm['catalogue.Book']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'short_text': ('django.db.models.fields.TextField', [], {}),
+            'text': ('django.db.models.fields.TextField', [], {})
+        },
+        'catalogue.tag': {
+            'Meta': {'ordering': "('sort_key',)", 'unique_together': "(('slug', 'category'),)", 'object_name': 'Tag'},
+            'book_count': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'category': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}),
+            'changed_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'gazeta_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '120', 'db_index': 'True'}),
+            'sort_key': ('django.db.models.fields.CharField', [], {'max_length': '120', 'db_index': 'True'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
+            'wiki_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'})
+        },
+        'catalogue.tagrelation': {
+            'Meta': {'unique_together': "(('tag', 'content_type', 'object_id'),)", 'object_name': 'TagRelation', 'db_table': "'catalogue_tag_relation'"},
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'object_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+            'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'items'", 'to': "orm['catalogue.Tag']"})
+        },
+        'contenttypes.contenttype': {
+            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        }
+    }
+
+    complete_apps = ['catalogue']
diff --git a/apps/catalogue/migrations/0021_build_covers.py b/apps/catalogue/migrations/0021_build_covers.py
new file mode 100644 (file)
index 0000000..319decb
--- /dev/null
@@ -0,0 +1,137 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import DataMigration
+from django.db import models
+
+class Migration(DataMigration):
+
+    def forwards(self, orm):
+        "Write your forwards methods here."
+        from StringIO import StringIO
+        from django.core.files.base import ContentFile
+        from librarian import ValidationError
+        from librarian.cover import WLCover
+        from librarian.dcparser import BookInfo
+
+        for book in orm.Book.objects.filter(cover=None):
+            try:
+                book_info = BookInfo.from_file(book.xml_file.path)
+            except ValidationError:
+                pass
+            else:
+                cover = WLCover(book_info).image()
+                imgstr = StringIO()
+                cover.save(imgstr, 'png')
+                book.cover.save('book/png/%s.png' % book.slug,
+                    ContentFile(imgstr.getvalue()))
+
+
+    def backwards(self, orm):
+        "Write your backwards methods here."
+
+
+    models = {
+        'auth.group': {
+            'Meta': {'object_name': 'Group'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        'auth.permission': {
+            'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        'auth.user': {
+            'Meta': {'object_name': 'User'},
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+        },
+        'catalogue.book': {
+            'Meta': {'ordering': "('sort_key',)", 'unique_together': "[['slug', 'language']]", 'object_name': 'Book'},
+            'changed_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'cover': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+            'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'epub_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+            'extra_info': ('catalogue.fields.JSONField', [], {'default': "'{}'"}),
+            'gazeta_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}),
+            'html_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'language': ('django.db.models.fields.CharField', [], {'default': "'pol'", 'max_length': '3', 'db_index': 'True'}),
+            'mobi_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+            'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['catalogue.Book']"}),
+            'parent_number': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'pdf_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '120', 'db_index': 'True'}),
+            'sort_key': ('django.db.models.fields.CharField', [], {'max_length': '120', 'db_index': 'True'}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '120'}),
+            'txt_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+            'wiki_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}),
+            'xml_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'})
+        },
+        'catalogue.bookmedia': {
+            'Meta': {'ordering': "('type', 'name')", 'object_name': 'BookMedia'},
+            'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'media'", 'to': "orm['catalogue.Book']"}),
+            'extra_info': ('catalogue.fields.JSONField', [], {'default': "'{}'"}),
+            'file': ('catalogue.fields.OverwritingFileField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': "'100'"}),
+            'source_sha1': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}),
+            'type': ('django.db.models.fields.CharField', [], {'max_length': "'100'"}),
+            'uploaded_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'})
+        },
+        'catalogue.fragment': {
+            'Meta': {'ordering': "('book', 'anchor')", 'object_name': 'Fragment'},
+            'anchor': ('django.db.models.fields.CharField', [], {'max_length': '120'}),
+            'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'fragments'", 'to': "orm['catalogue.Book']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'short_text': ('django.db.models.fields.TextField', [], {}),
+            'text': ('django.db.models.fields.TextField', [], {})
+        },
+        'catalogue.tag': {
+            'Meta': {'ordering': "('sort_key',)", 'unique_together': "(('slug', 'category'),)", 'object_name': 'Tag'},
+            'book_count': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'category': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}),
+            'changed_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'gazeta_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '120', 'db_index': 'True'}),
+            'sort_key': ('django.db.models.fields.CharField', [], {'max_length': '120', 'db_index': 'True'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
+            'wiki_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'})
+        },
+        'catalogue.tagrelation': {
+            'Meta': {'unique_together': "(('tag', 'content_type', 'object_id'),)", 'object_name': 'TagRelation', 'db_table': "'catalogue_tag_relation'"},
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'object_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+            'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'items'", 'to': "orm['catalogue.Tag']"})
+        },
+        'contenttypes.contenttype': {
+            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        }
+    }
+
+    complete_apps = ['catalogue']
diff --git a/apps/catalogue/migrations/0022_auto__add_field_book_common_slug__add_unique_book_slug__del_unique_boo.py b/apps/catalogue/migrations/0022_auto__add_field_book_common_slug__add_unique_book_slug__del_unique_boo.py
new file mode 100644 (file)
index 0000000..75a1c99
--- /dev/null
@@ -0,0 +1,137 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        
+        # Removing unique constraint on 'Book', fields ['slug', 'language']
+        db.delete_unique('catalogue_book', ['slug', 'language'])
+
+        # Adding field 'Book.common_slug'
+        db.add_column('catalogue_book', 'common_slug', self.gf('django.db.models.fields.SlugField')(default='-', max_length=120, db_index=True), keep_default=False)
+
+        # Adding unique constraint on 'Book', fields ['slug']
+        db.create_unique('catalogue_book', ['slug'])
+
+
+    def backwards(self, orm):
+        
+        # Removing unique constraint on 'Book', fields ['slug']
+        db.delete_unique('catalogue_book', ['slug'])
+
+        # Deleting field 'Book.common_slug'
+        db.delete_column('catalogue_book', 'common_slug')
+
+        # Adding unique constraint on 'Book', fields ['slug', 'language']
+        db.create_unique('catalogue_book', ['slug', 'language'])
+
+
+    models = {
+        'auth.group': {
+            'Meta': {'object_name': 'Group'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        'auth.permission': {
+            'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        'auth.user': {
+            'Meta': {'object_name': 'User'},
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+        },
+        'catalogue.book': {
+            'Meta': {'ordering': "('sort_key',)", 'object_name': 'Book'},
+            'changed_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'common_slug': ('django.db.models.fields.SlugField', [], {'max_length': '120', 'db_index': 'True'}),
+            'cover': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+            'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'epub_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+            'extra_info': ('catalogue.fields.JSONField', [], {'default': "'{}'"}),
+            'gazeta_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}),
+            'html_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'language': ('django.db.models.fields.CharField', [], {'default': "'pol'", 'max_length': '3', 'db_index': 'True'}),
+            'mobi_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+            'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['catalogue.Book']"}),
+            'parent_number': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'pdf_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '120', 'db_index': 'True'}),
+            'sort_key': ('django.db.models.fields.CharField', [], {'max_length': '120', 'db_index': 'True'}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '120'}),
+            'txt_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+            'wiki_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}),
+            'xml_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'})
+        },
+        'catalogue.bookmedia': {
+            'Meta': {'ordering': "('type', 'name')", 'object_name': 'BookMedia'},
+            'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'media'", 'to': "orm['catalogue.Book']"}),
+            'extra_info': ('catalogue.fields.JSONField', [], {'default': "'{}'"}),
+            'file': ('catalogue.fields.OverwritingFileField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': "'100'"}),
+            'source_sha1': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}),
+            'type': ('django.db.models.fields.CharField', [], {'max_length': "'100'"}),
+            'uploaded_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'})
+        },
+        'catalogue.fragment': {
+            'Meta': {'ordering': "('book', 'anchor')", 'object_name': 'Fragment'},
+            'anchor': ('django.db.models.fields.CharField', [], {'max_length': '120'}),
+            'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'fragments'", 'to': "orm['catalogue.Book']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'short_text': ('django.db.models.fields.TextField', [], {}),
+            'text': ('django.db.models.fields.TextField', [], {})
+        },
+        'catalogue.tag': {
+            'Meta': {'ordering': "('sort_key',)", 'unique_together': "(('slug', 'category'),)", 'object_name': 'Tag'},
+            'book_count': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'category': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}),
+            'changed_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'gazeta_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '120', 'db_index': 'True'}),
+            'sort_key': ('django.db.models.fields.CharField', [], {'max_length': '120', 'db_index': 'True'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
+            'wiki_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'})
+        },
+        'catalogue.tagrelation': {
+            'Meta': {'unique_together': "(('tag', 'content_type', 'object_id'),)", 'object_name': 'TagRelation', 'db_table': "'catalogue_tag_relation'"},
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'object_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+            'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'items'", 'to': "orm['catalogue.Tag']"})
+        },
+        'contenttypes.contenttype': {
+            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        }
+    }
+
+    complete_apps = ['catalogue']
diff --git a/apps/catalogue/migrations/0023_common_slug.py b/apps/catalogue/migrations/0023_common_slug.py
new file mode 100644 (file)
index 0000000..3863149
--- /dev/null
@@ -0,0 +1,123 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import DataMigration
+from django.db import models
+
+class Migration(DataMigration):
+
+    def forwards(self, orm):
+        "Write your forwards methods here."
+        orm.Book.objects.all().update(common_slug=models.F('slug'))
+
+
+    def backwards(self, orm):
+        "Write your backwards methods here."
+        pass
+
+
+    models = {
+        'auth.group': {
+            'Meta': {'object_name': 'Group'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        'auth.permission': {
+            'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        'auth.user': {
+            'Meta': {'object_name': 'User'},
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+        },
+        'catalogue.book': {
+            'Meta': {'ordering': "('sort_key',)", 'object_name': 'Book'},
+            'changed_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'common_slug': ('django.db.models.fields.SlugField', [], {'max_length': '120', 'db_index': 'True'}),
+            'cover': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+            'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'epub_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+            'extra_info': ('catalogue.fields.JSONField', [], {'default': "'{}'"}),
+            'gazeta_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}),
+            'html_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'language': ('django.db.models.fields.CharField', [], {'default': "'pol'", 'max_length': '3', 'db_index': 'True'}),
+            'mobi_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+            'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['catalogue.Book']"}),
+            'parent_number': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'pdf_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '120', 'db_index': 'True'}),
+            'sort_key': ('django.db.models.fields.CharField', [], {'max_length': '120', 'db_index': 'True'}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '120'}),
+            'txt_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+            'wiki_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}),
+            'xml_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'})
+        },
+        'catalogue.bookmedia': {
+            'Meta': {'ordering': "('type', 'name')", 'object_name': 'BookMedia'},
+            'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'media'", 'to': "orm['catalogue.Book']"}),
+            'extra_info': ('catalogue.fields.JSONField', [], {'default': "'{}'"}),
+            'file': ('catalogue.fields.OverwritingFileField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': "'100'"}),
+            'source_sha1': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}),
+            'type': ('django.db.models.fields.CharField', [], {'max_length': "'100'"}),
+            'uploaded_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'})
+        },
+        'catalogue.fragment': {
+            'Meta': {'ordering': "('book', 'anchor')", 'object_name': 'Fragment'},
+            'anchor': ('django.db.models.fields.CharField', [], {'max_length': '120'}),
+            'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'fragments'", 'to': "orm['catalogue.Book']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'short_text': ('django.db.models.fields.TextField', [], {}),
+            'text': ('django.db.models.fields.TextField', [], {})
+        },
+        'catalogue.tag': {
+            'Meta': {'ordering': "('sort_key',)", 'unique_together': "(('slug', 'category'),)", 'object_name': 'Tag'},
+            'book_count': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'category': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}),
+            'changed_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'gazeta_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '120', 'db_index': 'True'}),
+            'sort_key': ('django.db.models.fields.CharField', [], {'max_length': '120', 'db_index': 'True'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
+            'wiki_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'})
+        },
+        'catalogue.tagrelation': {
+            'Meta': {'unique_together': "(('tag', 'content_type', 'object_id'),)", 'object_name': 'TagRelation', 'db_table': "'catalogue_tag_relation'"},
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'object_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+            'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'items'", 'to': "orm['catalogue.Tag']"})
+        },
+        'contenttypes.contenttype': {
+            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        }
+    }
+
+    complete_apps = ['catalogue']
diff --git a/apps/catalogue/migrations/0024_auto__add_collection.py b/apps/catalogue/migrations/0024_auto__add_collection.py
new file mode 100644 (file)
index 0000000..e2e2100
--- /dev/null
@@ -0,0 +1,138 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        
+        # Adding model 'Collection'
+        db.create_table('catalogue_collection', (
+            ('title', self.gf('django.db.models.fields.CharField')(max_length=120, db_index=True)),
+            ('slug', self.gf('django.db.models.fields.SlugField')(max_length=120, primary_key=True, db_index=True)),
+            ('description', self.gf('django.db.models.fields.TextField')(null=True, blank=True)),
+            ('book_slugs', self.gf('django.db.models.fields.TextField')()),
+        ))
+        db.send_create_signal('catalogue', ['Collection'])
+
+
+    def backwards(self, orm):
+        
+        # Deleting model 'Collection'
+        db.delete_table('catalogue_collection')
+
+
+    models = {
+        'auth.group': {
+            'Meta': {'object_name': 'Group'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        'auth.permission': {
+            'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        'auth.user': {
+            'Meta': {'object_name': 'User'},
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+        },
+        'catalogue.book': {
+            'Meta': {'ordering': "('sort_key',)", 'object_name': 'Book'},
+            'changed_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'common_slug': ('django.db.models.fields.SlugField', [], {'max_length': '120', 'db_index': 'True'}),
+            'cover': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+            'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'epub_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+            'extra_info': ('catalogue.fields.JSONField', [], {'default': "'{}'"}),
+            'gazeta_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}),
+            'html_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'language': ('django.db.models.fields.CharField', [], {'default': "'pol'", 'max_length': '3', 'db_index': 'True'}),
+            'mobi_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+            'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['catalogue.Book']"}),
+            'parent_number': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'pdf_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '120', 'db_index': 'True'}),
+            'sort_key': ('django.db.models.fields.CharField', [], {'max_length': '120', 'db_index': 'True'}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '120'}),
+            'txt_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+            'wiki_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}),
+            'xml_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'})
+        },
+        'catalogue.bookmedia': {
+            'Meta': {'ordering': "('type', 'name')", 'object_name': 'BookMedia'},
+            'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'media'", 'to': "orm['catalogue.Book']"}),
+            'extra_info': ('catalogue.fields.JSONField', [], {'default': "'{}'"}),
+            'file': ('catalogue.fields.OverwritingFileField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': "'100'"}),
+            'source_sha1': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}),
+            'type': ('django.db.models.fields.CharField', [], {'max_length': "'100'"}),
+            'uploaded_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'})
+        },
+        'catalogue.collection': {
+            'Meta': {'ordering': "('title',)", 'object_name': 'Collection'},
+            'book_slugs': ('django.db.models.fields.TextField', [], {}),
+            'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '120', 'primary_key': 'True', 'db_index': 'True'}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '120', 'db_index': 'True'})
+        },
+        'catalogue.fragment': {
+            'Meta': {'ordering': "('book', 'anchor')", 'object_name': 'Fragment'},
+            'anchor': ('django.db.models.fields.CharField', [], {'max_length': '120'}),
+            'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'fragments'", 'to': "orm['catalogue.Book']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'short_text': ('django.db.models.fields.TextField', [], {}),
+            'text': ('django.db.models.fields.TextField', [], {})
+        },
+        'catalogue.tag': {
+            'Meta': {'ordering': "('sort_key',)", 'unique_together': "(('slug', 'category'),)", 'object_name': 'Tag'},
+            'book_count': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'category': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}),
+            'changed_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'gazeta_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '120', 'db_index': 'True'}),
+            'sort_key': ('django.db.models.fields.CharField', [], {'max_length': '120', 'db_index': 'True'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
+            'wiki_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'})
+        },
+        'catalogue.tagrelation': {
+            'Meta': {'unique_together': "(('tag', 'content_type', 'object_id'),)", 'object_name': 'TagRelation', 'db_table': "'catalogue_tag_relation'"},
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'object_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+            'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'items'", 'to': "orm['catalogue.Tag']"})
+        },
+        'contenttypes.contenttype': {
+            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        }
+    }
+
+    complete_apps = ['catalogue']
index 1417519..9a1e71a 100644 (file)
@@ -2,12 +2,13 @@
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
-from datetime import datetime
+from collections import namedtuple
 
 from django.db import models
 from django.db.models import permalink, Q
 import django.dispatch
 from django.core.cache import cache
+from django.core.files.storage import DefaultStorage
 from django.utils.translation import ugettext_lazy as _
 from django.contrib.auth.models import User
 from django.template.loader import render_to_string
@@ -22,9 +23,17 @@ from django.conf import settings
 from newtagging.models import TagBase, tags_updated
 from newtagging import managers
 from catalogue.fields import JSONField, OverwritingFileField
-from catalogue.utils import create_zip
+from catalogue.utils import create_zip, split_tags
+from catalogue.tasks import touch_tag, index_book
+from shutil import copy
+from glob import glob
+import re
+from os import path
 
 
+import search
+
+# Those are hard-coded here so that makemessages sees them.
 TAG_CATEGORIES = (
     ('author', _('author')),
     ('epoch', _('epoch')),
@@ -35,13 +44,6 @@ TAG_CATEGORIES = (
     ('book', _('book')),
 )
 
-MEDIA_FORMATS = (
-    ('odt', _('ODT file')),
-    ('mp3', _('MP3 file')),
-    ('ogg', _('OGG file')),
-    ('daisy', _('DAISY file')), 
-)
-
 # not quite, but Django wants you to set a timeout
 CACHE_FOREVER = 2419200  # 28 days
 
@@ -62,7 +64,6 @@ class Tag(TagBase):
     category = models.CharField(_('category'), max_length=50, blank=False, null=False,
         db_index=True, choices=TAG_CATEGORIES)
     description = models.TextField(_('description'), blank=True)
-    main_page = models.BooleanField(_('main page'), default=False, db_index=True, help_text=_('Show tag on main page'))
 
     user = models.ForeignKey(User, blank=True, null=True)
     book_count = models.IntegerField(_('book count'), blank=True, null=True)
@@ -107,25 +108,22 @@ class Tag(TagBase):
     has_description.boolean = True
 
     def get_count(self):
-        """ returns global book count for book tags, fragment count for themes """
-
-        if self.book_count is None:
-            if self.category == 'book':
-                # never used
-                objects = Book.objects.none()
-            elif self.category == 'theme':
-                objects = Fragment.tagged.with_all((self,))
-            else:
-                objects = Book.tagged.with_all((self,)).order_by()
-                if self.category != 'set':
-                    # eliminate descendants
-                    l_tags = Tag.objects.filter(slug__in=[book.book_tag_slug() for book in objects])
-                    descendants_keys = [book.pk for book in Book.tagged.with_any(l_tags)]
-                    if descendants_keys:
-                        objects = objects.exclude(pk__in=descendants_keys)
-            self.book_count = objects.count()
-            self.save()
-        return self.book_count
+        """Returns global book count for book tags, fragment count for themes."""
+
+        if self.category == 'book':
+            # never used
+            objects = Book.objects.none()
+        elif self.category == 'theme':
+            objects = Fragment.tagged.with_all((self,))
+        else:
+            objects = Book.tagged.with_all((self,)).order_by()
+            if self.category != 'set':
+                # eliminate descendants
+                l_tags = Tag.objects.filter(slug__in=[book.book_tag_slug() for book in objects])
+                descendants_keys = [book.pk for book in Book.tagged.with_any(l_tags)]
+                if descendants_keys:
+                    objects = objects.exclude(pk__in=descendants_keys)
+        return objects.count()
 
     @staticmethod
     def get_tag_list(tags):
@@ -169,28 +167,89 @@ class Tag(TagBase):
     def url_chunk(self):
         return '/'.join((Tag.categories_dict[self.category], self.slug))
 
+    @staticmethod
+    def tags_from_info(info):
+        from slughifi import slughifi
+        from sortify import sortify
+        meta_tags = []
+        categories = (('kinds', 'kind'), ('genres', 'genre'), ('authors', 'author'), ('epochs', 'epoch'))
+        for field_name, category in categories:
+            try:
+                tag_names = getattr(info, field_name)
+            except:
+                try:
+                    tag_names = [getattr(info, category)]
+                except:
+                    # For instance, Pictures do not have 'genre' field.
+                    continue
+            for tag_name in tag_names:
+                tag_sort_key = tag_name
+                if category == 'author':
+                    tag_sort_key = tag_name.last_name
+                    tag_name = tag_name.readable()
+                tag, created = Tag.objects.get_or_create(slug=slughifi(tag_name), category=category)
+                if created:
+                    tag.name = tag_name
+                    tag.sort_key = sortify(tag_sort_key.lower())
+                    tag.save()
+                meta_tags.append(tag)
+        return meta_tags
+
+
+
+def get_dynamic_path(media, filename, ext=None, maxlen=100):
+    from slughifi import slughifi
+
+    # how to put related book's slug here?
+    if not ext:
+        # BookMedia case
+        ext = media.formats[media.type].ext
+    if media is None or not media.name:
+        name = slughifi(filename.split(".")[0])
+    else:
+        name = slughifi(media.name)
+    return 'book/%s/%s.%s' % (ext, name[:maxlen-len('book/%s/.%s' % (ext, ext))-4], ext)
+
 
 # TODO: why is this hard-coded ?
 def book_upload_path(ext=None, maxlen=100):
-    def get_dynamic_path(media, filename, ext=ext):
-        from slughifi import slughifi
+    return lambda *args: get_dynamic_path(*args, ext=ext, maxlen=maxlen)
 
-        # how to put related book's slug here?
-        if not ext:
-            if media.type == 'daisy':
-                ext = 'daisy.zip'
-            else:
-                ext = media.type
-        if not media.name:
-            name = slughifi(filename.split(".")[0])
-        else:
-            name = slughifi(media.name)
-        return 'book/%s/%s.%s' % (ext, name[:maxlen-len('book/%s/.%s' % (ext, ext))-4], ext)
-    return get_dynamic_path
+
+def get_customized_pdf_path(book, customizations):
+    """
+    Returns a MEDIA_ROOT relative path for a customized pdf. The name will contain a hash of customization options.
+    """
+    customizations.sort()
+    h = hash(tuple(customizations))
+
+    pdf_name = '%s-custom-%s' % (book.slug, h)
+    pdf_file = get_dynamic_path(None, pdf_name, ext='pdf')
+
+    return pdf_file
+
+
+def get_existing_customized_pdf(book):
+    """
+    Returns a list of paths to generated customized pdf of a book
+    """
+    pdf_glob = '%s-custom-' % (book.slug,)
+    pdf_glob = get_dynamic_path(None, pdf_glob, ext='pdf')
+    pdf_glob = re.sub(r"[.]([a-z0-9]+)$", "*.\\1", pdf_glob)
+    return glob(path.join(settings.MEDIA_ROOT, pdf_glob))
 
 
 class BookMedia(models.Model):
-    type        = models.CharField(_('type'), choices=MEDIA_FORMATS, max_length="100")
+    FileFormat = namedtuple("FileFormat", "name ext")
+    formats = SortedDict([
+        ('mp3', FileFormat(name='MP3', ext='mp3')),
+        ('ogg', FileFormat(name='Ogg Vorbis', ext='ogg')),
+        ('daisy', FileFormat(name='DAISY', ext='daisy.zip')),
+    ])
+    format_choices = [(k, _('%s file') % t.name)
+            for k, t in formats.items()]
+
+    type        = models.CharField(_('type'), choices=format_choices, max_length="100")
     name        = models.CharField(_('name'), max_length="100")
     file        = OverwritingFileField(_('file'), upload_to=book_upload_path())
     uploaded_at = models.DateTimeField(_('creation date'), auto_now_add=True, editable=False)
@@ -213,7 +272,7 @@ class BookMedia(models.Model):
         try:
             old = BookMedia.objects.get(pk=self.pk)
         except BookMedia.DoesNotExist, e:
-            pass
+            old = None
         else:
             # if name changed, change the file name, too
             if slughifi(self.name) != slughifi(old.name):
@@ -222,7 +281,9 @@ class BookMedia(models.Model):
         super(BookMedia, self).save(*args, **kwargs)
 
         # remove the zip package for book with modified media
-        remove_zip(self.book.slug)
+        if old:
+            remove_zip("%s_%s" % (old.book.slug, old.type))
+        remove_zip("%s_%s" % (self.book.slug, self.type))
 
         extra_info = self.get_extra_info_value()
         extra_info.update(self.read_meta())
@@ -291,7 +352,11 @@ class BookMedia(models.Model):
 class Book(models.Model):
     title         = models.CharField(_('title'), max_length=120)
     sort_key = models.CharField(_('sort key'), max_length=120, db_index=True, editable=False)
-    slug          = models.SlugField(_('slug'), max_length=120, unique=True, db_index=True)
+    slug = models.SlugField(_('slug'), max_length=120, db_index=True,
+            unique=True)
+    common_slug = models.SlugField(_('slug'), max_length=120, db_index=True)
+    language = models.CharField(_('language code'), max_length=3, db_index=True,
+                    default=settings.CATALOGUE_DEFAULT_LANGUAGE)
     description   = models.TextField(_('description'), blank=True)
     created_at    = models.DateTimeField(_('creation date'), auto_now_add=True, db_index=True)
     changed_at    = models.DateTimeField(_('creation date'), auto_now=True, db_index=True)
@@ -301,8 +366,11 @@ class Book(models.Model):
     wiki_link     = models.CharField(blank=True, max_length=240)
     # files generated during publication
 
-    file_types = ['epub', 'html', 'mobi', 'pdf', 'txt', 'xml']
-    
+    cover = models.FileField(_('cover'), upload_to=book_upload_path('png'),
+                null=True, blank=True)
+    ebook_formats = ['pdf', 'epub', 'mobi', 'txt']
+    formats = ebook_formats + ['html', 'xml']
+
     parent        = models.ForeignKey('self', blank=True, null=True, related_name='children')
     objects  = models.Manager()
     tagged   = managers.ModelTaggedItemManager(Tag)
@@ -355,14 +423,14 @@ class Book(models.Model):
         return book_tag
 
     def has_media(self, type):
-        if type in Book.file_types:
+        if type in Book.formats:
             return bool(getattr(self, "%s_file" % type))
         else:
             return self.media.filter(type=type).exists()
 
     def get_media(self, type):
         if self.has_media(type):
-            if type in Book.file_types:
+            if type in Book.formats:
                 return getattr(self, "%s_file" % type)
             else:                                             
                 return self.media.filter(type=type)
@@ -385,6 +453,7 @@ class Book(models.Model):
         cache_key = "Book.short_html/%d/%s"
         for lang, langname in settings.LANGUAGES:
             cache.delete(cache_key % (self.id, lang))
+        cache.delete("Book.mini_box/%d" % (self.id, ))
         # Fragment.short_html relies on book's tags, so reset it here too
         for fragm in self.fragments.all():
             fragm.reset_short_html()
@@ -399,26 +468,15 @@ class Book(models.Model):
         if short_html is not None:
             return mark_safe(short_html)
         else:
-            tags = self.tags.filter(~Q(category__in=('set', 'theme', 'book')))
-            tags = [mark_safe(u'<a href="%s">%s</a>' % (tag.get_absolute_url(), tag.name)) for tag in tags]
+            tags = self.tags.filter(category__in=('author', 'kind', 'genre', 'epoch'))
+            tags = split_tags(tags)
 
-            formats = []
+            formats = {}
             # files generated during publication
-            if self.has_media("html"):
-                formats.append(u'<a href="%s">%s</a>' % (reverse('book_text', kwargs={'slug': self.slug}), _('Read online')))
-            if self.has_media("pdf"):
-                formats.append(u'<a href="%s">PDF</a>' % self.get_media('pdf').url)
-            if self.has_media("mobi"):
-                formats.append(u'<a href="%s">MOBI</a>' % self.get_media('mobi').url)
-            if self.root_ancestor.has_media("epub"):
-                formats.append(u'<a href="%s">EPUB</a>' % self.root_ancestor.get_media('epub').url)
-            if self.has_media("txt"):
-                formats.append(u'<a href="%s">TXT</a>' % self.get_media('txt').url)
-            # other files
-            for m in self.media.order_by('type'):
-                formats.append(u'<a href="%s">%s</a>' % (m.file.url, m.type.upper()))
-
-            formats = [mark_safe(format) for format in formats]
+            for ebook_format in self.ebook_formats:
+                if self.has_media(ebook_format):
+                    formats[ebook_format] = self.get_media(ebook_format)
+
 
             short_html = unicode(render_to_string('catalogue/book_short.html',
                 {'book': self, 'tags': tags, 'formats': formats}))
@@ -427,17 +485,22 @@ class Book(models.Model):
                 cache.set(cache_key, short_html, CACHE_FOREVER)
             return mark_safe(short_html)
 
-    @property
-    def root_ancestor(self):
-        """ returns the oldest ancestor """
+    def mini_box(self):
+        if self.id:
+            cache_key = "Book.mini_box/%d" % (self.id, )
+            short_html = cache.get(cache_key)
+        else:
+            short_html = None
 
-        if not hasattr(self, '_root_ancestor'):
-            book = self
-            while book.parent:
-                book = book.parent
-            self._root_ancestor = book
-        return self._root_ancestor
+        if short_html is None:
+            authors = self.tags.filter(category='author')
 
+            short_html = unicode(render_to_string('catalogue/book_mini_box.html',
+                {'book': self, 'authors': authors, 'STATIC_URL': settings.STATIC_URL}))
+
+            if self.id:
+                cache.set(cache_key, short_html, CACHE_FOREVER)
+        return mark_safe(short_html)
 
     def has_description(self):
         return len(self.description) > 0
@@ -445,11 +508,6 @@ class Book(models.Model):
     has_description.boolean = True
 
     # ugly ugly ugly
-    def has_odt_file(self):
-        return bool(self.has_media("odt"))
-    has_odt_file.short_description = 'ODT'
-    has_odt_file.boolean = True
-
     def has_mp3_file(self):
         return bool(self.has_media("mp3"))
     has_mp3_file.short_description = 'MP3'
@@ -465,103 +523,94 @@ class Book(models.Model):
     has_daisy_file.short_description = 'DAISY'
     has_daisy_file.boolean = True
 
-    def build_pdf(self):
-        """ (Re)builds the pdf file.
+    def wldocument(self, parse_dublincore=True):
+        from catalogue.import_utils import ORMDocProvider
+        from librarian.parser import WLDocument
+
+        return WLDocument.from_file(self.xml_file.path,
+                provider=ORMDocProvider(self),
+                parse_dublincore=parse_dublincore)
+
+    def build_cover(self, book_info=None):
+        """(Re)builds the cover image."""
+        from StringIO import StringIO
+        from django.core.files.base import ContentFile
+        from librarian.cover import WLCover
+
+        if book_info is None:
+            book_info = self.wldocument().book_info
 
+        cover = WLCover(book_info).image()
+        imgstr = StringIO()
+        cover.save(imgstr, 'png')
+        self.cover.save(None, ContentFile(imgstr.getvalue()))
+
+    def build_pdf(self, customizations=None, file_name=None):
+        """ (Re)builds the pdf file.
+        customizations - customizations which are passed to LaTeX class file.
+        file_name - save the pdf file under a different name and DO NOT save it in db.
         """
-        from tempfile import NamedTemporaryFile
         from os import unlink
         from django.core.files import File
-        from librarian import pdf
-        from catalogue.utils import ORMDocProvider, remove_zip
+        from catalogue.utils import remove_zip
 
-        try:
-            pdf_file = NamedTemporaryFile(delete=False)
-            pdf.transform(ORMDocProvider(self),
-                      file_path=str(self.xml_file.path),
-                      output_file=pdf_file,
-                      )
+        pdf = self.wldocument().as_pdf(customizations=customizations)
 
-            self.pdf_file.save('%s.pdf' % self.slug, File(open(pdf_file.name)))
-        finally:
-            unlink(pdf_file.name)
+        if file_name is None:
+            # we'd like to be sure not to overwrite changes happening while
+            # (timely) pdf generation is taking place (async celery scenario)
+            current_self = Book.objects.get(id=self.id)
+            current_self.pdf_file.save('%s.pdf' % self.slug,
+                    File(open(pdf.get_filename())))
+            self.pdf_file = current_self.pdf_file
 
-        # remove zip with all pdf files
-        remove_zip(settings.ALL_PDF_ZIP)
+            # remove cached downloadables
+            remove_zip(settings.ALL_PDF_ZIP)
+
+            for customized_pdf in get_existing_customized_pdf(self):
+                unlink(customized_pdf)
+        else:
+            print "saving %s" % file_name
+            print "to: %s" % DefaultStorage().path(file_name)
+            DefaultStorage().save(file_name, File(open(pdf.get_filename())))
 
     def build_mobi(self):
         """ (Re)builds the MOBI file.
 
         """
-        from tempfile import NamedTemporaryFile
-        from os import unlink
         from django.core.files import File
-        from librarian import mobi
-        from catalogue.utils import ORMDocProvider, remove_zip
+        from catalogue.utils import remove_zip
 
-        try:
-            mobi_file = NamedTemporaryFile(suffix='.mobi', delete=False)
-            mobi.transform(ORMDocProvider(self), verbose=1,
-                      file_path=str(self.xml_file.path),
-                      output_file=mobi_file.name,
-                      )
+        mobi = self.wldocument().as_mobi()
 
-            self.mobi_file.save('%s.mobi' % self.slug, File(open(mobi_file.name)))
-        finally:
-            unlink(mobi_file.name)
+        self.mobi_file.save('%s.mobi' % self.slug, File(open(mobi.get_filename())))
 
         # remove zip with all mobi files
         remove_zip(settings.ALL_MOBI_ZIP)
 
-    def build_epub(self, remove_descendants=True):
-        """ (Re)builds the epub file.
-            If book has a parent, does nothing.
-            Unless remove_descendants is False, descendants' epubs are removed.
-        """
-        from StringIO import StringIO
-        from hashlib import sha1
-        from django.core.files.base import ContentFile
-        from librarian import epub, NoDublinCore
-        from catalogue.utils import ORMDocProvider, remove_zip
-
-        if self.parent:
-            # don't need an epub
-            return
+    def build_epub(self):
+        """(Re)builds the epub file."""
+        from django.core.files import File
+        from catalogue.utils import remove_zip
 
-        epub_file = StringIO()
-        try:
-            epub.transform(ORMDocProvider(self), self.slug, output_file=epub_file)
-            self.epub_file.save('%s.epub' % self.slug, ContentFile(epub_file.getvalue()))
-            FileRecord(slug=self.slug, type='epub', sha1=sha1(epub_file.getvalue()).hexdigest()).save()
-        except NoDublinCore:
-            pass
+        epub = self.wldocument().as_epub()
 
-        book_descendants = list(self.children.all())
-        while len(book_descendants) > 0:
-            child_book = book_descendants.pop(0)
-            if remove_descendants and child_book.has_epub_file():
-                child_book.epub_file.delete()
-            # save anyway, to refresh short_html
-            child_book.save()
-            book_descendants += list(child_book.children.all())
+        self.epub_file.save('%s.epub' % self.slug,
+                File(open(epub.get_filename())))
 
         # remove zip package with all epub files
         remove_zip(settings.ALL_EPUB_ZIP)
 
     def build_txt(self):
-        from StringIO import StringIO
         from django.core.files.base import ContentFile
-        from librarian import text
 
-        out = StringIO()
-        text.transform(open(self.xml_file.path), out)
-        self.txt_file.save('%s.txt' % self.slug, ContentFile(out.getvalue()))
+        text = self.wldocument().as_text()
+        self.txt_file.save('%s.txt' % self.slug, ContentFile(text.get_string()))
 
 
     def build_html(self):
-        from tempfile import NamedTemporaryFile
         from markupstring import MarkupString
-        from django.core.files import File
+        from django.core.files.base import ContentFile
         from slughifi import slughifi
         from librarian import html
 
@@ -569,9 +618,10 @@ class Book(models.Model):
             category__in=('author', 'epoch', 'genre', 'kind')))
         book_tag = self.book_tag()
 
-        html_file = NamedTemporaryFile()
-        if html.transform(self.xml_file.path, html_file, parse_dublincore=False):
-            self.html_file.save('%s.html' % self.slug, File(html_file))
+        html_output = self.wldocument(parse_dublincore=False).as_html()
+        if html_output:
+            self.html_file.save('%s.html' % self.slug,
+                    ContentFile(html_output.get_string()))
 
             # get ancestor l-tags for adding to new fragments
             ancestor_tags = []
@@ -632,12 +682,25 @@ class Book(models.Model):
                     getattr(settings, "ALL_%s_ZIP" % format_.upper()))
         return result.wait()
 
-    def zip_audiobooks(self):
-        bm = BookMedia.objects.filter(book=self, type='mp3')
+    def zip_audiobooks(self, format_):
+        bm = BookMedia.objects.filter(book=self, type=format_)
         paths = map(lambda bm: (None, bm.file.path), bm)
-        result = create_zip.delay(paths, self.slug)
+        result = create_zip.delay(paths, "%s_%s" % (self.slug, format_))
         return result.wait()
 
+    def search_index(self, book_info=None, reuse_index=False):
+        if reuse_index:
+            idx = search.ReusableIndex()
+        else:
+            idx = search.Index()
+            
+        idx.open()
+        try:
+            idx.index_book(self, book_info)
+            idx.index_tags()
+        finally:
+            idx.close()
+
     @classmethod
     def from_xml_file(cls, xml_file, **kwargs):
         from django.core.files import File
@@ -656,25 +719,25 @@ class Book(models.Model):
 
     @classmethod
     def from_text_and_meta(cls, raw_file, book_info, overwrite=False,
-            build_epub=True, build_txt=True, build_pdf=True, build_mobi=True):
+            build_epub=True, build_txt=True, build_pdf=True, build_mobi=True,
+            search_index=True):
         import re
-        from slughifi import slughifi
         from sortify import sortify
 
         # check for parts before we do anything
         children = []
         if hasattr(book_info, 'parts'):
             for part_url in book_info.parts:
-                base, slug = part_url.rsplit('/', 1)
                 try:
-                    children.append(Book.objects.get(slug=slug))
+                    children.append(Book.objects.get(slug=part_url.slug))
                 except Book.DoesNotExist, e:
-                    raise Book.DoesNotExist(_('Book with slug = "%s" does not exist.') % slug)
+                    raise Book.DoesNotExist(_('Book "%s" does not exist.') %
+                            part_url.slug)
 
 
         # Read book metadata
-        book_base, book_slug = book_info.url.rsplit('/', 1)
-        if re.search(r'[^a-zA-Z0-9-]', book_slug):
+        book_slug = book_info.url.slug
+        if re.search(r'[^a-z0-9-]', book_slug):
             raise ValueError('Invalid characters in slug')
         book, created = Book.objects.get_or_create(slug=book_slug)
 
@@ -682,32 +745,21 @@ class Book(models.Model):
             book_shelves = []
         else:
             if not overwrite:
-                raise Book.AlreadyExists(_('Book %s already exists') % book_slug)
+                raise Book.AlreadyExists(_('Book %s already exists') % (
+                        book_slug))
             # Save shelves for this book
             book_shelves = list(book.tags.filter(category='set'))
 
+        book.language = book_info.language
         book.title = book_info.title
+        if book_info.variant_of:
+            book.common_slug = book_info.variant_of.slug
+        else:
+            book.common_slug = book.slug
         book.set_extra_info_value(book_info.to_dict())
         book.save()
 
-        meta_tags = []
-        categories = (('kinds', 'kind'), ('genres', 'genre'), ('authors', 'author'), ('epochs', 'epoch'))
-        for field_name, category in categories:
-            try:
-                tag_names = getattr(book_info, field_name)
-            except:
-                tag_names = [getattr(book_info, category)]
-            for tag_name in tag_names:
-                tag_sort_key = tag_name
-                if category == 'author':
-                    tag_sort_key = tag_name.last_name
-                    tag_name = ' '.join(tag_name.first_names) + ' ' + tag_name.last_name
-                tag, created = Tag.objects.get_or_create(slug=slughifi(tag_name), category=category)
-                if created:
-                    tag.name = tag_name
-                    tag.sort_key = sortify(tag_sort_key.lower())
-                    tag.save()
-                meta_tags.append(tag)
+        meta_tags = Tag.tags_from_info(book_info)
 
         book.tags = set(meta_tags + book_shelves)
 
@@ -728,26 +780,35 @@ class Book(models.Model):
             if not settings.NO_BUILD_TXT and build_txt:
                 book.build_txt()
 
+        book.build_cover(book_info)
+
         if not settings.NO_BUILD_EPUB and build_epub:
-            book.root_ancestor.build_epub()
+            book.build_epub()
 
         if not settings.NO_BUILD_PDF and build_pdf:
-            book.root_ancestor.build_pdf()
+            book.build_pdf()
 
         if not settings.NO_BUILD_MOBI and build_mobi:
             book.build_mobi()
 
+        if not settings.NO_SEARCH_INDEX and search_index:
+            index_book.delay(book.id, book_info)
+
         book_descendants = list(book.children.all())
+        descendants_tags = set()
         # add l-tag to descendants and their fragments
-        # delete unnecessary EPUB files
         while len(book_descendants) > 0:
             child_book = book_descendants.pop(0)
+            descendants_tags.update(child_book.tags)
             child_book.tags = list(child_book.tags) + [book_tag]
             child_book.save()
             for fragment in child_book.fragments.all():
                 fragment.tags = set(list(fragment.tags) + [book_tag])
             book_descendants += list(child_book.children.all())
 
+        for tag in descendants_tags:
+            touch_tag.delay(tag)
+
         book.save()
 
         # refresh cache
@@ -857,7 +918,8 @@ class Book(models.Model):
         """
 
         books_by_parent = {}
-        books = cls.objects.all().order_by('parent_number', 'sort_key').only('title', 'parent', 'slug')
+        books = cls.objects.all().order_by('parent_number', 'sort_key').only(
+                'title', 'parent', 'slug')
         if filter:
             books = books.filter(filter).distinct()
             book_ids = set((book.pk for book in books))
@@ -885,6 +947,19 @@ class Book(models.Model):
 
         return books_by_author, orphans, books_by_parent
 
+    _audiences_pl = {
+        "SP1": (1, u"szkoła podstawowa"),
+        "SP2": (1, u"szkoła podstawowa"),
+        "P": (1, u"szkoła podstawowa"),
+        "G": (2, u"gimnazjum"),
+        "L": (3, u"liceum"),
+        "LP": (3, u"liceum"),
+    }
+    def audiences_pl(self):
+        audiences = self.get_extra_info_value().get('audiences', [])
+        audiences = sorted(set([self._audiences_pl[a] for a in audiences]))
+        return [a[1] for a in audiences]
+
 
 def _has_factory(ftype):
     has = lambda self: bool(getattr(self, "%s_file" % ftype))
@@ -895,7 +970,7 @@ def _has_factory(ftype):
 
     
 # add the file fields
-for t in Book.file_types:
+for t in Book.formats:
     field_name = "%s_file" % t
     models.FileField(_("%s file" % t.upper()),
             upload_to=book_upload_path(t),
@@ -920,7 +995,7 @@ class Fragment(models.Model):
         verbose_name_plural = _('fragments')
 
     def get_absolute_url(self):
-        return '%s#m%s' % (reverse('book_text', kwargs={'slug': self.book.slug}), self.anchor)
+        return '%s#m%s' % (reverse('book_text', args=[self.book.slug]), self.anchor)
 
     def reset_short_html(self):
         if self.id is None:
@@ -947,21 +1022,6 @@ class Fragment(models.Model):
             return mark_safe(short_html)
 
 
-class FileRecord(models.Model):
-    slug = models.SlugField(_('slug'), max_length=120, db_index=True)
-    type = models.CharField(_('type'), max_length=20, db_index=True)
-    sha1 = models.CharField(_('sha-1 hash'), max_length=40)
-    time = models.DateTimeField(_('time'), auto_now_add=True)
-
-    class Meta:
-        ordering = ('-time','-slug', '-type')
-        verbose_name = _('file record')
-        verbose_name_plural = _('file records')
-
-    def __unicode__(self):
-        return "%s %s.%s" % (self.sha1,  self.slug, self.type)
-
-
 class Collection(models.Model):
     """A collection of books, which might be defined before publishing them."""
     title = models.CharField(_('title'), max_length=120, db_index=True)
@@ -990,7 +1050,8 @@ class Collection(models.Model):
 def _tags_updated_handler(sender, affected_tags, **kwargs):
     # reset tag global counter
     # we want Tag.changed_at updated for API to know the tag was touched
-    Tag.objects.filter(pk__in=[tag.pk for tag in affected_tags]).update(book_count=None, changed_at=datetime.now())
+    for tag in affected_tags:
+        touch_tag.delay(tag)
 
     # if book tags changed, reset book tag counter
     if isinstance(sender, Book) and \
diff --git a/apps/catalogue/tasks.py b/apps/catalogue/tasks.py
new file mode 100755 (executable)
index 0000000..86e84a5
--- /dev/null
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
+from datetime import datetime
+from celery.task import task
+import catalogue.models
+
+@task
+def touch_tag(tag):
+    update_dict = {
+        'book_count': tag.get_count(),
+        'changed_at': datetime.now(),
+    }
+
+    type(tag).objects.filter(pk=tag.pk).update(**update_dict)
+
+
+@task
+def index_book(book_id, book_info=None):
+    return catalogue.models.Book.objects.get(id=book_id).search_index(book_info)
index e6544b0..df938a6 100644 (file)
@@ -15,6 +15,7 @@ from django.conf import settings
 from django.utils.translation import ugettext as _
 
 from catalogue.forms import SearchForm
+from catalogue.utils import split_tags
 
 
 register = template.Library()
@@ -144,12 +145,14 @@ def book_tree(book_list, books_by_parent):
 def book_tree_texml(book_list, books_by_parent, depth=1):
     return "".join("""
             <cmd name='hspace'><parm>%(depth)dem</parm></cmd>%(title)s
+            <spec cat='align' /><cmd name="note"><parm>%(audiences)s</parm></cmd>
             <spec cat='align' /><cmd name="note"><parm>%(audiobook)s</parm></cmd>
             <ctrl ch='\\' />
             %(children)s
             """ % {
                 "depth": depth,
                 "title": book.title, 
+                "audiences": ", ".join(book.audiences_pl()),
                 "audiobook": "audiobook" if book.has_media('mp3') else "",
                 "children": book_tree_texml(books_by_parent.get(book.id, ()), books_by_parent, depth + 1)
             } for book in book_list)
@@ -182,22 +185,6 @@ def authentication_form():
     return LoginForm(prefix='login').as_ul()
 
 
-@register.inclusion_tag('catalogue/search_form.html')
-def search_form():
-    return {"form": SearchForm()}
-
-@register.inclusion_tag('catalogue/breadcrumbs.html')
-def breadcrumbs(tags, search_form=True):
-    context = {'tag_list': tags}
-    try:
-        max_tag_list = settings.MAX_TAG_LIST
-    except AttributeError:
-        max_tag_list = -1
-    if search_form and (max_tag_list == -1 or len(tags) < max_tag_list):
-        context['search_form'] = SearchForm(tags=tags)
-    return context
-
-
 @register.tag
 def catalogue_url(parser, token):
     bits = token.split_contents()
@@ -277,24 +264,35 @@ def tag_list(tags, choices=None):
         one_tag = tags[0]
     return locals()
 
-
-@register.inclusion_tag('catalogue/folded_tag_list.html')
-def folded_tag_list(tags, title='', choices=None):
-    tags = [tag for tag in tags if tag.count]
+@register.inclusion_tag('catalogue/inline_tag_list.html')
+def inline_tag_list(tags, choices=None):
     if choices is None:
         choices = []
-    some_tags_hidden = False
-    tag_count = len(tags)
-
-    if tag_count == 1:
+    if len(tags) == 1:
         one_tag = tags[0]
-    else:
-        shown_tags = [tag for tag in tags if tag.main_page]
-        if tag_count > len(shown_tags):
-            some_tags_hidden = True
     return locals()
 
 
 @register.inclusion_tag('catalogue/book_info.html')
 def book_info(book):
     return locals()
+
+
+@register.inclusion_tag('catalogue/book_wide.html')
+def book_wide(book):
+    tags = book.tags.filter(category__in=('author', 'kind', 'genre', 'epoch'))
+    tags = split_tags(tags)
+
+    formats = {}
+    # files generated during publication
+    for ebook_format in book.ebook_formats:
+        if book.has_media(ebook_format):
+            formats[ebook_format] = book.get_media(ebook_format)
+
+    extra_info = book.get_extra_info_value()
+
+    has_media = {}
+    for media_format in ['mp3', 'ogg']:
+        has_media[media_format] = book.has_media(media_format)
+
+    return locals()
index a5f0b4f..eeda03f 100644 (file)
@@ -3,6 +3,7 @@ from django.test import TestCase
 import shutil
 import tempfile
 from slughifi import slughifi
+from librarian import WLURI
 
 class WLTestCase(TestCase):
     """
@@ -12,10 +13,12 @@ class WLTestCase(TestCase):
         self._MEDIA_ROOT, settings.MEDIA_ROOT = settings.MEDIA_ROOT, tempfile.mkdtemp(prefix='djangotest_')
         settings.NO_BUILD_PDF = settings.NO_BUILD_MOBI = settings.NO_BUILD_EPUB = settings.NO_BUILD_TXT = True
         settings.CELERY_ALWAYS_EAGER = True
+        self._CACHE_BACKEND, settings.CACHE_BACKEND = settings.CACHE_BACKEND, 'dummy://'
 
     def tearDown(self):
         shutil.rmtree(settings.MEDIA_ROOT, True)
         settings.MEDIA_ROOT = self._MEDIA_ROOT
+        settings.CACHE_BACKEND = self._CACHE_BACKEND
 
 class PersonStub(object):
 
@@ -23,8 +26,16 @@ class PersonStub(object):
         self.first_names = first_names
         self.last_name = last_name
 
+    def readable(self):
+        return " ".join(self.first_names + (self.last_name,))
+
 
 class BookInfoStub(object):
+    _empty_fields = ['cover_url', 'variant_of']
+    # allow single definition for multiple-value fields
+    _salias = {
+        'authors': 'author',
+    }
 
     def __init__(self, **kwargs):
         self.__dict = kwargs
@@ -35,18 +46,28 @@ class BookInfoStub(object):
         return object.__setattr__(self, key, value)
 
     def __getattr__(self, key):
-        return self.__dict[key]
+        try:
+            return self.__dict[key]
+        except KeyError:
+            if key in self._empty_fields:
+                return None
+            elif key in self._salias:
+                return [getattr(self, self._salias[key])]
+            else:
+                raise
 
     def to_dict(self):
         return dict((key, unicode(value)) for key, value in self.__dict.items())
 
 
-def info_args(title):
+def info_args(title, language=None):
     """ generate some keywords for comfortable BookInfoCreation  """
     slug = unicode(slughifi(title))
+    if language is None:
+        language = u'pol'
     return {
         'title': unicode(title),
-        'slug': slug,
-        'url': u"http://wolnelektury.pl/example/%s" % slug,
+        'url': WLURI.from_slug(slug),
         'about': u"http://wolnelektury.pl/example/URI/%s" % slug,
+        'language': language,
     }
index f65d880..3af1bb4 100644 (file)
@@ -4,23 +4,25 @@ from __future__ import with_statement
 from django.core.files.base import ContentFile, File
 from catalogue.test_utils import *
 from catalogue import models
+from librarian import WLURI
 
 from nose.tools import raises
 import tempfile
-from os import unlink,path
+from os import unlink, path, makedirs
 
 class BookImportLogicTests(WLTestCase):
 
     def setUp(self):
         WLTestCase.setUp(self)
         self.book_info = BookInfoStub(
-            url=u"http://wolnelektury.pl/example/default-book",
+            url=WLURI.from_slug(u"default-book"),
             about=u"http://wolnelektury.pl/example/URI/default_book",
             title=u"Default Book",
             author=PersonStub(("Jim",), "Lazy"),
             kind="X-Kind",
             genre="X-Genre",
             epoch="X-Epoch",
+            language=u"pol",
         )
 
         self.expected_tags = [
@@ -112,7 +114,7 @@ class BookImportLogicTests(WLTestCase):
     @raises(ValueError)
     def test_book_with_invalid_slug(self):
         """ Book with invalid characters in slug shouldn't be imported """
-        self.book_info.url = "http://wolnelektury.pl/example/default_book"
+        self.book_info.url = WLURI.from_slug(u"default_book")
         BOOK_TEXT = "<utwor />"
         book = models.Book.from_text_and_meta(ContentFile(BOOK_TEXT), self.book_info)
 
@@ -242,6 +244,42 @@ class ChildImportTests(WLTestCase):
                         'wrong related theme list')
 
 
+class MultilingualBookImportTest(WLTestCase):
+    def setUp(self):
+        WLTestCase.setUp(self)
+        common_uri = WLURI.from_slug('common-slug')
+
+        self.pol_info = BookInfoStub(
+            genre='X-Genre',
+            epoch='X-Epoch',
+            kind='X-Kind',
+            author=PersonStub(("Joe",), "Doe"),
+            variant_of=common_uri,
+            **info_args(u"Książka")
+        )
+
+        self.eng_info = BookInfoStub(
+            genre='X-Genre',
+            epoch='X-Epoch',
+            kind='X-Kind',
+            author=PersonStub(("Joe",), "Doe"),
+            variant_of=common_uri,
+            **info_args("A book", "eng")
+        )
+
+    def test_multilingual_import(self):
+        BOOK_TEXT = """<utwor><opowiadanie><akap>A</akap></opowiadanie></utwor>"""
+
+        book1 = models.Book.from_text_and_meta(ContentFile(BOOK_TEXT), self.pol_info)
+        book2 = models.Book.from_text_and_meta(ContentFile(BOOK_TEXT), self.eng_info)
+
+        self.assertEqual(
+                set([b.language for b in models.Book.objects.all()]),
+                set(['pol', 'eng']),
+                'Books imported in wrong languages.'
+            )
+
+
 class BookImportGenerateTest(WLTestCase):
     def setUp(self):
         WLTestCase.setUp(self)
@@ -258,3 +296,13 @@ class BookImportGenerateTest(WLTestCase):
         parent = models.Book.from_xml_file(input)
         parent.build_pdf()
         self.assertTrue(path.exists(parent.pdf_file.path))
+
+    def test_custom_pdf(self):
+        out = models.get_dynamic_path(None, 'test-custom', ext='pdf')
+        absoulute_path = path.join(settings.MEDIA_ROOT, out)
+        
+        if not path.exists(path.dirname(absoulute_path)):
+            makedirs(path.dirname(absoulute_path))
+
+        self.book.build_pdf(customizations=['nofootnotes', '13pt', 'a4paper'], file_name=out)
+        self.assertTrue(path.exists(absoulute_path))
index b6598b3..5d2ba66 100644 (file)
@@ -103,8 +103,8 @@ class BookMediaTests(WLTestCase):
         bm.file.save(None, self.file)
         bm.save()
         
-        zip_url = self.book.zip_audiobooks()
-        self.assertEqual('zip/'+self.book.slug+'.zip', zip_url)
+        zip_url = self.book.zip_audiobooks('ogg')
+        self.assertEqual('zip/'+self.book.slug+'_ogg.zip', zip_url)
         self.assertTrue(exists(join(settings.MEDIA_ROOT, zip_url)))
 
         bm2 = models.BookMedia(book=self.book, type='ogg', name="Other title")
index edb29ab..90e7c12 100755 (executable)
@@ -12,7 +12,7 @@
 <dc:subject.genre xml:lang="pl">Fraszka</dc:subject.genre>
 
 <dc:description xml:lang="pl"></dc:description>
-<dc:identifier.url xml:lang="pl">http://wolnelektury.pl/lektura/fraszki</dc:identifier.url>
+<dc:identifier.url xml:lang="pl">http://wolnelektury.pl/katalog/lektura/fraszki</dc:identifier.url>
 <dc:source xml:lang="pl"></dc:source>
 <dc:rights xml:lang="pl">Domena publiczna - Jan Kochanowski zm. 1584</dc:rights>
 <dc:date.pd xml:lang="pl">1584</dc:date.pd>
index 7e6e667..a47e426 100644 (file)
@@ -3,9 +3,6 @@ from catalogue import models
 from catalogue.test_utils import *
 from django.core.files.base import ContentFile
 
-from nose.tools import raises
-
-
 class BooksByTagTests(WLTestCase):
     """ tests the /katalog/category/tag page for found books """
 
@@ -66,12 +63,13 @@ class BooksByTagTests(WLTestCase):
                          ['Child'])
 
 
-
+from django.test import Client
 class TagRelatedTagsTests(WLTestCase):
     """ tests the /katalog/category/tag/ page for related tags """
 
     def setUp(self):
         WLTestCase.setUp(self)
+        self.client = Client()
         author = PersonStub(("Common",), "Man")
 
         gchild_info = BookInfoStub(author=author, genre="GchildGenre", epoch='Epoch', kind="Kind",
@@ -132,14 +130,14 @@ class TagRelatedTagsTests(WLTestCase):
     def test_related_differ(self):
         """ related tags shouldn't include filtering tags """
 
-        cats = self.client.get('/katalog/rodzaj/kind/').context['categories']
+        response = self.client.get('/katalog/rodzaj/kind/')
+        cats = response.context['categories']
         self.assertFalse('Kind' in [tag.name for tag in cats['kind']],
                          'filtering tag wrongly included in related')
         cats = self.client.get('/katalog/motyw/theme/').context['categories']
         self.assertFalse('Theme' in [tag.name for tag in cats['theme']],
                          'filtering theme wrongly included in related')
 
-
     def test_parent_tag_once(self):
         """ if parent and descendants have a common tag, count it only once """
 
index 0e0da4b..db044fc 100644 (file)
@@ -4,12 +4,22 @@
 #
 from django.conf.urls.defaults import *
 from catalogue.feeds import AudiobookFeed
+from catalogue.models import Book
+from picture.models import Picture
+from catalogue.views import CustomPDFFormView
 
 
-urlpatterns = patterns('catalogue.views',
-    url(r'^$', 'main_page', name='main_page'),
+SLUG = r'[a-z0-9-]*'
+
+urlpatterns = patterns('picture.views',
+                       # pictures - currently pictures are coupled with catalogue, hence the url is here
+        url(r'^obraz/?$', 'picture_list'),
+        url(r'^obraz/(?P<picture>%s)/?$' % SLUG, 'picture_detail')
+        ) + \
+    patterns('catalogue.views',
+    url(r'^$', 'catalogue', name='catalogue'),
     url(r'^polki/(?P<shelf>[a-zA-Z0-9-]+)/formaty/$', 'shelf_book_formats', name='shelf_book_formats'),
-    url(r'^polki/(?P<shelf>[a-zA-Z0-9-]+)/(?P<book>[a-zA-Z0-9-0-]+)/usun$', 'remove_from_shelf', name='remove_from_shelf'),
+    url(r'^polki/(?P<shelf>[a-zA-Z0-9-]+)/(?P<slug>%s)/usun$' % SLUG, 'remove_from_shelf', name='remove_from_shelf'),
     url(r'^polki/$', 'user_shelves', name='user_shelves'),
     url(r'^polki/(?P<slug>[a-zA-Z0-9-]+)/usun/$', 'delete_shelf', name='delete_shelf'),
     url(r'^polki/(?P<slug>[a-zA-Z0-9-]+)\.zip$', 'download_shelf', name='download_shelf'),
@@ -17,29 +27,31 @@ urlpatterns = patterns('catalogue.views',
     url(r'^lektury/(?P<slug>[a-zA-Z0-9-]+)/$', 'collection', name='collection'),
     url(r'^audiobooki/$', 'audiobook_list', name='audiobook_list'),
     url(r'^daisy/$', 'daisy_list', name='daisy_list'),
-    url(r'^lektura/(?P<slug>[a-zA-Z0-9-]+)/polki/', 'book_sets', name='book_shelves'),
+    url(r'^lektura/(?P<book>%s)/polki/' % SLUG, 'book_sets', name='book_shelves'),
     url(r'^polki/nowa/$', 'new_set', name='new_set'),
     url(r'^tags/$', 'tags_starting_with', name='hint'),
     url(r'^jtags/$', 'json_tags_starting_with', name='jhint'),
-    url(r'^szukaj/$', 'search', name='search'),
+    url(r'^szukaj/$', 'search', name='old_search'),
 
     # zip
-    #url(r'^zip/pdf\.zip$', 'download_zip', {'format': 'pdf', 'slug': None}, 'download_zip_pdf'),
-    #url(r'^zip/epub\.zip$', 'download_zip', {'format': 'epub', 'slug': None}, 'download_zip_epub'),
-    #url(r'^zip/mobi\.zip$', 'download_zip', {'format': 'mobi', 'slug': None}, 'download_zip_mobi'),
-    #url(r'^zip/audiobook/(?P<slug>[a-zA-Z0-9-]+)\.zip', 'download_zip', {'format': 'audiobook'}, 'download_zip_audiobook'),
-
-    # tools
-    url(r'^zegar/$', 'clock', name='clock'),
+    url(r'^zip/pdf\.zip$', 'download_zip', {'format': 'pdf', 'slug': None}, 'download_zip_pdf'),
+    url(r'^zip/epub\.zip$', 'download_zip', {'format': 'epub', 'slug': None}, 'download_zip_epub'),
+    url(r'^zip/mobi\.zip$', 'download_zip', {'format': 'mobi', 'slug': None}, 'download_zip_mobi'),
+    url(r'^zip/mp3/(?P<slug>%s)\.zip' % SLUG, 'download_zip', {'format': 'mp3'}, 'download_zip_mp3'),
+    url(r'^zip/ogg/(?P<slug>%s)\.zip' % SLUG, 'download_zip', {'format': 'ogg'}, 'download_zip_ogg'),
 
     # Public interface. Do not change this URLs.
-    url(r'^lektura/(?P<slug>[a-zA-Z0-9-]+)\.html$', 'book_text', name='book_text'),
-    url(r'^lektura/(?P<slug>[a-zA-Z0-9-]+)/$', 'book_detail', name='book_detail'),
-    url(r'^lektura/(?P<book_slug>[a-zA-Z0-9-]+)/motyw/(?P<theme_slug>[a-zA-Z0-9-]+)/$',
+    url(r'^lektura/(?P<slug>%s)\.html$' % SLUG, 'book_text', name='book_text'),
+    url(r'^lektura/(?P<slug>%s)/audiobook/$' % SLUG, 'player', name='book_player'),
+    url(r'^lektura/(?P<slug>%s)/$' % SLUG, 'book_detail', name='book_detail'),
+    url(r'^lektura/(?P<slug>%s)/motyw/(?P<theme_slug>[a-zA-Z0-9-]+)/$' % SLUG,
         'book_fragments', name='book_fragments'),
 
     url(r'^(?P<tags>[a-zA-Z0-9-/]*)/$', 'tagged_object_list', name='tagged_object_list'),
 
     url(r'^audiobooki/(?P<type>mp3|ogg|daisy|all).xml$', AudiobookFeed(), name='audiobook_feed'),
-)
 
+    url(r'^custompdf$', CustomPDFFormView(), name='custom_pdf_form'),
+    url(r'^custompdf/(?P<slug>%s).pdf' % SLUG, 'download_custom_pdf'),
+
+) 
index 0134701..a48ec03 100644 (file)
@@ -8,7 +8,10 @@ import random
 import time
 from base64 import urlsafe_b64encode
 
+from django.http import HttpResponse, HttpResponseRedirect, Http404, HttpResponsePermanentRedirect
 from django.core.files.uploadedfile import UploadedFile
+from django.core.files.base import File
+from django.core.files.storage import DefaultStorage
 from django.utils.hashcompat import sha_constructor
 from django.conf import settings
 from celery.task import task
@@ -17,8 +20,9 @@ from errno import EEXIST, ENOENT
 from fcntl import flock, LOCK_EX
 from zipfile import ZipFile
 
-from librarian import DocProvider
-
+from reporting.utils import read_chunks
+from celery.task import task
+import catalogue.models
 
 # Use the system (hardware-based) random number generator if it exists.
 if hasattr(random, 'SystemRandom'):
@@ -55,19 +59,6 @@ class ExistingFile(UploadedFile):
         pass
 
 
-class ORMDocProvider(DocProvider):
-    """Used for getting books' children."""
-
-    def __init__(self, book):
-        self.book = book
-
-    def by_slug(self, slug):
-        if slug == self.book.slug:
-            return self.book.xml_file
-        else:
-            return type(self.book).objects.get(slug=slug).xml_file
-
-
 class LockFile(object):
     """
     A file lock monitor class; createas an ${objname}.lock
@@ -131,3 +122,64 @@ def remove_zip(zip_slug):
     except OSError as oe:
         if oe.errno != ENOENT:
             raise oe
+
+
+class AttachmentHttpResponse(HttpResponse):
+    """Response serving a file to be downloaded.
+    """
+    def __init__ (self, file_path, file_name, mimetype):
+        super(AttachmentHttpResponse, self).__init__(mimetype=mimetype)
+        self['Content-Disposition'] = 'attachment; filename=%s' % file_name
+        self.file_path = file_path
+        self.file_name = file_name
+
+        with open(DefaultStorage().path(self.file_path)) as f:
+            for chunk in read_chunks(f):
+                self.write(chunk)
+
+@task
+def async_build_pdf(book_id, customizations, file_name):
+    """
+    A celery task to generate pdf files.
+    Accepts the same args as Book.build_pdf, but with book id as first parameter
+    instead of Book instance
+    """
+    book = catalogue.models.Book.objects.get(id=book_id)
+    print "will gen %s" % DefaultStorage().path(file_name)
+    if not DefaultStorage().exists(file_name):
+        book.build_pdf(customizations=customizations, file_name=file_name)
+    print "done."
+
+
+class MultiQuerySet(object):
+    def __init__(self, *args, **kwargs):
+        self.querysets = args
+        self._count = None
+    
+    def count(self):
+        if not self._count:
+            self._count = sum(len(qs) for qs in self.querysets)
+        return self._count
+    
+    def __len__(self):
+        return self.count()
+        
+    def __getitem__(self, item):
+        try:
+            indices = (offset, stop, step) = item.indices(self.count())
+        except AttributeError:
+            # it's not a slice - make it one
+            return self[item : item + 1][0]
+        items = []
+        total_len = stop - offset
+        for qs in self.querysets:
+            if len(qs) < offset:
+                offset -= len(qs)
+            else:
+                items += list(qs[offset:stop])
+                if len(items) >= total_len:
+                    return items
+                else:
+                    offset = 0
+                    stop = total_len - len(items)
+                    continue
\ No newline at end of file
index bf0c42f..f57797d 100644 (file)
@@ -17,56 +17,39 @@ from django.utils.datastructures import SortedDict
 from django.views.decorators.http import require_POST
 from django.contrib import auth
 from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
-from django.utils import simplejson
-from django.utils.functional import Promise
-from django.utils.encoding import force_unicode
 from django.utils.http import urlquote_plus
 from django.views.decorators import cache
 from django.utils import translation
 from django.utils.translation import ugettext as _
 from django.views.generic.list_detail import object_list
 
+from ajaxable.utils import LazyEncoder, JSONResponse, AjaxableFormView
+
 from catalogue import models
 from catalogue import forms
-from catalogue.utils import split_tags
+from catalogue.utils import (split_tags, AttachmentHttpResponse,
+    async_build_pdf, MultiQuerySet)
+from catalogue.tasks import touch_tag
 from pdcounter import models as pdcounter_models
 from pdcounter import views as pdcounter_views
 from suggest.forms import PublishingSuggestForm
+from picture.models import Picture
 
+from os import path
 
 staff_required = user_passes_test(lambda user: user.is_staff)
 
 
-class LazyEncoder(simplejson.JSONEncoder):
-    def default(self, obj):
-        if isinstance(obj, Promise):
-            return force_unicode(obj)
-        return obj
-
-# shortcut for JSON reponses
-class JSONResponse(HttpResponse):
-    def __init__(self, data={}, callback=None, **kwargs):
-        # get rid of mimetype
-        kwargs.pop('mimetype', None)
-        data = simplejson.dumps(data)
-        if callback:
-            data = callback + "(" + data + ");" 
-        super(JSONResponse, self).__init__(data, mimetype="application/json", **kwargs)
-
-
-def main_page(request):
-    if request.user.is_authenticated():
-        shelves = models.Tag.objects.filter(category='set', user=request.user)
-        new_set_form = forms.NewSetForm()
-
-    tags = models.Tag.objects.exclude(category__in=('set', 'book'))
+def catalogue(request):
+    tags = models.Tag.objects.exclude(
+        category__in=('set', 'book')).exclude(book_count=0)
+    tags = list(tags)
     for tag in tags:
-        tag.count = tag.get_count()
+        tag.count = tag.book_count
     categories = split_tags(tags)
     fragment_tags = categories.get('theme', [])
 
-    form = forms.SearchForm()
-    return render_to_response('catalogue/main_page.html', locals(),
+    return render_to_response('catalogue/catalogue.html', locals(),
         context_instance=RequestContext(request))
 
 
@@ -74,8 +57,6 @@ def book_list(request, filter=None, template_name='catalogue/book_list.html',
         context=None):
     """ generates a listing of all books, optionally filtered with a test function """
 
-    form = forms.SearchForm()
-
     books_by_author, orphans, books_by_parent = models.Book.book_list(filter)
     books_nav = SortedDict()
     for tag in books_by_author:
@@ -122,6 +103,7 @@ def differentiate_tags(request, tags, ambiguous_slugs):
 
 
 def tagged_object_list(request, tags=''):
+    #    import pdb; pdb.set_trace()
     try:
         tags = models.Tag.get_tag_list(tags)
     except models.Tag.DoesNotExist:
@@ -200,28 +182,29 @@ def tagged_object_list(request, tags=''):
         only_author = len(tags) == 1 and tags[0].category == 'author'
         objects = models.Book.objects.none()
 
-    return object_list(
-        request,
-        objects,
-        template_name='catalogue/tagged_object_list.html',
-        extra_context={
+    # Add pictures
+    objects = MultiQuerySet(Picture.tagged.with_all(tags), objects)
+
+    return render_to_response('catalogue/tagged_object_list.html',
+        {
+            'object_list': objects,
             'categories': categories,
             'only_shelf': only_shelf,
             'only_author': only_author,
             'only_my_shelf': only_my_shelf,
             'formats_form': forms.DownloadFormatsForm(),
             'tags': tags,
-        }
-    )
+        },
+        context_instance=RequestContext(request))
 
 
-def book_fragments(request, book_slug, theme_slug):
-    book = get_object_or_404(models.Book, slug=book_slug)
-    book_tag = get_object_or_404(models.Tag, slug='l-' + book_slug, category='book')
+def book_fragments(request, slug, theme_slug):
+    book = get_object_or_404(models.Book, slug=slug)
+
+    book_tag = book.book_tag()
     theme = get_object_or_404(models.Tag, slug=theme_slug, category='theme')
     fragments = models.Fragment.tagged.with_all([book_tag, theme])
 
-    form = forms.SearchForm()
     return render_to_response('catalogue/book_fragments.html', locals(),
         context_instance=RequestContext(request))
 
@@ -230,13 +213,13 @@ def book_detail(request, slug):
     try:
         book = models.Book.objects.get(slug=slug)
     except models.Book.DoesNotExist:
-        return pdcounter_views.book_stub_detail(request, slug)
+        return pdcounter_views.book_stub_detail(request, kwargs['slug'])
 
     book_tag = book.book_tag()
     tags = list(book.tags.filter(~Q(category='set')))
     categories = split_tags(tags)
     book_children = book.children.all().order_by('parent_number', 'sort_key')
-    
+
     _book = book
     parents = []
     while _book.parent:
@@ -252,25 +235,52 @@ def book_detail(request, slug):
     extra_info = book.get_extra_info_value()
     hide_about = extra_info.get('about', '').startswith('http://wiki.wolnepodreczniki.pl')
 
+    custom_pdf_form = forms.CustomPDFForm()
+    return render_to_response('catalogue/book_detail.html', locals(),
+        context_instance=RequestContext(request))
+
+
+def player(request, slug):
+    book = get_object_or_404(models.Book, slug=slug)
+    if not book.has_media('mp3'):
+        raise Http404
+
+    ogg_files = {}
+    for m in book.media.filter(type='ogg').order_by():
+        ogg_files[m.name] = m
+
+    audiobooks = []
+    have_oggs = True
     projects = set()
-    for m in book.media.filter(type='mp3'):
+    for mp3 in book.media.filter(type='mp3'):
         # ogg files are always from the same project
-        meta = m.get_extra_info_value()
+        meta = mp3.get_extra_info_value()
         project = meta.get('project')
         if not project:
             # temporary fallback
             project = u'CzytamySłuchając'
 
         projects.add((project, meta.get('funded_by', '')))
+
+        media = {'mp3': mp3}
+
+        ogg = ogg_files.get(mp3.name)
+        if ogg:
+            media['ogg'] = ogg
+        else:
+            have_oggs = False
+        audiobooks.append(media)
+    print audiobooks
+
     projects = sorted(projects)
 
-    form = forms.SearchForm()
-    return render_to_response('catalogue/book_detail.html', locals(),
+    return render_to_response('catalogue/player.html', locals(),
         context_instance=RequestContext(request))
 
 
 def book_text(request, slug):
     book = get_object_or_404(models.Book, slug=slug)
+
     if not book.has_html_file():
         raise Http404
     book_themes = {}
@@ -402,7 +412,7 @@ def books_starting_with(prefix):
 
 
 def find_best_matches(query, user=None):
-    """ Finds a Book, Tag, BookStub or Author best matching a query.
+    """ Finds a models.Book, Tag, models.BookStub or Author best matching a query.
 
     Returns a with:
       - zero elements when nothing is found,
@@ -457,7 +467,7 @@ def search(request):
             context_instance=RequestContext(request))
     else:
         form = PublishingSuggestForm(initial={"books": prefix + ", "})
-        return render_to_response('catalogue/search_no_hits.html', 
+        return render_to_response('catalogue/search_no_hits.html',
             {'tags':tag_list, 'prefix':prefix, "pubsuggest_form": form},
             context_instance=RequestContext(request))
 
@@ -468,7 +478,7 @@ def tags_starting_with(request):
     if len(prefix) < 2:
         return HttpResponse('')
     tags_list = []
-    result = ""   
+    result = ""
     for tag in _tags_starting_with(prefix, request.user):
         if not tag.name in tags_list:
             result += "\n" + tag.name
@@ -509,6 +519,7 @@ def book_sets(request, slug):
         return HttpResponse(_('<p>To maintain your shelves you need to be logged in.</p>'))
 
     book = get_object_or_404(models.Book, slug=slug)
+
     user_sets = models.Tag.objects.filter(category='set', user=request.user)
     book_sets = book.tags.filter(category='set', user=request.user)
 
@@ -519,12 +530,10 @@ def book_sets(request, slug):
             new_shelves = [models.Tag.objects.get(pk=id) for id in form.cleaned_data['set_ids']]
 
             for shelf in [shelf for shelf in old_shelves if shelf not in new_shelves]:
-                shelf.book_count = None
-                shelf.save()
+                touch_tag(shelf)
 
             for shelf in [shelf for shelf in new_shelves if shelf not in old_shelves]:
-                shelf.book_count = None
-                shelf.save()
+                touch_tag(shelf)
 
             book.tags = new_shelves + list(book.tags.filter(~Q(category='set') | ~Q(user=request.user)))
             if request.is_ajax():
@@ -542,15 +551,14 @@ def book_sets(request, slug):
 @login_required
 @require_POST
 @cache.never_cache
-def remove_from_shelf(request, shelf, book):
-    book = get_object_or_404(models.Book, slug=book)
+def remove_from_shelf(request, shelf, slug):
+    book = get_object_or_404(models.Book, slug=slug)
+
     shelf = get_object_or_404(models.Tag, slug=shelf, category='set', user=request.user)
 
     if shelf in book.tags:
         models.Tag.objects.remove_tag(book, shelf)
-
-        shelf.book_count = None
-        shelf.save()
+        touch_tag(shelf)
 
         return HttpResponse(_('Book was successfully removed from the shelf'))
     else:
@@ -588,31 +596,17 @@ def download_shelf(request, slug):
     if form.is_valid():
         formats = form.cleaned_data['formats']
     if len(formats) == 0:
-        formats = ['pdf', 'epub', 'mobi', 'odt', 'txt']
+        formats = models.Book.ebook_formats
 
     # Create a ZIP archive
     temp = tempfile.TemporaryFile()
     archive = zipfile.ZipFile(temp, 'w')
 
-    already = set()
     for book in collect_books(models.Book.tagged.with_all(shelf)):
-        if 'pdf' in formats and book.pdf_file:
-            filename = book.pdf_file.path
-            archive.write(filename, str('%s.pdf' % book.slug))
-        if 'mobi' in formats and book.mobi_file:
-            filename = book.mobi_file.path
-            archive.write(filename, str('%s.mobi' % book.slug))
-        if book.root_ancestor not in already and 'epub' in formats and book.root_ancestor.epub_file:
-            filename = book.root_ancestor.epub_file.path
-            archive.write(filename, str('%s.epub' % book.root_ancestor.slug))
-            already.add(book.root_ancestor)
-        if 'odt' in formats and book.has_media("odt"):
-            for file in book.get_media("odt"):
-                filename = file.file.path
-                archive.write(filename, str('%s.odt' % slughifi(file.name)))
-        if 'txt' in formats and book.txt_file:
-            filename = book.txt_file.path
-            archive.write(filename, str('%s.txt' % book.slug))
+        for ebook_format in models.Book.ebook_formats:
+            if ebook_format in formats and book.has_media(ebook_format):
+                filename = book.get_media(ebook_format).path
+                archive.write(filename, str('%s.%s' % (book.slug, ebook_format)))
     archive.close()
 
     response = HttpResponse(content_type='application/zip', mimetype='application/x-zip-compressed')
@@ -631,20 +625,14 @@ def shelf_book_formats(request, shelf):
     """
     shelf = get_object_or_404(models.Tag, slug=shelf, category='set')
 
-    formats = {'pdf': False, 'epub': False, 'mobi': False, 'odt': False, 'txt': False}
+    formats = {}
+    for ebook_format in models.Book.ebook_formats:
+        formats[ebook_format] = False
 
     for book in collect_books(models.Book.tagged.with_all(shelf)):
-        if book.pdf_file:
-            formats['pdf'] = True
-        if book.root_ancestor.epub_file:
-            formats['epub'] = True
-        if book.mobi_file:
-            formats['mobi'] = True
-        if book.txt_file:
-            formats['txt'] = True
-        for format in ('odt',):
-            if book.has_media(format):
-                formats[format] = True
+        for ebook_format in models.Book.ebook_formats:
+            if book.has_media(ebook_format):
+                formats[ebook_format] = True
 
     return HttpResponse(LazyEncoder().encode(formats))
 
@@ -678,45 +666,6 @@ def delete_shelf(request, slug):
         return HttpResponseRedirect('/')
 
 
-# ==================
-# = Authentication =
-# ==================
-@require_POST
-@cache.never_cache
-def login(request):
-    form = AuthenticationForm(data=request.POST, prefix='login')
-    if form.is_valid():
-        auth.login(request, form.get_user())
-        response_data = {'success': True, 'errors': {}}
-    else:
-        response_data = {'success': False, 'errors': form.errors}
-    return HttpResponse(LazyEncoder(ensure_ascii=False).encode(response_data))
-
-
-@require_POST
-@cache.never_cache
-def register(request):
-    registration_form = UserCreationForm(request.POST, prefix='registration')
-    if registration_form.is_valid():
-        user = registration_form.save()
-        user = auth.authenticate(
-            username=registration_form.cleaned_data['username'],
-            password=registration_form.cleaned_data['password1']
-        )
-        auth.login(request, user)
-        response_data = {'success': True, 'errors': {}}
-    else:
-        response_data = {'success': False, 'errors': registration_form.errors}
-    return HttpResponse(LazyEncoder(ensure_ascii=False).encode(response_data))
-
-
-@cache.never_cache
-def logout_then_redirect(request):
-    auth.logout(request)
-    return HttpResponseRedirect(urlquote_plus(request.GET.get('next', '/'), safe='/?='))
-
-
-
 # =========
 # = Admin =
 # =========
@@ -741,14 +690,6 @@ def import_book(request):
         return HttpResponse(_("Error importing file: %r") % book_import_form.errors)
 
 
-
-def clock(request):
-    """ Provides server time for jquery.countdown,
-    in a format suitable for Date.parse()
-    """
-    return HttpResponse(datetime.now().strftime('%Y/%m/%d %H:%M:%S'))
-
-
 # info views for API
 
 def book_info(request, id, lang='pl'):
@@ -764,13 +705,50 @@ def tag_info(request, id):
     return HttpResponse(tag.description)
 
 
-def download_zip(request, format, slug):
+def download_zip(request, format, slug=None):
     url = None
-    if format in ('pdf', 'epub', 'mobi'):
+    if format in models.Book.ebook_formats:
         url = models.Book.zip_format(format)
-    elif format == 'audiobook' and slug is not None:
-        book = models.Book.objects.get(slug=slug)
-        url = book.zip_audiobooks()
+    elif format in ('mp3', 'ogg') and slug is not None:
+        book = get_object_or_404(models.Book, slug=slug)
+        url = book.zip_audiobooks(format)
     else:
         raise Http404('No format specified for zip package')
     return HttpResponseRedirect(urlquote_plus(settings.MEDIA_URL + url, safe='/?='))
+
+
+def download_custom_pdf(request, slug, method='GET'):
+    book = get_object_or_404(models.Book, slug=slug)
+
+    if request.method == method:
+        form = forms.CustomPDFForm(method == 'GET' and request.GET or request.POST)
+        if form.is_valid():
+            cust = form.customizations
+            pdf_file = models.get_customized_pdf_path(book, cust)
+
+            if not path.exists(pdf_file):
+                result = async_build_pdf.delay(book.id, cust, pdf_file)
+                result.wait()
+            return AttachmentHttpResponse(file_name=("%s.pdf" % book.slug), file_path=pdf_file, mimetype="application/pdf")
+        else:
+            raise Http404(_('Incorrect customization options for PDF'))
+    else:
+        raise Http404(_('Bad method'))
+
+
+class CustomPDFFormView(AjaxableFormView):
+    form_class = forms.CustomPDFForm
+    title = _('Download custom PDF')
+    submit = _('Download')
+
+    def __call__(self, request):
+        from copy import copy
+        if request.method == 'POST':
+            request.GET = copy(request.GET)
+            request.GET['next'] = "%s?%s" % (reverse('catalogue.views.download_custom_pdf', args=[request.GET['slug']]),
+                                             request.POST.urlencode())
+        return super(CustomPDFFormView, self).__call__(request)
+        
+
+    def success(self, *args):
+        pass
index e0b10f3..6eac93a 100755 (executable)
@@ -1,17 +1,16 @@
 {% extends "base.html" %}
 {% load i18n pagination_tags %}
-{% load catalogue_tags %}
 
 
 {% block bodyid %}footnotes{% endblock %}
 
-{% block title %}{% trans "Footnotes on WolneLektury.pl" %}{% endblock %}
+{% block titleextra %}{% trans "Footnotes" %}{% endblock %}
 
 
 {% block body %}
     <h1>{% trans "Footnotes" %}</h1>
-    {% search_form %}
 
+<div class="normal-text">
 
 <p>
 {% trans "By first letter" %}:
@@ -52,4 +51,6 @@
 
 {% endif %}
 
+</div>
+
 {% endblock %}
index e120063..7b9cd53 100755 (executable)
@@ -7,7 +7,6 @@ from catalogue.forms import SearchForm
 from dictionary.models import Note
 
 def letter_notes(request, letter=None):
-    form = SearchForm()
     letters = ["0-9"] + [chr(a) for a in range(ord('a'), ord('z')+1)]
     objects = Note.objects.all()
     if letter == "0-9":
index 66f2996..e5bc93c 100644 (file)
@@ -4,6 +4,6 @@ from modeltranslation.admin import TranslationAdmin
 from infopages.models import InfoPage
 
 class InfoPageAdmin(TranslationAdmin):
-    list_display = ('title',)
+    list_display = ('title', 'slug', 'main_page')
 
 admin.site.register(InfoPage, InfoPageAdmin)
\ No newline at end of file
diff --git a/apps/infopages/migrations/0002_auto__del_field_infopage_page_title__del_field_infopage_page_title_en_.py b/apps/infopages/migrations/0002_auto__del_field_infopage_page_title__del_field_infopage_page_title_en_.py
new file mode 100644 (file)
index 0000000..6ecd60b
--- /dev/null
@@ -0,0 +1,111 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        
+        # Deleting field 'InfoPage.page_title'
+        db.delete_column('infopages_infopage', 'page_title')
+
+        # Deleting field 'InfoPage.page_title_en'
+        db.delete_column('infopages_infopage', 'page_title_en')
+
+        # Deleting field 'InfoPage.page_title_es'
+        db.delete_column('infopages_infopage', 'page_title_es')
+
+        # Deleting field 'InfoPage.page_title_fr'
+        db.delete_column('infopages_infopage', 'page_title_fr')
+
+        # Deleting field 'InfoPage.page_title_uk'
+        db.delete_column('infopages_infopage', 'page_title_uk')
+
+        # Deleting field 'InfoPage.page_title_de'
+        db.delete_column('infopages_infopage', 'page_title_de')
+
+        # Deleting field 'InfoPage.page_title_lt'
+        db.delete_column('infopages_infopage', 'page_title_lt')
+
+        # Deleting field 'InfoPage.page_title_pl'
+        db.delete_column('infopages_infopage', 'page_title_pl')
+
+        # Deleting field 'InfoPage.page_title_ru'
+        db.delete_column('infopages_infopage', 'page_title_ru')
+
+        # Adding field 'InfoPage.main_page'
+        db.add_column('infopages_infopage', 'main_page', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True), keep_default=False)
+
+
+    def backwards(self, orm):
+        
+        # Adding field 'InfoPage.page_title'
+        db.add_column('infopages_infopage', 'page_title', self.gf('django.db.models.fields.CharField')(default='', max_length=120, blank=True), keep_default=False)
+
+        # Adding field 'InfoPage.page_title_en'
+        db.add_column('infopages_infopage', 'page_title_en', self.gf('django.db.models.fields.CharField')(max_length=120, null=True, blank=True), keep_default=False)
+
+        # Adding field 'InfoPage.page_title_es'
+        db.add_column('infopages_infopage', 'page_title_es', self.gf('django.db.models.fields.CharField')(max_length=120, null=True, blank=True), keep_default=False)
+
+        # Adding field 'InfoPage.page_title_fr'
+        db.add_column('infopages_infopage', 'page_title_fr', self.gf('django.db.models.fields.CharField')(max_length=120, null=True, blank=True), keep_default=False)
+
+        # Adding field 'InfoPage.page_title_uk'
+        db.add_column('infopages_infopage', 'page_title_uk', self.gf('django.db.models.fields.CharField')(max_length=120, null=True, blank=True), keep_default=False)
+
+        # Adding field 'InfoPage.page_title_de'
+        db.add_column('infopages_infopage', 'page_title_de', self.gf('django.db.models.fields.CharField')(max_length=120, null=True, blank=True), keep_default=False)
+
+        # Adding field 'InfoPage.page_title_lt'
+        db.add_column('infopages_infopage', 'page_title_lt', self.gf('django.db.models.fields.CharField')(max_length=120, null=True, blank=True), keep_default=False)
+
+        # Adding field 'InfoPage.page_title_pl'
+        db.add_column('infopages_infopage', 'page_title_pl', self.gf('django.db.models.fields.CharField')(max_length=120, null=True, blank=True), keep_default=False)
+
+        # Adding field 'InfoPage.page_title_ru'
+        db.add_column('infopages_infopage', 'page_title_ru', self.gf('django.db.models.fields.CharField')(max_length=120, null=True, blank=True), keep_default=False)
+
+        # Deleting field 'InfoPage.main_page'
+        db.delete_column('infopages_infopage', 'main_page')
+
+
+    models = {
+        'infopages.infopage': {
+            'Meta': {'ordering': "('main_page', 'slug')", 'object_name': 'InfoPage'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'left_column': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'left_column_de': ('django.db.models.fields.TextField', [], {'null': True, 'blank': True}),
+            'left_column_en': ('django.db.models.fields.TextField', [], {'null': True, 'blank': True}),
+            'left_column_es': ('django.db.models.fields.TextField', [], {'null': True, 'blank': True}),
+            'left_column_fr': ('django.db.models.fields.TextField', [], {'null': True, 'blank': True}),
+            'left_column_lt': ('django.db.models.fields.TextField', [], {'null': True, 'blank': True}),
+            'left_column_pl': ('django.db.models.fields.TextField', [], {'null': True, 'blank': True}),
+            'left_column_ru': ('django.db.models.fields.TextField', [], {'null': True, 'blank': True}),
+            'left_column_uk': ('django.db.models.fields.TextField', [], {'null': True, 'blank': True}),
+            'main_page': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'right_column': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'right_column_de': ('django.db.models.fields.TextField', [], {'null': True, 'blank': True}),
+            'right_column_en': ('django.db.models.fields.TextField', [], {'null': True, 'blank': True}),
+            'right_column_es': ('django.db.models.fields.TextField', [], {'null': True, 'blank': True}),
+            'right_column_fr': ('django.db.models.fields.TextField', [], {'null': True, 'blank': True}),
+            'right_column_lt': ('django.db.models.fields.TextField', [], {'null': True, 'blank': True}),
+            'right_column_pl': ('django.db.models.fields.TextField', [], {'null': True, 'blank': True}),
+            'right_column_ru': ('django.db.models.fields.TextField', [], {'null': True, 'blank': True}),
+            'right_column_uk': ('django.db.models.fields.TextField', [], {'null': True, 'blank': True}),
+            'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '120', 'db_index': 'True'}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '120', 'blank': 'True'}),
+            'title_de': ('django.db.models.fields.CharField', [], {'max_length': '120', 'null': True, 'blank': True}),
+            'title_en': ('django.db.models.fields.CharField', [], {'max_length': '120', 'null': True, 'blank': True}),
+            'title_es': ('django.db.models.fields.CharField', [], {'max_length': '120', 'null': True, 'blank': True}),
+            'title_fr': ('django.db.models.fields.CharField', [], {'max_length': '120', 'null': True, 'blank': True}),
+            'title_lt': ('django.db.models.fields.CharField', [], {'max_length': '120', 'null': True, 'blank': True}),
+            'title_pl': ('django.db.models.fields.CharField', [], {'max_length': '120', 'null': True, 'blank': True}),
+            'title_ru': ('django.db.models.fields.CharField', [], {'max_length': '120', 'null': True, 'blank': True}),
+            'title_uk': ('django.db.models.fields.CharField', [], {'max_length': '120', 'null': True, 'blank': True})
+        }
+    }
+
+    complete_apps = ['infopages']
index 9fe7b32..cf9e9bf 100644 (file)
@@ -6,21 +6,22 @@ from django.db import models
 from django.utils.translation import ugettext_lazy as _
 
 class InfoPage(models.Model):
-    """
-    An InfoPage is used to display a two-column flatpage
-    """
+    """An InfoPage is used to display a two-column flatpage."""
 
-    page_title = models.CharField(_('page title'), max_length=120, blank=True)
+    main_page = models.IntegerField(_('main page priority'), null=True, blank=True)
     slug = models.SlugField(_('slug'), max_length=120, unique=True, db_index=True)
     title = models.CharField(_('title'), max_length=120, blank=True)
     left_column = models.TextField(_('left column'), blank=True)
     right_column = models.TextField(_('right column'), blank=True)
 
     class Meta:
-        ordering = ('slug',)
+        ordering = ('main_page', 'slug',)
         verbose_name = _('info page')
         verbose_name_plural = _('info pages')
 
     def __unicode__(self):
         return self.title
 
+    @models.permalink
+    def get_absolute_url(self):
+        return ('infopage', [self.slug])
diff --git a/apps/infopages/templates/infopages/infopage.html b/apps/infopages/templates/infopages/infopage.html
new file mode 100755 (executable)
index 0000000..db2d49b
--- /dev/null
@@ -0,0 +1,24 @@
+{% extends "base.html" %}
+{% load i18n %}
+{% load chunks %}
+
+{% block titleextra %}{{ page.title }}{% endblock %}
+
+{% block metadescription %}{{ left_column|striptags|truncatewords:10 }}{% endblock %}
+
+{% block body %}
+    <h1>{{ page.title }}</h1>
+
+    {% autoescape off %}
+    <div class="left-column">
+        <div class="normal-text">
+            {{ left_column }}
+        </div>
+    </div>
+    <div class="right-column">
+        <div class="normal-text">
+            {{ right_column }}
+        </div>
+    </div>
+    {% endautoescape %}
+{% endblock %}
diff --git a/apps/infopages/templates/infopages/on_main.html b/apps/infopages/templates/infopages/on_main.html
new file mode 100755 (executable)
index 0000000..dc0103c
--- /dev/null
@@ -0,0 +1,5 @@
+<ul>
+{% for page in objects %}
+    <li><a href="{{ page.get_absolute_url }}">{{ page.title }}</a></li>
+{% endfor %}
+</ul>
diff --git a/apps/infopages/templatetags/__init__.py b/apps/infopages/templatetags/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/apps/infopages/templatetags/infopages_tags.py b/apps/infopages/templatetags/infopages_tags.py
new file mode 100755 (executable)
index 0000000..d7c93ca
--- /dev/null
@@ -0,0 +1,14 @@
+# -*- coding: utf-8 -*-
+# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
+from django import template
+from infopages.models import InfoPage
+
+register = template.Library()
+
+
+@register.inclusion_tag('infopages/on_main.html')
+def infopages_on_main():
+    objects = InfoPage.objects.exclude(main_page=None)
+    return {"objects": objects}
diff --git a/apps/infopages/urls.py b/apps/infopages/urls.py
new file mode 100755 (executable)
index 0000000..081e0ef
--- /dev/null
@@ -0,0 +1,11 @@
+# -*- coding: utf-8 -*-
+# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
+from django.conf.urls.defaults import *
+
+
+urlpatterns = patterns('infopages.views',
+    url(r'^(?P<slug>[a-zA-Z0-9_-]+)/$', 'infopage', name='infopage'),
+)
+
index 07a416b..d5dee76 100644 (file)
@@ -2,15 +2,24 @@
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
-from django.shortcuts import render_to_response
-from django.template import RequestContext
+from django.shortcuts import render_to_response, get_object_or_404
+from django.template import RequestContext, Template, TemplateSyntaxError
 
-from catalogue.forms import SearchForm
 from infopages.models import InfoPage
 
+
 def infopage(request, slug):
-    form = SearchForm()
-    object = InfoPage.objects.get(slug=slug)
+    page = get_object_or_404(InfoPage, slug=slug)
+    rc = RequestContext(request)
+    try:
+        left_column = Template(page.left_column).render(rc)
+    except TemplateSyntaxError:
+        left_column = ''
+
+    try:
+        right_column = Template(page.right_column).render(rc)
+    except TemplateSyntaxError:
+        left_column = ''
 
-    return render_to_response('info/base.html', locals(),
-                context_instance=RequestContext(request))
\ No newline at end of file
+    return render_to_response('infopages/infopage.html', locals(),
+                context_instance=RequestContext(request))
index e7bbb48..de48644 100644 (file)
@@ -3,11 +3,12 @@
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 from django.conf.urls.defaults import *
+from catalogue.models import Book
 
 urlpatterns = patterns('lesmianator.views',
     url(r'^$', 'main_page', name='lesmianator'),
     url(r'^wiersz/$', 'new_poem', name='new_poem'),
-    url(r'^lektura/(?P<slug>[a-zA-Z0-9-]+)/$', 'poem_from_book', name='poem_from_book'),
+    url(r'^lektura/(?P<slug>[a-z0-9-]+)/$', 'poem_from_book', name='poem_from_book'),
     url(r'^polka/(?P<shelf>[a-zA-Z0-9-]+)/$', 'poem_from_set', name='poem_from_set'),
     url(r'^wiersz/(?P<poem>[a-zA-Z0-9-]+)/$', 'get_poem', name='get_poem'),
 )
index 56acb57..e86febe 100644 (file)
@@ -1,5 +1,6 @@
 # Create your views here.
 
+from django.http import Http404
 from django.shortcuts import render_to_response, get_object_or_404
 from django.template import RequestContext
 from django.contrib.auth.decorators import login_required
@@ -7,17 +8,15 @@ from django.views.decorators import cache
 
 from catalogue.utils import get_random_hash
 from catalogue.models import Book, Tag
-from catalogue import forms
 from lesmianator.models import Poem, Continuations
 
 
 def main_page(request):
     last = Poem.objects.all().order_by('-created_at')[:10]
-    form = forms.SearchForm()
     shelves = Tag.objects.filter(user__username='lesmianator')
 
     return render_to_response('lesmianator/lesmianator.html', 
-                {"last": last, "form": form, "shelves": shelves},
+                {"last": last, "shelves": shelves},
                 context_instance=RequestContext(request))
 
 
index 1d25637..4b6a3e1 100644 (file)
@@ -9,9 +9,6 @@ from catalogue import forms
 urlpatterns = patterns('',
     url(r'^$', 'django.views.generic.simple.direct_to_template', {
         'template': 'lessons/document_list.html',
-        'extra_context': {
-            'form': forms.SearchForm(),
-        },
     }, name='lessons_document_list'),
 
     url(r'^(?P<slug>[a-zA-Z0-9_-]+)/$', 'lessons.views.document_detail', name='lessons_document_detail'),
index 242526d..9314d1c 100644 (file)
@@ -3,7 +3,6 @@
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 from django.views.generic.list_detail import object_detail
-from catalogue import forms
 from lessons import models
 
 
@@ -17,6 +16,4 @@ def document_detail(request, slug):
         slug_field='slug',
         queryset=models.Document.objects.all(),
         template_name=template_name,
-        extra_context={
-            'form': forms.SearchForm(),
-        })
+    )
index c7d3828..dc094bb 100644 (file)
@@ -5,6 +5,7 @@
 from base64 import b64encode
 import os.path
 from urlparse import urljoin
+from urllib2 import unquote
 
 from django.contrib.syndication.views import Feed
 from django.core.urlresolvers import reverse
@@ -16,7 +17,11 @@ from django.contrib.sites.models import Site
 
 from basicauth import logged_in_or_basicauth, factory_decorator
 from catalogue.models import Book, Tag
-from catalogue.views import books_starting_with
+
+from search import Search, SearchResult, JVM
+from lucene import Term, QueryWrapperFilter, TermQuery
+
+import re
 
 from stats.utils import piwik_track
 
@@ -229,7 +234,7 @@ class ByCategoryFeed(Feed):
         return feed['title']
 
     def items(self, feed):
-        return (tag for tag in Tag.objects.filter(category=feed['category']) if tag.get_count() > 0)
+        return Tag.objects.filter(category=feed['category']).exclude(book_count=0)
 
     def item_title(self, item):
         return item.name
@@ -280,7 +285,7 @@ class UserFeed(Feed):
         return u"Półki użytkownika %s" % user.username
 
     def items(self, user):
-        return (tag for tag in Tag.objects.filter(category='set', user=user) if tag.get_count() > 0)
+        return Tag.objects.filter(category='set', user=user).exclude(book_count=0)
 
     def item_title(self, item):
         return item.name
@@ -316,20 +321,124 @@ class UserSetFeed(AcquisitionFeed):
 # no class decorators in python 2.5
 #UserSetFeed = factory_decorator(logged_in_or_basicauth())(UserSetFeed)
 
+
 @piwik_track
 class SearchFeed(AcquisitionFeed):
     description = u"Wyniki wyszukiwania na stronie WolneLektury.pl"
     title = u"Wyniki wyszukiwania"
+
+    INLINE_QUERY_RE = re.compile(r"(author:(?P<author>[^ ]+)|title:(?P<title>[^ ]+)|categories:(?P<categories>[^ ]+)|description:(?P<description>[^ ]+))")
     
     def get_object(self, request):
-        return request.GET.get('q', '')
+        """
+        For OPDS 1.1 We should handle a query for search terms
+        and criteria provided either as opensearch or 'inline' query.
+        OpenSearch defines fields: atom:author, atom:contributor (treated as translator),
+        atom:title. Inline query provides author, title, categories (treated as book tags),
+        description (treated as content search terms).
+        
+        if search terms are provided, we shall search for books
+        according to Hint information (from author & contributror & title).
+
+        but if search terms are empty, we should do a different search
+        (perhaps for is_book=True)
+
+        """
+        JVM.attachCurrentThread()
+
+        query = request.GET.get('q', '')
+
+        inline_criteria = re.findall(self.INLINE_QUERY_RE, query)
+        if inline_criteria:
+            def get_criteria(criteria, name, position):
+                e = filter(lambda el: el[0][0:len(name)] == name, criteria)
+                print e
+                if not e:
+                    return None
+                c = e[0][position]
+                print c
+                if c[0] == '"' and c[-1] == '"':
+                    c = c[1:-1]
+                    c = c.replace('+', ' ')
+                return c
+
+            #import pdb; pdb.set_trace()
+            author = get_criteria(inline_criteria, 'author', 1)
+            title = get_criteria(inline_criteria, 'title', 2)
+            translator = None
+            categories = get_criteria(inline_criteria, 'categories', 3)
+            query = get_criteria(inline_criteria, 'description', 4)
+        else:
+            author = request.GET.get('author', '')
+            title = request.GET.get('title', '')
+            translator = request.GET.get('translator', '')
+            categories = None
+            fuzzy = False
+
+
+        srch = Search()
+        hint = srch.hint()
+
+        # Scenario 1: full search terms provided.
+        # Use auxiliarry information to narrow it and make it better.
+        if query:
+            filters = []
+
+            if author:
+                print "narrow to author %s" % author
+                hint.tags(srch.search_tags(author, filter=srch.term_filter(Term('tag_category', 'author'))))
+
+            if translator:
+                print "filter by translator %s" % translator
+                filters.append(QueryWrapperFilter(
+                    srch.make_phrase(srch.get_tokens(translator, field='translators'),
+                                     field='translators')))
+
+            if categories:
+                filters.append(QueryWrapperFilter(
+                    srch.make_phrase(srch.get_tokens(categories, field="tag_name_pl"),
+                                     field='tag_name_pl')))
+
+            flt = srch.chain_filters(filters)
+            if title:
+                print "hint by book title %s" % title
+                q = srch.make_phrase(srch.get_tokens(title, field='title'), field='title')
+                hint.books(*srch.search_books(q, filter=flt))
+
+            toks = srch.get_tokens(query)
+            print "tokens: %s" % toks
+            #            import pdb; pdb.set_trace()
+            results = SearchResult.aggregate(srch.search_perfect_book(toks, fuzzy=fuzzy, hint=hint),
+                srch.search_perfect_parts(toks, fuzzy=fuzzy, hint=hint),
+                srch.search_everywhere(toks, fuzzy=fuzzy, hint=hint))
+            results.sort(reverse=True)
+            return [r.book for r in results]
+        else:
+            # Scenario 2: since we no longer have to figure out what the query term means to the user,
+            # we can just use filters and not the Hint class.
+            filters = []
+
+            fields = {
+                'author': author,
+                'translators': translator,
+                'title': title
+                }
+
+            for fld, q in fields.items():
+                if q:
+                    filters.append(QueryWrapperFilter(
+                        srch.make_phrase(srch.get_tokens(q, field=fld), field=fld)))
+
+            flt = srch.chain_filters(filters)
+            books = srch.search_books(TermQuery(Term('is_book', 'true')), filter=flt)
+            return books
 
     def get_link(self, query):
-        return "%s?q=%s" % (reverse('search'), query) 
+        return "%s?q=%s" % (reverse('search'), query)
 
-    def items(self, query):
+    def items(self, books):
         try:
-            return books_starting_with(query)
+            return books
         except ValueError:
             # too short a query
             return []
diff --git a/apps/pdcounter/urls.py b/apps/pdcounter/urls.py
deleted file mode 100644 (file)
index 2325137..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-# -*- coding: utf-8 -*-
-# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
-# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
-#
-from django.conf.urls.defaults import *
-
-
-urlpatterns = patterns('catalogue.views',
-    url(r'^$', 'main_page', name='main_page'),
-    url(r'^polki/(?P<shelf>[a-zA-Z0-9-]+)/formaty/$', 'shelf_book_formats', name='shelf_book_formats'),
-    url(r'^polki/(?P<shelf>[a-zA-Z0-9-]+)/(?P<book>[a-zA-Z0-9-0-]+)/usun$', 'remove_from_shelf', name='remove_from_shelf'),
-    url(r'^polki/$', 'user_shelves', name='user_shelves'),
-    url(r'^polki/(?P<slug>[a-zA-Z0-9-]+)/usun/$', 'delete_shelf', name='delete_shelf'),
-    url(r'^polki/(?P<slug>[a-zA-Z0-9-]+)\.zip$', 'download_shelf', name='download_shelf'),
-    url(r'^lektury/', 'book_list', name='book_list'),
-    url(r'^audiobooki/', 'audiobook_list', name='audiobook_list'),
-    url(r'^daisy/', 'daisy_list', name='daisy_list'),
-    url(r'^lektura/(?P<slug>[a-zA-Z0-9-]+)/polki/', 'book_sets', name='book_shelves'),
-    url(r'^polki/nowa/$', 'new_set', name='new_set'),
-    url(r'^tags/$', 'tags_starting_with', name='hint'),
-    url(r'^jtags/$', 'json_tags_starting_with', name='jhint'),
-    url(r'^szukaj/$', 'search', name='search'),
-
-    # tools
-    url(r'^zegar/$', 'clock', name='clock'),
-    url(r'^xmls.zip$', 'xmls', name='xmls'),
-    url(r'^epubs.tar$', 'epubs', name='epubs'),
-
-    # Public interface. Do not change this URLs.
-    url(r'^lektura/(?P<slug>[a-zA-Z0-9-]+)\.html$', 'book_text', name='book_text'),
-    url(r'^lektura/(?P<slug>[a-zA-Z0-9-]+)/$', 'book_detail', name='book_detail'),
-    url(r'^lektura/(?P<book_slug>[a-zA-Z0-9-]+)/motyw/(?P<theme_slug>[a-zA-Z0-9-]+)/$',
-        'book_fragments', name='book_fragments'),
-    url(r'^(?P<tags>[a-zA-Z0-9-/]*)/$', 'tagged_object_list', name='tagged_object_list'),
-)
-
index efcfe95..b07ee11 100644 (file)
@@ -7,16 +7,14 @@
 from django.template import RequestContext
 from django.shortcuts import render_to_response, get_object_or_404
 from pdcounter import models
-from catalogue import forms
 from suggest.forms import PublishingSuggestForm
 
 
 def book_stub_detail(request, slug):
     book = get_object_or_404(models.BookStub, slug=slug)
     pd_counter = book.pd
-    form = forms.SearchForm()
 
-    pubsuggest_form = PublishingSuggestForm(
+    form = PublishingSuggestForm(
             initial={"books": u"%s — %s, \n" % (book.author, book.title)})
 
     return render_to_response('pdcounter/book_stub_detail.html', locals(),
@@ -26,9 +24,8 @@ def book_stub_detail(request, slug):
 def author_detail(request, slug):
     author = get_object_or_404(models.Author, slug=slug)
     pd_counter = author.goes_to_pd()
-    form = forms.SearchForm()
 
-    pubsuggest_form = PublishingSuggestForm(initial={"books": author.name + ", \n"})
+    form = PublishingSuggestForm(initial={"books": author.name + ", \n"})
 
     return render_to_response('pdcounter/author_detail.html', locals(),
         context_instance=RequestContext(request))
diff --git a/apps/picture/__init__.py b/apps/picture/__init__.py
new file mode 100644 (file)
index 0000000..8b13789
--- /dev/null
@@ -0,0 +1 @@
+
diff --git a/apps/picture/admin.py b/apps/picture/admin.py
new file mode 100644 (file)
index 0000000..fb6bcf2
--- /dev/null
@@ -0,0 +1,9 @@
+
+from django.contrib import admin
+from picture.models import Picture
+from sorl.thumbnail.admin import AdminImageMixin
+
+class PictureAdmin(AdminImageMixin, admin.ModelAdmin):
+    pass
+
+admin.site.register(Picture, PictureAdmin)
diff --git a/apps/picture/forms.py b/apps/picture/forms.py
new file mode 100644 (file)
index 0000000..9d0c5a5
--- /dev/null
@@ -0,0 +1,35 @@
+from django import forms
+from django.utils.translation import ugettext_lazy as _
+from picture.models import Picture
+
+
+class PictureImportForm(forms.Form):
+    picture_xml_file = forms.FileField(required=False)
+    picture_xml = forms.CharField(required=False)
+    picture_image_file = forms.FileField(required=False)
+    picture_image_data = forms.CharField(required=False)
+
+    def clean(self):
+        from base64 import b64decode
+        from django.core.files.base import ContentFile
+
+        if not self.cleaned_data['picture_xml_file']:
+            if self.cleaned_data['picture_xml']:
+                self.cleaned_data['picture_xml_file'] = \
+                        ContentFile(self.cleaned_data['picture_xml'].encode('utf-8'))
+            else:
+                raise forms.ValidationError(_("Please supply an XML."))
+
+        if not self.cleaned_data['picture_image_file']:
+            if self.cleaned_data['picture_image_data']:
+                self.cleaned_data['picture_image_file'] = \
+                        ContentFile(b64decode(
+                                self.cleaned_data['picture_image_data']))
+            else:
+                raise forms.ValidationError(_("Please supply an image."))
+
+        return super(PictureImportForm, self).clean()
+
+    def save(self, commit=True, **kwargs):
+        return Picture.from_xml_file(self.cleaned_data['picture_xml_file'], image_file=self.cleaned_data['picture_image_file'],
+                                     overwrite=True, **kwargs)
diff --git a/apps/picture/models.py b/apps/picture/models.py
new file mode 100644 (file)
index 0000000..0217d8b
--- /dev/null
@@ -0,0 +1,195 @@
+from django.db import models
+import catalogue.models
+from django.db.models import permalink
+from sorl.thumbnail import ImageField
+from django.conf import settings
+from django.core.files.storage import FileSystemStorage
+from django.utils.datastructures import SortedDict
+from django.template.loader import render_to_string
+from django.core.cache import cache
+from catalogue.utils import split_tags
+from django.utils.safestring import mark_safe
+from slughifi import slughifi
+
+from django.utils.translation import ugettext_lazy as _
+from newtagging import managers
+from os import path
+
+
+picture_storage = FileSystemStorage(location=path.join(settings.MEDIA_ROOT, 'pictures'), base_url=settings.MEDIA_URL + "pictures/")
+
+
+class Picture(models.Model):
+    """
+    Picture resource.
+
+    """
+    title       = models.CharField(_('title'), max_length=120)
+    slug        = models.SlugField(_('slug'), max_length=120, db_index=True, unique=True)
+    sort_key    = models.CharField(_('sort key'), max_length=120, db_index=True, editable=False)
+    created_at  = models.DateTimeField(_('creation date'), auto_now_add=True, db_index=True)
+    changed_at  = models.DateTimeField(_('creation date'), auto_now=True, db_index=True)
+    xml_file    = models.FileField('xml_file', upload_to="xml", storage=picture_storage)
+    image_file  = ImageField(_('image_file'), upload_to="images", storage=picture_storage)
+    objects     = models.Manager()
+    tagged      = managers.ModelTaggedItemManager(catalogue.models.Tag)
+    tags        = managers.TagDescriptor(catalogue.models.Tag)
+
+    class AlreadyExists(Exception):
+        pass
+
+    class Meta:
+        ordering = ('sort_key',)
+
+        verbose_name = _('picture')
+        verbose_name_plural = _('pictures')
+
+    def save(self, force_insert=False, force_update=False, reset_short_html=True, **kwargs):
+        from sortify import sortify
+
+        self.sort_key = sortify(self.title)
+
+        ret = super(Picture, self).save(force_insert, force_update)
+
+        if reset_short_html:
+            self.reset_short_html()
+
+        return ret
+
+    def __unicode__(self):
+        return self.title
+
+    @permalink
+    def get_absolute_url(self):
+        return ('picture.views.picture_detail', [self.slug])
+
+    @classmethod
+    def from_xml_file(cls, xml_file, image_file=None, overwrite=False):
+        """
+        Import xml and it's accompanying image file.
+        If image file is missing, it will be fetched by librarian.picture.ImageStore
+        which looks for an image file in the same directory the xml is, with extension matching
+        its mime type.
+        """
+        from sortify import sortify
+        from django.core.files import File
+        from librarian.picture import WLPicture, ImageStore
+        close_xml_file = False
+        close_image_file = False
+        # class SimpleImageStore(object):
+        #     def path(self_, slug, mime_type):
+        #         """Returns the image file. Ignores slug ad mime_type."""
+        #         return image_file
+
+        if image_file is not None and not isinstance(image_file, File):
+            image_file = File(open(image_file))
+            close_image_file = True
+
+        if not isinstance(xml_file, File):
+            xml_file = File(open(xml_file))
+            close_xml_file = True
+
+        try:
+            # use librarian to parse meta-data
+            picture_xml = WLPicture.from_file(xml_file,
+                                              image_store=ImageStore(picture_storage.path('images')))
+                    # image_store=SimpleImageStore
+
+            picture, created = Picture.objects.get_or_create(slug=picture_xml.slug)
+            if not created and not overwrite:
+                raise Picture.AlreadyExists('Picture %s already exists' % picture_xml.slug)
+
+            picture.title = picture_xml.picture_info.title
+
+            motif_tags = set()
+            for part in picture_xml.partiter():
+                for motif in part['themes']:
+                    tag, created = catalogue.models.Tag.objects.get_or_create(slug=slughifi(motif), category='theme')
+                    if created:
+                        tag.name = motif
+                        tag.sort_key = sortify(tag.name)
+                        tag.save()
+                    motif_tags.add(tag)
+
+            picture.tags = catalogue.models.Tag.tags_from_info(picture_xml.picture_info) + \
+                list(motif_tags)
+
+            if image_file is not None:
+                img = image_file
+            else:
+                img = picture_xml.image_file()
+
+            # FIXME: hardcoded extension
+            picture.image_file.save(path.basename(picture_xml.image_path), File(img))
+
+            picture.xml_file.save("%s.xml" % picture.slug, File(xml_file))
+            picture.save()
+        finally:
+            if close_xml_file:
+                xml_file.close()
+            if close_image_file:
+                image_file.close()
+        return picture
+
+    @classmethod
+    def picture_list(cls, filter=None):
+        """Generates a hierarchical listing of all pictures
+        Pictures are optionally filtered with a test function.
+        """
+
+        pics = cls.objects.all().order_by('sort_key')\
+            .only('title', 'slug', 'image_file')
+
+        if filter:
+            pics = pics.filter(filter).distinct()
+
+        pics_by_author = SortedDict()
+        orphans = []
+        for tag in catalogue.models.Tag.objects.filter(category='author'):
+            pics_by_author[tag] = []
+
+        for pic in pics:
+            authors = list(pic.tags.filter(category='author'))
+            if authors:
+                for author in authors:
+                    pics_by_author[author].append(pic)
+            else:
+                orphans.append(pic)
+
+        return pics_by_author, orphans
+
+    @property
+    def info(self):
+        if not hasattr(self, '_info'):
+            from librarian import dcparser
+            from librarian import picture
+            info = dcparser.parse(self.xml_file.path, picture.PictureInfo)
+            self._info = info
+        return self._info
+
+    def reset_short_html(self):
+        if self.id is None:
+            return
+
+        cache_key = "Picture.short_html/%d" % (self.id)
+        cache.delete(cache_key)
+
+    def short_html(self):
+        if self.id:
+            cache_key = "Picture.short_html/%d" % (self.id)
+            short_html = cache.get(cache_key)
+        else:
+            short_html = None
+
+        if short_html is not None:
+            return mark_safe(short_html)
+        else:
+            tags = self.tags.filter(category__in=('author', 'kind', 'epoch'))
+            tags = split_tags(tags)
+
+            short_html = unicode(render_to_string('picture/picture_short.html',
+                {'picture': self, 'tags': tags}))
+
+            if self.id:
+                cache.set(cache_key, short_html, catalogue.models.CACHE_FOREVER)
+            return mark_safe(short_html)
diff --git a/apps/picture/templates/admin/picture/picture/change_list.html b/apps/picture/templates/admin/picture/picture/change_list.html
new file mode 100755 (executable)
index 0000000..e150da5
--- /dev/null
@@ -0,0 +1,11 @@
+{% extends "admin/change_list.html" %}
+{% load i18n %}
+
+{% block content %}
+    <form action="{% url import_picture %}" method="post" enctype="multipart/form-data">
+        <p>XML: <input type="file" id="id_picture_xml_file" name="picture_xml_file" /><br/>
+            {% trans "Image" %}: <input type="file" id="id_picture_image_file" name="picture_image_file" /><br/>
+            <input type="submit" value="{% trans "Import picture" %}"/></p>
+    </form>
+    {{ block.super }}
+{% endblock content %}
\ No newline at end of file
diff --git a/apps/picture/tests/__init__.py b/apps/picture/tests/__init__.py
new file mode 100644 (file)
index 0000000..8817a1c
--- /dev/null
@@ -0,0 +1 @@
+from picture.tests.picture_import import *
diff --git a/apps/picture/tests/files/kandinsky-composition-viii.png b/apps/picture/tests/files/kandinsky-composition-viii.png
new file mode 100644 (file)
index 0000000..a809eb5
Binary files /dev/null and b/apps/picture/tests/files/kandinsky-composition-viii.png differ
diff --git a/apps/picture/tests/files/kandinsky-composition-viii.xml b/apps/picture/tests/files/kandinsky-composition-viii.xml
new file mode 100644 (file)
index 0000000..d9e79cb
--- /dev/null
@@ -0,0 +1,36 @@
+<picture>
+  <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
+    <rdf:Description rdf:about="http://wiki.wolnepodreczniki.pl/Lektury:Andersen/Brzydkie_kaczątko">
+      <dc:creator xml:lang="pl">Kandinsky, Vasily</dc:creator>
+      <dc:title xml:lang="la">composition 8</dc:title>
+      <dc:publisher xml:lang="pl">Fundacja Nowoczesna Polska</dc:publisher>
+      <dc:contributor.editor xml:lang="pl" xmlns:dc="http://purl.org/dc/elements/1.1/">Sekuła, Aleksandra</dc:contributor.editor>
+      <dc:contributor.editor xml:lang="pl" xmlns:dc="http://purl.org/dc/elements/1.1/">Kwiatkowska, Katarzyna</dc:contributor.editor>
+      <dc:contributor.technical_editor xml:lang="pl" xmlns:dc="http://purl.org/dc/elements/1.1/">Trzeciak, Weronika</dc:contributor.technical_editor>
+      <dc:subject.period xml:lang="pl">Modernizm</dc:subject.period>
+      <dc:subject.type xml:lang="pl">Obraz</dc:subject.type>
+      <dc:description xml:lang="pl">Publikacja zrealizowana w ramach projektu Wolne Lektury (http://wolnelektury.pl). Reprodukcja cyfrowa wykonana przez Bibliotekę Narodową z egzemplarza pochodzącego ze zbiorów BN.</dc:description>
+      <dc:description.dimensions xml:lang="pl">55 1/8 x 79 1/8 inches</dc:description.dimensions>
+      <dc:description.medium xml:lang="pl">Olej na płótnie</dc:description.medium>
+      <dc:identifier.url xml:lang="pl">http://wolnelektury.pl/katalog/obraz/kandinsky-composition-viii</dc:identifier.url>
+      <dc:source.URL xml:lang="pl">http://www.ibiblio.org/wm/paint/auth/kandinsky/kandinsky.comp-8.jpg</dc:source.URL>
+      <dc:source xml:lang="pl">Muzeum Narodowe, inw. 00000001.</dc:source>
+      <dc:rights xml:lang="pl">Domena publiczna - Vasily Kandinsky zm. ?</dc:rights>
+      <dc:date.pd xml:lang="pl">1940</dc:date.pd>
+      <dc:type>Image</dc:type>
+      <dc:format xml:lang="pl">image/png</dc:format>
+      <dc:format.dimensions xml:lang="pl">1090 x 755 px</dc:format.dimensions>
+      <dc:format.checksum.sha1 xml:lang="pl">122b590510ce70cc80e617557f82048ce20f1d7b</dc:format.checksum.sha1>
+      <dc:date xml:lang="pl">1923</dc:date>
+      <dc:language xml:lang="pl" xmlns:dc="http://purl.org/dc/elements/1.1/">eng</dc:language>
+    </rdf:Description>
+  </rdf:RDF>
+
+  <sem type="object" object="kosmos">
+    <div type="area" x1="17" y1="31" x2="275" y2="309"/>
+  </sem>
+  <sem type="theme" theme="nieporządek">
+    <div type="area" x1="300" y1="138" x2="976" y2="514"/>
+  </sem>
+</picture>
diff --git a/apps/picture/tests/picture_import.py b/apps/picture/tests/picture_import.py
new file mode 100644 (file)
index 0000000..91fb35f
--- /dev/null
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+from __future__ import with_statement
+
+from django.core.files.base import ContentFile, File
+from catalogue.test_utils import *
+from catalogue import models
+from librarian import WLURI
+from picture.models import Picture
+
+from nose.tools import raises
+import tempfile
+from os import unlink, path, makedirs
+
+
+class PictureTest(TestCase):
+    
+    def test_import(self):
+        picture = Picture.from_xml_file(path.join(path.dirname(__file__), "files/kandinsky-composition-viii.xml"))
+
+        motifs = set([tag.name for tag in picture.tags if tag.category == 'theme'])
+        assert motifs == set([u'nieporządek']), 'theme tags are wrong. %s' % motifs
+
+        picture.delete()
+
+    def test_import_with_explicit_image(self):
+        picture = Picture.from_xml_file(path.join(path.dirname(__file__), "files/kandinsky-composition-viii.xml"),
+                                        path.join(path.dirname(__file__), "files/kandinsky-composition-viii.png"))
+
+        picture.delete()
+        
diff --git a/apps/picture/views.py b/apps/picture/views.py
new file mode 100644 (file)
index 0000000..24457e2
--- /dev/null
@@ -0,0 +1,58 @@
+from picture.models import Picture
+from django.contrib.auth.decorators import permission_required
+from django.utils.datastructures import SortedDict
+from django.shortcuts import render_to_response, get_object_or_404
+from django.template import RequestContext
+
+
+def picture_list(request, filter=None, template_name='catalogue/picture_list.html'):
+    """ generates a listing of all books, optionally filtered with a test function """
+
+    pictures_by_author, orphans = Picture.picture_list()
+    books_nav = SortedDict()
+    for tag in pictures_by_author:
+        if pictures_by_author[tag]:
+            books_nav.setdefault(tag.sort_key[0], []).append(tag)
+
+            #    import pdb; pdb.set_trace()
+    return render_to_response(template_name, locals(),
+        context_instance=RequestContext(request))
+
+
+def picture_detail(request, picture):
+    picture = get_object_or_404(Picture, slug=picture)
+
+    categories = SortedDict()
+    for tag in picture.tags:
+        categories.setdefault(tag.category, []).append(tag)
+
+    picture_themes = []
+
+    return render_to_response("catalogue/picture_detail.html", locals(),
+                              context_instance=RequestContext(request))
+
+# =========
+# = Admin =
+# =========
+@permission_required('picture.add_picture')
+def import_picture(request):
+    """docstring for import_book"""
+    from django.http import HttpResponse
+    from picture.forms import PictureImportForm
+    from django.utils.translation import ugettext as _
+
+    import_form = PictureImportForm(request.POST, request.FILES)
+    if import_form.is_valid():
+        try:
+            import_form.save()
+        except:
+            import sys
+            import pprint
+            import traceback
+            info = sys.exc_info()
+            exception = pprint.pformat(info[1])
+            tb = '\n'.join(traceback.format_tb(info[2]))
+            return HttpResponse(_("An error occurred: %(exception)s\n\n%(tb)s") % {'exception':exception, 'tb':tb}, mimetype='text/plain')
+        return HttpResponse(_("Picture imported successfully"))
+    else:
+        return HttpResponse(_("Error importing file: %r") % import_form.errors)
index 93a13d0..c17ed07 100755 (executable)
@@ -47,7 +47,7 @@ LetterSpace=-1.0
 \setlength{\leftmargin}{0em}
 \setlength{\rightmargin}{0em}
 \setlength{\textheight}{24cm}
-\setlength{\textwidth}{17cm}
+\setlength{\textwidth}{17.5cm}
 
 
 \pagestyle{fancy}
@@ -98,7 +98,7 @@ LetterSpace=-1.0
         \end{flushright}
     \end{minipage}
 
-    \begin{longtable}{p{15cm} p{2cm}}
+    \begin{longtable}{p{9.5cm} p{5.5cm}r p{2cm}}
 
         <TeXML escape="1">
             {% book_tree_texml orphans books_by_parent %}
index bbfca99..485610d 100755 (executable)
@@ -2,23 +2,22 @@
 {% load i18n %}
 {% load reporting_stats catalogue_tags %}
 
-{% block title %}Statystyka w  WolneLektury.pl{% endblock %}
+{% block titleextra %}{% trans "Reports" %}{% endblock %}
 
 {% block bodyid %}reports-stats{% endblock %}
 
 
 {% block body %}
     <h1>Statystyka</h1>
-    {% search_form %}
 
-    <p><a href="{% url reporting_catalogue_pdf %}">Katalog biblioteki w formacie PDF.</a></p>
+    <div class="normal-text">
 
     <table class="stats">
         <tr><th>Utwory</th></tr>
-        <tr><td>Wszystkie utwory:</td><td>{% count_books_all %}</td></tr>
-        <tr><td>Utwory z własną treścią:</td><td>{% count_books_nonempty %}</td></tr>
-        <tr><td>Utwory bez własnej treści:</td><td>{% count_books_empty %}</td></tr>
+        <tr><td>Utwory:</td><td>{% count_books %}</td></tr>
         <tr><td>Niezależne książki:</td><td>{% count_books_root %}</td></tr>
+        <tr><td>Utwory nadrzędne:</td><td>{% count_books_parent %}</td></tr>
+        <tr><td>Wszystkie utwory:</td><td>{% count_books_all %}</td></tr>
 
         <tr><th>Media</th><th>Liczba</th><th>Rozmiar</th><th>Do wymiany</th></tr>
         {% for mt in media_types %}
                 <td>{{ mt.size|filesizeformat }}</td>
                 <td>{{ mt.deprecated }}
                     {% for m in mt.deprecated_files %}
-                        <br/><a href="{{ m.book.get_absolute_url }}">{{ m }}</a>
+                        <br/><a href="{{ m.book.get_absolute_url }}">{% book_title m.book %}: {{ m }}</a>
                     {% endfor %}
                 </td>
             </tr>
         {% endfor %}
     </table>
 
+    </div>
+
 {% endblock %}
index dceee00..6f20c08 100755 (executable)
@@ -49,11 +49,11 @@ def count_books_all():
     return Book.objects.all().count()
 
 @register_counter
-def count_books_nonempty():
+def count_books():
     return Book.objects.exclude(html_file='').count()
 
 @register_counter
-def count_books_empty():
+def count_books_parent():
     return Book.objects.filter(html_file='').count()
 
 @register_counter
diff --git a/apps/search/__init__.py b/apps/search/__init__.py
new file mode 100644 (file)
index 0000000..1451fa2
--- /dev/null
@@ -0,0 +1,3 @@
+import lucene
+
+from index import Index, Search, ReusableIndex, SearchResult, JVM, IndexChecker, IndexStore
diff --git a/apps/search/context_processors.py b/apps/search/context_processors.py
new file mode 100644 (file)
index 0000000..c54525a
--- /dev/null
@@ -0,0 +1,7 @@
+
+from catalogue.forms import SearchForm
+from django.core.urlresolvers import reverse
+
+
+def search_form(request):
+    return { 'search_form': SearchForm(reverse('search.views.hint'), request.GET) }
diff --git a/apps/search/index.py b/apps/search/index.py
new file mode 100644 (file)
index 0000000..cc478ee
--- /dev/null
@@ -0,0 +1,1174 @@
+# -*- coding: utf-8 -*-
+
+from django.conf import settings
+from lucene import SimpleFSDirectory, IndexWriter, CheckIndex, \
+    File, Field, Integer, \
+    NumericField, Version, Document, JavaError, IndexSearcher, \
+    QueryParser, PerFieldAnalyzerWrapper, \
+    SimpleAnalyzer, PolishAnalyzer, ArrayList, \
+    KeywordAnalyzer, NumericRangeQuery, NumericRangeFilter, BooleanQuery, \
+    BlockJoinQuery, BlockJoinCollector, Filter, TermsFilter, ChainedFilter, \
+    HashSet, BooleanClause, Term, CharTermAttribute, \
+    PhraseQuery, MultiPhraseQuery, StringReader, TermQuery, \
+    FuzzyQuery, FuzzyTermEnum, PrefixTermEnum, Sort, Integer, \
+    SimpleHTMLFormatter, Highlighter, QueryScorer, TokenSources, TextFragment, \
+    BooleanFilter, TermsFilter, FilterClause, QueryWrapperFilter, \
+    initVM, CLASSPATH, JArray, JavaError
+    # KeywordAnalyzer
+
+# Initialize jvm
+JVM = initVM(CLASSPATH)
+
+import sys
+import os
+import re
+import errno
+from librarian import dcparser
+from librarian.parser import WLDocument
+import catalogue.models
+from multiprocessing.pool import ThreadPool
+from threading import current_thread
+import atexit
+import traceback
+
+
+class WLAnalyzer(PerFieldAnalyzerWrapper):
+    def __init__(self):
+        polish = PolishAnalyzer(Version.LUCENE_34)
+        #        polish_gap.setPositionIncrementGap(999)
+
+        simple = SimpleAnalyzer(Version.LUCENE_34)
+        #        simple_gap.setPositionIncrementGap(999)
+
+        keyword = KeywordAnalyzer(Version.LUCENE_34)
+
+        # not sure if needed: there's NOT_ANALYZED meaning basically the same
+
+        PerFieldAnalyzerWrapper.__init__(self, polish)
+
+        self.addAnalyzer("tags", simple)
+        self.addAnalyzer("technical_editors", simple)
+        self.addAnalyzer("editors", simple)
+        self.addAnalyzer("url", keyword)
+        self.addAnalyzer("source_url", keyword)
+        self.addAnalyzer("source_name", simple)
+        self.addAnalyzer("publisher", simple)
+        self.addAnalyzer("authors", simple)
+        self.addAnalyzer("title", simple)
+
+        self.addAnalyzer("is_book", keyword)
+        # shouldn't the title have two forms? _pl and simple?
+
+        self.addAnalyzer("themes", simple)
+        self.addAnalyzer("themes_pl", polish)
+
+        self.addAnalyzer("tag_name", simple)
+        self.addAnalyzer("tag_name_pl", polish)
+
+        self.addAnalyzer("translators", simple)
+
+        self.addAnalyzer("KEYWORD", keyword)
+        self.addAnalyzer("SIMPLE", simple)
+        self.addAnalyzer("POLISH", polish)
+
+
+class IndexStore(object):
+    """
+    Provides access to search index.
+
+    self.store - lucene index directory
+    """
+    def __init__(self):
+        self.make_index_dir()
+        self.store = SimpleFSDirectory(File(settings.SEARCH_INDEX))
+
+    def make_index_dir(self):
+        try:
+            os.makedirs(settings.SEARCH_INDEX)
+        except OSError as exc:
+            if exc.errno == errno.EEXIST:
+                pass
+            else: raise
+
+
+class IndexChecker(IndexStore):
+    def __init__(self):
+        IndexStore.__init__(self)
+
+    def check(self):
+        checker = CheckIndex(self.store)
+        status = checker.checkIndex()
+        return status
+
+
+class Snippets(object):
+    """
+    This class manages snippet files for indexed object (book)
+    the snippets are concatenated together, and their positions and
+    lengths are kept in lucene index fields.
+    """
+    SNIPPET_DIR = "snippets"
+
+    def __init__(self, book_id):
+        try:
+            os.makedirs(os.path.join(settings.SEARCH_INDEX, self.SNIPPET_DIR))
+        except OSError as exc:
+            if exc.errno == errno.EEXIST:
+                pass
+            else: raise
+        self.book_id = book_id
+        self.file = None
+
+    def open(self, mode='r'):
+        """
+        Open the snippet file. Call .close() afterwards.
+        """
+        if not 'b' in mode:
+            mode += 'b'
+        self.file = open(os.path.join(settings.SEARCH_INDEX, self.SNIPPET_DIR, str(self.book_id)), mode)
+        self.position = 0
+        return self
+
+    def add(self, snippet):
+        """
+        Append a snippet (unicode) to the snippet file.
+        Return a (position, length) tuple
+        """
+        txt = snippet.encode('utf-8')
+        l = len(txt)
+        self.file.write(txt)
+        pos = (self.position, l)
+        self.position += l
+        return pos
+
+    def get(self, pos):
+        """
+        Given a tuple of (position, length) return an unicode
+        of the snippet stored there.
+        """
+        self.file.seek(pos[0], 0)
+        txt = self.file.read(pos[1]).decode('utf-8')
+        return txt
+
+    def close(self):
+        """Close snippet file"""
+        self.file.close()
+
+
+class BaseIndex(IndexStore):
+    """
+    Base index class.
+    Provides basic operations on index: opening, closing, optimizing.
+    """
+    def __init__(self, analyzer=None):
+        super(BaseIndex, self).__init__()
+        self.index = None
+        if not analyzer:
+            analyzer = WLAnalyzer()
+        self.analyzer = analyzer
+
+    def open(self, analyzer=None):
+        if self.index:
+            raise Exception("Index is already opened")
+        self.index = IndexWriter(self.store, self.analyzer,\
+                                 IndexWriter.MaxFieldLength.LIMITED)
+        return self.index
+
+    def optimize(self):
+        self.index.optimize()
+
+    def close(self):
+        try:
+            self.index.optimize()
+        except JavaError, je:
+            print "Error during optimize phase, check index: %s" % je
+
+        self.index.close()
+        self.index = None
+
+    def __enter__(self):
+        self.open()
+        return self
+
+    def __exit__(self, type, value, tb):
+        self.close()
+
+
+class Index(BaseIndex):
+    """
+    Class indexing books.
+    """
+    def __init__(self, analyzer=None):
+        super(Index, self).__init__(analyzer)
+
+    def index_tags(self):
+        """
+        Re-index global tag list.
+        Removes all tags from index, then index them again.
+        Indexed fields include: id, name (with and without polish stems), category
+        """
+        q = NumericRangeQuery.newIntRange("tag_id", 0, Integer.MAX_VALUE, True, True)
+        self.index.deleteDocuments(q)
+
+        for tag in catalogue.models.Tag.objects.all():
+            doc = Document()
+            doc.add(NumericField("tag_id", Field.Store.YES, True).setIntValue(tag.id))
+            doc.add(Field("tag_name", tag.name, Field.Store.NO, Field.Index.ANALYZED))
+            doc.add(Field("tag_name_pl", tag.name, Field.Store.NO, Field.Index.ANALYZED))
+            doc.add(Field("tag_category", tag.category, Field.Store.NO, Field.Index.NOT_ANALYZED))
+            self.index.addDocument(doc)
+
+    def create_book_doc(self, book):
+        """
+        Create a lucene document referring book id.
+        """
+        doc = Document()
+        doc.add(NumericField("book_id", Field.Store.YES, True).setIntValue(book.id))
+        if book.parent is not None:
+            doc.add(NumericField("parent_id", Field.Store.YES, True).setIntValue(book.parent.id))
+        return doc
+
+    def remove_book(self, book):
+        """Removes a book from search index.
+        book - Book instance."""
+        q = NumericRangeQuery.newIntRange("book_id", book.id, book.id, True, True)
+        self.index.deleteDocuments(q)
+
+    def index_book(self, book, book_info=None, overwrite=True):
+        """
+        Indexes the book.
+        Creates a lucene document for extracted metadata
+        and calls self.index_content() to index the contents of the book.
+        """
+        if overwrite:
+            self.remove_book(book)
+
+        book_doc = self.create_book_doc(book)
+        meta_fields = self.extract_metadata(book, book_info)
+        for f in meta_fields.values():
+            if isinstance(f, list) or isinstance(f, tuple):
+                for elem in f:
+                    book_doc.add(elem)
+            else:
+                book_doc.add(f)
+
+        self.index.addDocument(book_doc)
+        del book_doc
+
+        self.index_content(book, book_fields=[meta_fields['title'], meta_fields['authors']])
+
+    master_tags = [
+        'opowiadanie',
+        'powiesc',
+        'dramat_wierszowany_l',
+        'dramat_wierszowany_lp',
+        'dramat_wspolczesny', 'liryka_l', 'liryka_lp',
+        'wywiad'
+        ]
+
+    skip_header_tags = ['autor_utworu', 'nazwa_utworu', 'dzielo_nadrzedne']
+
+    def extract_metadata(self, book, book_info=None):
+        """
+        Extract metadata from book and returns a map of fields keyed by fieldname
+        """
+        fields = {}
+
+        if book_info is None:
+            book_info = dcparser.parse(open(book.xml_file.path))
+
+        fields['slug'] = Field("slug", book.slug, Field.Store.NO, Field.Index.ANALYZED_NO_NORMS)
+        fields['tags'] = self.add_gaps([Field("tags", t.name, Field.Store.NO, Field.Index.ANALYZED) for t in book.tags], 'tags')
+        fields['is_book'] = Field("is_book", 'true', Field.Store.NO, Field.Index.NOT_ANALYZED)
+
+        # validator, name
+        for field in dcparser.BookInfo.FIELDS:
+            if hasattr(book_info, field.name):
+                if not getattr(book_info, field.name):
+                    continue
+                # since no type information is available, we use validator
+                type_indicator = field.validator
+                if type_indicator == dcparser.as_unicode:
+                    s = getattr(book_info, field.name)
+                    if field.multiple:
+                        s = ', '.join(s)
+                    try:
+                        fields[field.name] = Field(field.name, s, Field.Store.NO, Field.Index.ANALYZED)
+                    except JavaError as je:
+                        raise Exception("failed to add field: %s = '%s', %s(%s)" % (field.name, s, je.message, je.args))
+                elif type_indicator == dcparser.as_person:
+                    p = getattr(book_info, field.name)
+                    if isinstance(p, dcparser.Person):
+                        persons = unicode(p)
+                    else:
+                        persons = ', '.join(map(unicode, p))
+                    fields[field.name] = Field(field.name, persons, Field.Store.NO, Field.Index.ANALYZED)
+                elif type_indicator == dcparser.as_date:
+                    dt = getattr(book_info, field.name)
+                    fields[field.name] = Field(field.name, "%04d%02d%02d" %\
+                                               (dt.year, dt.month, dt.day), Field.Store.NO, Field.Index.NOT_ANALYZED)
+
+        return fields
+
+    def add_gaps(self, fields, fieldname):
+        """
+        Interposes a list of fields with gap-fields, which are indexed spaces and returns it.
+        This allows for doing phrase queries which do not overlap the gaps (when slop is 0).
+        """
+        def gap():
+            while True:
+                yield Field(fieldname, ' ', Field.Store.NO, Field.Index.NOT_ANALYZED)
+        return reduce(lambda a, b: a + b, zip(fields, gap()))[0:-1]
+
+    def get_master(self, root):
+        """
+        Returns the first master tag from an etree.
+        """
+        for master in root.iter():
+            if master.tag in self.master_tags:
+                return master
+
+    def index_content(self, book, book_fields=[]):
+        """
+        Walks the book XML and extract content from it.
+        Adds parts for each header tag and for each fragment.
+        """
+        wld = WLDocument.from_file(book.xml_file.path, parse_dublincore=False)
+        root = wld.edoc.getroot()
+
+        master = self.get_master(root)
+        if master is None:
+            return []
+
+        def walker(node):
+            yield node, None
+            for child in list(node):
+                for b, e in walker(child):
+                    yield b, e
+            yield None, node
+            return
+
+        def fix_format(text):
+            return re.sub("(?m)/$", "", text)
+
+        def add_part(snippets, **fields):
+            doc = self.create_book_doc(book)
+            for f in book_fields:
+                doc.add(f)
+
+            doc.add(NumericField('header_index', Field.Store.YES, True).setIntValue(fields["header_index"]))
+            doc.add(NumericField("header_span", Field.Store.YES, True)\
+                    .setIntValue('header_span' in fields and fields['header_span'] or 1))
+            doc.add(Field('header_type', fields["header_type"], Field.Store.YES, Field.Index.NOT_ANALYZED))
+
+            doc.add(Field('content', fields["content"], Field.Store.NO, Field.Index.ANALYZED, \
+                          Field.TermVector.WITH_POSITIONS_OFFSETS))
+
+            snip_pos = snippets.add(fields["content"])
+            doc.add(NumericField("snippets_position", Field.Store.YES, True).setIntValue(snip_pos[0]))
+            doc.add(NumericField("snippets_length", Field.Store.YES, True).setIntValue(snip_pos[1]))
+
+            if 'fragment_anchor' in fields:
+                doc.add(Field("fragment_anchor", fields['fragment_anchor'],
+                              Field.Store.YES, Field.Index.NOT_ANALYZED))
+
+            if 'themes' in fields:
+                themes, themes_pl = zip(*[
+                    (Field("themes", theme, Field.Store.YES, Field.Index.ANALYZED, Field.TermVector.WITH_POSITIONS),
+                     Field("themes_pl", theme, Field.Store.NO, Field.Index.ANALYZED, Field.TermVector.WITH_POSITIONS))
+                     for theme in fields['themes']])
+
+                themes = self.add_gaps(themes, 'themes')
+                themes_pl = self.add_gaps(themes_pl, 'themes_pl')
+
+                for t in themes:
+                    doc.add(t)
+                for t in themes_pl:
+                    doc.add(t)
+
+            return doc
+
+        def give_me_utf8(s):
+            if isinstance(s, unicode):
+                return s.encode('utf-8')
+            else:
+                return s
+
+        fragments = {}
+        snippets = Snippets(book.id).open('w')
+        try:
+            for header, position in zip(list(master), range(len(master))):
+
+                if header.tag in self.skip_header_tags:
+                    continue
+
+                content = u' '.join([t for t in header.itertext()])
+                content = fix_format(content)
+
+                doc = add_part(snippets, header_index=position, header_type=header.tag, content=content)
+
+                self.index.addDocument(doc)
+
+                for start, end in walker(header):
+                    if start is not None and start.tag == 'begin':
+                        fid = start.attrib['id'][1:]
+                        fragments[fid] = {'content': [], 'themes': [], 'start_section': position, 'start_header': header.tag}
+                        fragments[fid]['content'].append(start.tail)
+                    elif start is not None and start.tag == 'motyw':
+                        fid = start.attrib['id'][1:]
+                        if start.text is not None:
+                            fragments[fid]['themes'] += map(str.strip, map(give_me_utf8, start.text.split(',')))
+                        fragments[fid]['content'].append(start.tail)
+                    elif start is not None and start.tag == 'end':
+                        fid = start.attrib['id'][1:]
+                        if fid not in fragments:
+                            continue  # a broken <end> node, skip it
+                        frag = fragments[fid]
+                        if frag['themes'] == []:
+                            continue  # empty themes list.
+                        del fragments[fid]
+
+                        def jstr(l):
+                            return u' '.join(map(
+                                lambda x: x == None and u'(none)' or unicode(x),
+                                l))
+
+                        doc = add_part(snippets,
+                                       header_type=frag['start_header'],
+                                       header_index=frag['start_section'],
+                                       header_span=position - frag['start_section'] + 1,
+                                       fragment_anchor=fid,
+                                       content=u' '.join(filter(lambda s: s is not None, frag['content'])),
+                                       themes=frag['themes'])
+
+                        self.index.addDocument(doc)
+                    elif start is not None:
+                        for frag in fragments.values():
+                            frag['content'].append(start.text)
+                    elif end is not None:
+                        for frag in fragments.values():
+                            frag['content'].append(end.tail)
+        finally:
+            snippets.close()
+
+
+def log_exception_wrapper(f):
+    def _wrap(*a):
+        try:
+            f(*a)
+        except Exception, e:
+            print("Error in indexing thread: %s" % e)
+            traceback.print_exc()
+            raise e
+    return _wrap
+
+
+class ReusableIndex(Index):
+    """
+    Works like index, but does not close/optimize Lucene index
+    until program exit (uses atexit hook).
+    This is usefull for importbooks command.
+
+    if you cannot rely on atexit, use ReusableIndex.close_reusable() yourself.
+    """
+    index = None
+
+    def open(self, analyzer=None, threads=4):
+        if ReusableIndex.index is not None:
+            self.index = ReusableIndex.index
+        else:
+            print("opening index")
+            Index.open(self, analyzer)
+            ReusableIndex.index = self.index
+            atexit.register(ReusableIndex.close_reusable)
+
+    # def index_book(self, *args, **kw):
+    #     job = ReusableIndex.pool.apply_async(log_exception_wrapper(Index.index_book), (self,) + args, kw)
+    #     ReusableIndex.pool_jobs.append(job)
+
+    @staticmethod
+    def close_reusable():
+        if ReusableIndex.index is not None:
+            ReusableIndex.index.optimize()
+            ReusableIndex.index.close()
+            ReusableIndex.index = None
+
+    def close(self):
+        pass
+
+
+class JoinSearch(object):
+    """
+    This mixin could be used to handle block join queries.
+    (currently unused)
+    """
+    def __init__(self, *args, **kw):
+        super(JoinSearch, self).__init__(*args, **kw)
+
+    def wrapjoins(self, query, fields=[]):
+        """
+        This functions modifies the query in a recursive way,
+        so Term and Phrase Queries contained, which match
+        provided fields are wrapped in a BlockJoinQuery,
+        and so delegated to children documents.
+        """
+        if BooleanQuery.instance_(query):
+            qs = BooleanQuery.cast_(query)
+            for clause in qs:
+                clause = BooleanClause.cast_(clause)
+                clause.setQuery(self.wrapjoins(clause.getQuery(), fields))
+            return qs
+        else:
+            termset = HashSet()
+            query.extractTerms(termset)
+            for t in termset:
+                t = Term.cast_(t)
+                if t.field() not in fields:
+                    return query
+            return BlockJoinQuery(query, self.parent_filter,
+                                  BlockJoinQuery.ScoreMode.Total)
+
+    def bsearch(self, query, max_results=50):
+        q = self.query(query)
+        bjq = BlockJoinQuery(q, self.parent_filter, BlockJoinQuery.ScoreMode.Avg)
+
+        tops = self.searcher.search(bjq, max_results)
+        bks = []
+        for found in tops.scoreDocs:
+            doc = self.searcher.doc(found.doc)
+            bks.append(catalogue.models.Book.objects.get(id=doc.get("book_id")))
+        return (bks, tops.totalHits)
+
+
+class SearchResult(object):
+    def __init__(self, searcher, scoreDocs, score=None, how_found=None, snippets=None):
+        if score:
+            self.score = score
+        else:
+            self.score = scoreDocs.score
+
+        self._hits = []
+        self.hits = None  # processed hits
+
+        stored = searcher.doc(scoreDocs.doc)
+        self.book_id = int(stored.get("book_id"))
+
+        header_type = stored.get("header_type")
+        if not header_type:
+            return
+
+        sec = (header_type, int(stored.get("header_index")))
+        header_span = stored.get('header_span')
+        header_span = header_span is not None and int(header_span) or 1
+
+        fragment = stored.get("fragment_anchor")
+
+        hit = (sec + (header_span,), fragment, scoreDocs.score, {'how_found': how_found, 'snippets': snippets and [snippets] or []})
+
+        self._hits.append(hit)
+
+    def merge(self, other):
+        if self.book_id != other.book_id:
+            raise ValueError("this search result is or book %d; tried to merge with %d" % (self.book_id, other.book_id))
+        self._hits += other._hits
+        if other.score > self.score:
+            self.score = other.score
+        return self
+
+    def get_book(self):
+        return catalogue.models.Book.objects.get(id=self.book_id)
+
+    book = property(get_book)
+
+    def process_hits(self):
+        POSITION = 0
+        FRAGMENT = 1
+        POSITION_INDEX = 1
+        POSITION_SPAN = 2
+        SCORE = 2
+        OTHER = 3
+
+        # to sections and fragments
+        frags = filter(lambda r: r[FRAGMENT] is not None, self._hits)
+        sect = filter(lambda r: r[FRAGMENT] is None, self._hits)
+        sect = filter(lambda s: 0 == len(filter(
+            lambda f: s[POSITION][POSITION_INDEX] >= f[POSITION][POSITION_INDEX]
+            and s[POSITION][POSITION_INDEX] < f[POSITION][POSITION_INDEX] + f[POSITION][POSITION_SPAN],
+            frags)), sect)
+
+        hits = []
+
+        # remove duplicate fragments
+        fragments = {}
+        for f in frags:
+            fid = f[FRAGMENT]
+            if fid in fragments:
+                if fragments[fid][SCORE] >= f[SCORE]:
+                    continue
+            fragments[fid] = f
+        frags = fragments.values()
+
+        # remove duplicate sections
+        sections = {}
+
+        for s in sect:
+            si = s[POSITION][POSITION_INDEX]
+            # skip existing
+            if si in sections:
+                if sections[si]['score'] >= s[SCORE]:
+                    continue
+
+            m = {'score': s[SCORE],
+                 'section_number': s[POSITION][POSITION_INDEX] + 1,
+                 }
+            m.update(s[OTHER])
+            sections[si] = m
+
+        hits = sections.values()
+
+        for f in frags:
+            frag = catalogue.models.Fragment.objects.get(anchor=f[FRAGMENT])
+            m = {'score': f[SCORE],
+                 'fragment': frag,
+                 'themes': frag.tags.filter(category='theme')
+                 }
+            m.update(f[OTHER])
+            hits.append(m)
+
+        hits.sort(lambda a, b: cmp(a['score'], b['score']), reverse=True)
+
+        self.hits = hits
+
+        return self
+
+    def __unicode__(self):
+        return u'SearchResult(book_id=%d, score=%d)' % (self.book_id, self.score)
+
+    @staticmethod
+    def aggregate(*result_lists):
+        books = {}
+        for rl in result_lists:
+            for r in rl:
+                if r.book_id in books:
+                    books[r.book_id].merge(r)
+                    #print(u"already have one with score %f, and this one has score %f" % (books[book.id][0], found.score))
+                else:
+                    books[r.book_id] = r
+        return books.values()
+
+    def __cmp__(self, other):
+        return cmp(self.score, other.score)
+
+
+class Hint(object):
+    """
+    Given some hint information (information we already know about)
+    our search target - like author, title (specific book), epoch, genre, kind
+    we can narrow down search using filters.
+    """
+    def __init__(self, search):
+        """
+        Accepts a Searcher instance.
+        """
+        self.search = search
+        self.book_tags = {}
+        self.part_tags = []
+        self._books = []
+
+    def books(self, *books):
+        """
+        Give a hint that we search these books.
+        """
+        self._books = books
+
+    def tags(self, tags):
+        """
+        Give a hint that these Tag objects (a list of)
+        is necessary.
+        """
+        for t in tags:
+            if t.category in ['author', 'title', 'epoch', 'genre', 'kind']:
+                lst = self.book_tags.get(t.category, [])
+                lst.append(t)
+                self.book_tags[t.category] = lst
+            if t.category in ['theme', 'theme_pl']:
+                self.part_tags.append(t)
+
+    def tag_filter(self, tags, field='tags'):
+        """
+        Given a lsit of tags and an optional field (but they are normally in tags field)
+        returns a filter accepting only books with specific tags.
+        """
+        q = BooleanQuery()
+
+        for tag in tags:
+            toks = self.search.get_tokens(tag.name, field=field)
+            tag_phrase = PhraseQuery()
+            for tok in toks:
+                tag_phrase.add(Term(field, tok))
+            q.add(BooleanClause(tag_phrase, BooleanClause.Occur.MUST))
+
+        return QueryWrapperFilter(q)
+
+    def book_filter(self):
+        """
+        Filters using book tags (all tag kinds except a theme)
+        """
+        tags = reduce(lambda a, b: a + b, self.book_tags.values(), [])
+        if tags:
+            return self.tag_filter(tags)
+        else:
+            return None
+
+    def part_filter(self):
+        """
+        This filter can be used to look for book parts.
+        It filters on book id and/or themes.
+        """
+        fs = []
+        if self.part_tags:
+            fs.append(self.tag_filter(self.part_tags, field='themes'))
+
+        if self._books != []:
+            bf = BooleanFilter()
+            for b in self._books:
+                id_filter = NumericRangeFilter.newIntRange('book_id', b.id, b.id, True, True)
+                bf.add(FilterClause(id_filter, BooleanClause.Occur.SHOULD))
+            fs.append(bf)
+
+        return Search.chain_filters(fs)
+
+    def should_search_for_book(self):
+        return self._books == []
+
+    def just_search_in(self, all):
+        """Holds logic to figure out which indexes should be search, when we have some hinst already"""
+        some = []
+        for field in all:
+            if field == 'authors' and 'author' in self.book_tags:
+                continue
+            if field == 'title' and self._books != []:
+                continue
+            if (field == 'themes' or field == 'themes_pl') and self.part_tags:
+                continue
+            some.append(field)
+        return some
+
+
+class Search(IndexStore):
+    """
+    Search facilities.
+    """
+    def __init__(self, default_field="content"):
+        IndexStore.__init__(self)
+        self.analyzer = WLAnalyzer()  # PolishAnalyzer(Version.LUCENE_34)
+        # self.analyzer = WLAnalyzer()
+        self.searcher = IndexSearcher(self.store, True)
+        self.parser = QueryParser(Version.LUCENE_34, default_field,
+                                  self.analyzer)
+
+        self.parent_filter = TermsFilter()
+        self.parent_filter.addTerm(Term("is_book", "true"))
+
+    def query(self, query):
+        """Parse query in default Lucene Syntax. (for humans)
+        """
+        return self.parser.parse(query)
+
+    def simple_search(self, query, max_results=50):
+        """Runs a query for books using lucene syntax. (for humans)
+        Returns (books, total_hits)
+        """
+
+        tops = self.searcher.search(self.query(query), max_results)
+        bks = []
+        for found in tops.scoreDocs:
+            doc = self.searcher.doc(found.doc)
+            bks.append(catalogue.models.Book.objects.get(id=doc.get("book_id")))
+        return (bks, tops.totalHits)
+
+    def get_tokens(self, searched, field='content'):
+        """returns tokens analyzed by a proper (for a field) analyzer
+        argument can be: StringReader, string/unicode, or tokens. In the last case
+        they will just be returned (so we can reuse tokens, if we don't change the analyzer)
+        """
+        if isinstance(searched, str) or isinstance(searched, unicode):
+            searched = StringReader(searched)
+        elif isinstance(searched, list):
+            return searched
+
+        searched.reset()
+        tokens = self.analyzer.reusableTokenStream(field, searched)
+        toks = []
+        while tokens.incrementToken():
+            cta = tokens.getAttribute(CharTermAttribute.class_)
+            toks.append(cta.toString())
+        return toks
+
+    def fuzziness(self, fuzzy):
+        """Helper method to sanitize fuzziness"""
+        if not fuzzy:
+            return None
+        if isinstance(fuzzy, float) and fuzzy > 0.0 and fuzzy <= 1.0:
+            return fuzzy
+        else:
+            return 0.5
+
+    def make_phrase(self, tokens, field='content', slop=2, fuzzy=False):
+        """
+        Return a PhraseQuery with a series of tokens.
+        """
+        if fuzzy:
+            phrase = MultiPhraseQuery()
+            for t in tokens:
+                term = Term(field, t)
+                fuzzterm = FuzzyTermEnum(self.searcher.getIndexReader(), term, self.fuzziness(fuzzy))
+                fuzzterms = []
+
+                while True:
+                    #                    print("fuzz %s" % unicode(fuzzterm.term()).encode('utf-8'))
+                    ft = fuzzterm.term()
+                    if ft:
+                        fuzzterms.append(ft)
+                    if not fuzzterm.next(): break
+                if fuzzterms:
+                    phrase.add(JArray('object')(fuzzterms, Term))
+                else:
+                    phrase.add(term)
+        else:
+            phrase = PhraseQuery()
+            phrase.setSlop(slop)
+            for t in tokens:
+                term = Term(field, t)
+                phrase.add(term)
+        return phrase
+
+    def make_term_query(self, tokens, field='content', modal=BooleanClause.Occur.SHOULD, fuzzy=False):
+        """
+        Returns term queries joined by boolean query.
+        modal - applies to boolean query
+        fuzzy - should the query by fuzzy.
+        """
+        q = BooleanQuery()
+        for t in tokens:
+            term = Term(field, t)
+            if fuzzy:
+                term = FuzzyQuery(term, self.fuzziness(fuzzy))
+            else:
+                term = TermQuery(term)
+            q.add(BooleanClause(term, modal))
+        return q
+
+    # def content_query(self, query):
+    #     return BlockJoinQuery(query, self.parent_filter,
+    #                           BlockJoinQuery.ScoreMode.Total)
+
+    def search_perfect_book(self, searched, max_results=20, fuzzy=False, hint=None):
+        """
+        Search for perfect book matches. Just see if the query matches with some author or title,
+        taking hints into account.
+        """
+        fields_to_search = ['authors', 'title']
+        only_in = None
+        if hint:
+            if not hint.should_search_for_book():
+                return []
+            fields_to_search = hint.just_search_in(fields_to_search)
+            only_in = hint.book_filter()
+
+        qrys = [self.make_phrase(self.get_tokens(searched, field=fld), field=fld, fuzzy=fuzzy) for fld in fields_to_search]
+
+        books = []
+        for q in qrys:
+            top = self.searcher.search(q,
+                self.chain_filters([only_in, self.term_filter(Term('is_book', 'true'))]),
+                max_results)
+            for found in top.scoreDocs:
+                books.append(SearchResult(self.searcher, found))
+        return books
+
+    def search_book(self, searched, max_results=20, fuzzy=False, hint=None):
+        fields_to_search = ['tags', 'authors', 'title']
+
+        only_in = None
+        if hint:
+            if not hint.should_search_for_book():
+                return []
+            fields_to_search = hint.just_search_in(fields_to_search)
+            only_in = hint.book_filter()
+
+        tokens = self.get_tokens(searched, field='SIMPLE')
+
+        q = BooleanQuery()
+
+        for fld in fields_to_search:
+            q.add(BooleanClause(self.make_term_query(tokens, field=fld,
+                                fuzzy=fuzzy), BooleanClause.Occur.SHOULD))
+
+        books = []
+        top = self.searcher.search(q,
+                                   self.chain_filters([only_in, self.term_filter(Term('is_book', 'true'))]),
+            max_results)
+        for found in top.scoreDocs:
+            books.append(SearchResult(self.searcher, found))
+
+        return books
+
+    def search_perfect_parts(self, searched, max_results=20, fuzzy=False, hint=None):
+        """
+        Search for book parts which containt a phrase perfectly matching (with a slop of 2, default for make_phrase())
+        some part/fragment of the book.
+        """
+        qrys = [self.make_phrase(self.get_tokens(searched), field=fld, fuzzy=fuzzy) for fld in ['content']]
+
+        flt = None
+        if hint:
+            flt = hint.part_filter()
+
+        books = []
+        for q in qrys:
+            top = self.searcher.search(q,
+                                       self.chain_filters([self.term_filter(Term('is_book', 'true'), inverse=True),
+                                                           flt]),
+                                       max_results)
+            for found in top.scoreDocs:
+                books.append(SearchResult(self.searcher, found, snippets=self.get_snippets(found, q)))
+
+        return books
+
+    def search_everywhere(self, searched, max_results=20, fuzzy=False, hint=None):
+        """
+        Tries to use search terms to match different fields of book (or its parts).
+        E.g. one word can be an author survey, another be a part of the title, and the rest
+        are some words from third chapter.
+        """
+        books = []
+        only_in = None
+
+        if hint:
+            only_in = hint.part_filter()
+
+        # content only query : themes x content
+        q = BooleanQuery()
+
+        tokens_pl = self.get_tokens(searched, field='content')
+        tokens = self.get_tokens(searched, field='SIMPLE')
+
+        # only search in themes when we do not already filter by themes
+        if hint is None or hint.just_search_in(['themes']) != []:
+            q.add(BooleanClause(self.make_term_query(tokens_pl, field='themes_pl',
+                                                     fuzzy=fuzzy), BooleanClause.Occur.MUST))
+
+        q.add(BooleanClause(self.make_term_query(tokens_pl, field='content',
+                                                 fuzzy=fuzzy), BooleanClause.Occur.SHOULD))
+
+        topDocs = self.searcher.search(q, only_in, max_results)
+        for found in topDocs.scoreDocs:
+            books.append(SearchResult(self.searcher, found))
+            print "* %s theme x content: %s" % (searched, books[-1]._hits)
+
+        # query themes/content x author/title/tags
+        q = BooleanQuery()
+        in_content = BooleanQuery()
+        in_meta = BooleanQuery()
+
+        for fld in ['themes_pl', 'content']:
+            in_content.add(BooleanClause(self.make_term_query(tokens_pl, field=fld, fuzzy=False), BooleanClause.Occur.SHOULD))
+
+        for fld in ['tags', 'authors', 'title']:
+            in_meta.add(BooleanClause(self.make_term_query(tokens, field=fld, fuzzy=False), BooleanClause.Occur.SHOULD))
+
+        q.add(BooleanClause(in_content, BooleanClause.Occur.MUST))
+        q.add(BooleanClause(in_meta, BooleanClause.Occur.SHOULD))
+
+        topDocs = self.searcher.search(q, only_in, max_results)
+        for found in topDocs.scoreDocs:
+            books.append(SearchResult(self.searcher, found))
+            print "* %s scatter search: %s" % (searched, books[-1]._hits)
+
+        return books
+
+    # def multisearch(self, query, max_results=50):
+    #     """
+    #     Search strategy:
+    #     - (phrase) OR -> content
+    #                   -> title
+    #                   -> authors
+    #     - (keywords)  -> authors
+    #                   -> motyw
+    #                   -> tags
+    #                   -> content
+    #     """
+        # queryreader = StringReader(query)
+        # tokens = self.get_tokens(queryreader)
+
+        # top_level = BooleanQuery()
+        # Should = BooleanClause.Occur.SHOULD
+
+        # phrase_level = BooleanQuery()
+        # phrase_level.setBoost(1.3)
+
+        # p_content = self.make_phrase(tokens, joined=True)
+        # p_title = self.make_phrase(tokens, 'title')
+        # p_author = self.make_phrase(tokens, 'author')
+
+        # phrase_level.add(BooleanClause(p_content, Should))
+        # phrase_level.add(BooleanClause(p_title, Should))
+        # phrase_level.add(BooleanClause(p_author, Should))
+
+        # kw_level = BooleanQuery()
+
+        # kw_level.add(self.make_term_query(tokens, 'author'), Should)
+        # j_themes = self.make_term_query(tokens, 'themes', joined=True)
+        # kw_level.add(j_themes, Should)
+        # kw_level.add(self.make_term_query(tokens, 'tags'), Should)
+        # j_con = self.make_term_query(tokens, joined=True)
+        # kw_level.add(j_con, Should)
+
+        # top_level.add(BooleanClause(phrase_level, Should))
+        # top_level.add(BooleanClause(kw_level, Should))
+
+        # return None
+
+    def get_snippets(self, scoreDoc, query, field='content'):
+        """
+        Returns a snippet for found scoreDoc.
+        """
+        htmlFormatter = SimpleHTMLFormatter()
+        highlighter = Highlighter(htmlFormatter, QueryScorer(query))
+
+        stored = self.searcher.doc(scoreDoc.doc)
+
+        # locate content.
+        snippets = Snippets(stored.get('book_id')).open()
+        try:
+            text = snippets.get((int(stored.get('snippets_position')),
+                                 int(stored.get('snippets_length'))))
+        finally:
+            snippets.close()
+
+        tokenStream = TokenSources.getAnyTokenStream(self.searcher.getIndexReader(), scoreDoc.doc, field, self.analyzer)
+        #  highlighter.getBestTextFragments(tokenStream, text, False, 10)
+        snip = highlighter.getBestFragments(tokenStream, text, 3, "...")
+
+        return snip
+
+    @staticmethod
+    def enum_to_array(enum):
+        """
+        Converts a lucene TermEnum to array of Terms, suitable for
+        addition to queries
+        """
+        terms = []
+
+        while True:
+            t = enum.term()
+            if t:
+                terms.append(t)
+            if not enum.next(): break
+
+        if terms:
+            return JArray('object')(terms, Term)
+
+    def search_tags(self, query, filter=None, max_results=40):
+        """
+        Search for Tag objects using query.
+        """
+        tops = self.searcher.search(query, filter, max_results)
+
+        tags = []
+        for found in tops.scoreDocs:
+            doc = self.searcher.doc(found.doc)
+            tag = catalogue.models.Tag.objects.get(id=doc.get("tag_id"))
+            tags.append(tag)
+            print "%s (%d) -> %f" % (tag, tag.id, found.score)
+
+        return tags
+
+    def search_books(self, query, filter=None, max_results=10):
+        """
+        Searches for Book objects using query
+        """
+        bks = []
+        tops = self.searcher.search(query, filter, max_results)
+        for found in tops.scoreDocs:
+            doc = self.searcher.doc(found.doc)
+            bks.append(catalogue.models.Book.objects.get(id=doc.get("book_id")))
+        return bks
+
+    def create_prefix_phrase(self, toks, field):
+        q = MultiPhraseQuery()
+        for i in range(len(toks)):
+            t = Term(field, toks[i])
+            if i == len(toks) - 1:
+                pterms = Search.enum_to_array(PrefixTermEnum(self.searcher.getIndexReader(), t))
+                if pterms:
+                    q.add(pterms)
+                else:
+                    q.add(t)
+            else:
+                q.add(t)
+        return q
+
+    @staticmethod
+    def term_filter(term, inverse=False):
+        only_term = TermsFilter()
+        only_term.addTerm(term)
+
+        if inverse:
+            neg = BooleanFilter()
+            neg.add(FilterClause(only_term, BooleanClause.Occur.MUST_NOT))
+            only_term = neg
+
+        return only_term
+
+    def hint_tags(self, string, max_results=50):
+        """
+        Return auto-complete hints for tags
+        using prefix search.
+        """
+        toks = self.get_tokens(string, field='SIMPLE')
+        top = BooleanQuery()
+
+        for field in ['tag_name', 'tag_name_pl']:
+            q = self.create_prefix_phrase(toks, field)
+            top.add(BooleanClause(q, BooleanClause.Occur.SHOULD))
+
+        no_book_cat = self.term_filter(Term("tag_category", "book"), inverse=True)
+
+        return self.search_tags(top, no_book_cat, max_results=max_results)
+
+    def hint_books(self, string, max_results=50):
+        """
+        Returns auto-complete hints for book titles
+        Because we do not index 'pseudo' title-tags.
+        Prefix search.
+        """
+        toks = self.get_tokens(string, field='SIMPLE')
+
+        q = self.create_prefix_phrase(toks, 'title')
+
+        return self.search_books(q, self.term_filter(Term("is_book", "true")), max_results=max_results)
+
+    @staticmethod
+    def chain_filters(filters, op=ChainedFilter.AND):
+        """
+        Chains a filter list together
+        """
+        filters = filter(lambda x: x is not None, filters)
+        if not filters:
+            return None
+        chf = ChainedFilter(JArray('object')(filters, Filter), op)
+        return chf
+
+    def filtered_categories(self, tags):
+        """
+        Return a list of tag categories, present in tags list.
+        """
+        cats = {}
+        for t in tags:
+            cats[t.category] = True
+        return cats.keys()
+
+    def hint(self):
+        return Hint(self)
diff --git a/apps/search/management/__init__.py b/apps/search/management/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/apps/search/management/commands/__init__.py b/apps/search/management/commands/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/apps/search/management/commands/checkindex.py b/apps/search/management/commands/checkindex.py
new file mode 100644 (file)
index 0000000..b910277
--- /dev/null
@@ -0,0 +1,22 @@
+
+from django.core.management.base import BaseCommand
+from search import IndexChecker
+
+class Command(BaseCommand):
+    help = 'Check Lucene search index'
+    args = ''
+
+    def handle(self, *args, **opts):
+        checker = IndexChecker()
+        status = checker.check()
+        if status.clean:
+            print "No problems found."
+        else:
+            if status.missingSegments:
+                print "Unable to locate."
+            print "Number of bad segments: %d / %d (max segment name is %d)" % \
+                (status.numBadSegments, status.numSegments, status.maxSegmentName)
+            print "Total lost documents (due to bad segments) %d" % status.totLoseDocCount
+            if not status.validCounter:
+                print "Segment counter is not valid."
+        
diff --git a/apps/search/management/commands/optimizeindex.py b/apps/search/management/commands/optimizeindex.py
new file mode 100644 (file)
index 0000000..a8a4cf9
--- /dev/null
@@ -0,0 +1,15 @@
+
+from django.core.management.base import BaseCommand
+from search import Index
+
+class Command(BaseCommand):
+    help = 'Optimize Lucene search index'
+    args = ''
+
+    def handle(self, *args, **opts):
+        index = Index()
+        index.open()
+        try:
+            index.optimize()
+        finally:
+            index.close()
diff --git a/apps/search/management/commands/reindex.py b/apps/search/management/commands/reindex.py
new file mode 100755 (executable)
index 0000000..bce4708
--- /dev/null
@@ -0,0 +1,25 @@
+from django.core.management.base import BaseCommand
+
+class Command(BaseCommand):
+    help = 'Reindex everything.'
+    args = ''
+
+    def handle(self, *args, **opts):
+        from catalogue.models import Book
+        import search
+        idx = search.ReusableIndex()
+        idx.open()
+
+        if args:
+            books = []
+            for a in args:
+                books += Book.objects.filter(slug=a).all()
+        else:
+            books = Book.objects.all()
+            
+        for b in books:
+            print b.title
+            idx.index_book(b, None)
+        print 'Reindexing tags.'
+        idx.index_tags()
+        idx.close()
diff --git a/apps/search/tests/__init__.py b/apps/search/tests/__init__.py
new file mode 100644 (file)
index 0000000..403c290
--- /dev/null
@@ -0,0 +1 @@
+from search.tests.index import *
diff --git a/apps/search/tests/files/fraszka-do-anusie.xml b/apps/search/tests/files/fraszka-do-anusie.xml
new file mode 100755 (executable)
index 0000000..3bbda15
--- /dev/null
@@ -0,0 +1,49 @@
+<?xml version='1.0' encoding='utf-8'?>
+<utwor>
+  <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
+<rdf:Description rdf:about="http://wiki.wolnepodreczniki.pl/index.php?title=Lektury:S%C4%99p-Szarzy%C5%84ski/Rytmy/Fraszka_do_Anusie">
+<dc:creator xml:lang="pl">Sęp Szarzyński, Mikołaj</dc:creator>
+<dc:title xml:lang="pl">Fraszka do Anusie</dc:title>
+<dc:contributor.editor xml:lang="pl">Sekuła, Aleksandra</dc:contributor.editor>
+<dc:contributor.technical_editor xml:lang="pl">Sutkowska, Olga</dc:contributor.technical_editor>
+<dc:publisher xml:lang="pl">Fundacja Nowoczesna Polska</dc:publisher>
+<dc:subject.period xml:lang="pl">Barok</dc:subject.period>
+<dc:subject.type xml:lang="pl">Liryka</dc:subject.type>
+<dc:subject.genre xml:lang="pl">Fraszka</dc:subject.genre>
+<dc:description xml:lang="pl">Publikacja zrealizowana w ramach projektu Wolne Lektury (http://wolnelektury.pl). Reprodukcja cyfrowa wykonana przez Bibliotekę Narodową z egzemplarza pochodzącego ze zbiorów BN.</dc:description>
+<dc:identifier.url xml:lang="pl">http://wolnelektury.pl/katalog/lektura/fraszka-do-anusie</dc:identifier.url>
+<dc:source.URL xml:lang="pl">http://www.polona.pl/Content/8759</dc:source.URL>
+<dc:source xml:lang="pl">Szarzyński Sęp, Mikołaj (ca 1550-1581), Rytmy abo Wiersze polskie w wyborze, E. Wende, Warszawa, 1914</dc:source>
+<dc:rights xml:lang="pl">Domena publiczna - Mikołaj Sęp Szarzyński zm. 1581</dc:rights>
+<dc:date.pd xml:lang="pl">1581</dc:date.pd>
+<dc:format xml:lang="pl">xml</dc:format>
+<dc:type xml:lang="pl">text</dc:type>
+<dc:type xml:lang="en">text</dc:type>
+<dc:date xml:lang="pl">2008-12-29</dc:date>
+<dc:audience xml:lang="pl">L</dc:audience>
+<dc:audience xml:lang="pl">L</dc:audience>
+<dc:language xml:lang="pl">pol</dc:language>
+</rdf:Description>
+</rdf:RDF>
+  <liryka_l>
+
+<autor_utworu>Mikołaj Sęp Szarzyński</autor_utworu>
+
+<nazwa_utworu>Fraszka do Anusie</nazwa_utworu>
+
+
+
+<strofa><begin id="b1230084410751"/><motyw id="m1230084410751">Kochanek, Łzy, Miłość, Oko, Serce, Wzrok</motyw>Jeśli oczu hamować swoich nie umiały/
+Leśnych krynic boginie, aby nie płakały,/
+Gdy baczyły<pe><slowo_obce>baczyły</slowo_obce> --- tu: zobaczyły, patrzyły na.</pe> przy studni Narcyza pięknego,/
+A on umarł prze miłość oblicza swojego;/
+Jeśli nieśmiertelnym stanom żałość rozkazuje,/
+Gdy niebaczna fortuna co niesłusznie psuje:</strofa>
+
+<strofa>Jakoż ja mam hamować, by na lice moje/
+Z oczu smutnych żałośne nie płynęły zdroje?/
+Jako serce powściągać, aby nie wzdychało/
+I od ciężkiej żałości omdlewać nie miało?<end id="e1230084410751"/></strofa>
+
+</liryka_l>
+</utwor>
diff --git a/apps/search/tests/files/fraszki.xml b/apps/search/tests/files/fraszki.xml
new file mode 100755 (executable)
index 0000000..edb29ab
--- /dev/null
@@ -0,0 +1,27 @@
+<?xml version='1.0' encoding='utf-8'?>
+<utwor>
+<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
+<rdf:Description rdf:about="">
+<dc:creator xml:lang="pl">Kochanowski, Jan</dc:creator>
+<dc:title xml:lang="pl">Fraszki</dc:title>
+<dc:relation.hasPart xml:lang="pl">http://wolnelektury.pl/katalog/lektura/fraszka-do-anusie</dc:relation.hasPart>
+
+<dc:publisher xml:lang="pl">Fundacja Nowoczesna Polska</dc:publisher>
+<dc:subject.period xml:lang="pl">Renesans</dc:subject.period>
+<dc:subject.type xml:lang="pl">Liryka</dc:subject.type>
+<dc:subject.genre xml:lang="pl">Fraszka</dc:subject.genre>
+
+<dc:description xml:lang="pl"></dc:description>
+<dc:identifier.url xml:lang="pl">http://wolnelektury.pl/lektura/fraszki</dc:identifier.url>
+<dc:source xml:lang="pl"></dc:source>
+<dc:rights xml:lang="pl">Domena publiczna - Jan Kochanowski zm. 1584</dc:rights>
+<dc:date.pd xml:lang="pl">1584</dc:date.pd>
+<dc:format xml:lang="pl">xml</dc:format>
+<dc:type xml:lang="pl">text</dc:type>
+
+<dc:type xml:lang="en">text</dc:type>
+<dc:date xml:lang="pl">2008-11-12</dc:date>
+<dc:language xml:lang="pl">pol</dc:language>
+</rdf:Description>
+</rdf:RDF>
+</utwor>
diff --git a/apps/search/tests/index.py b/apps/search/tests/index.py
new file mode 100644 (file)
index 0000000..ee376a8
--- /dev/null
@@ -0,0 +1,77 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+from django.conf import settings
+from search import Index, Search, IndexStore, JVM, SearchResult
+from catalogue import models
+from catalogue.test_utils import WLTestCase
+from lucene import PolishAnalyzer, Version
+#from nose.tools import raises
+from os import path
+
+
+class BookSearchTests(WLTestCase):
+    def setUp(self):
+        JVM.attachCurrentThread()
+        WLTestCase.setUp(self)
+        settings.SEARCH_INDEX = path.join(settings.MEDIA_ROOT, 'search')
+
+        txt = path.join(path.dirname(__file__), 'files/fraszka-do-anusie.xml')
+        self.book = models.Book.from_xml_file(txt)
+
+        index = Index()
+        index.open()
+        try:
+            index.index_book(self.book)
+        except:
+            index.close()
+
+        self.search = Search()
+
+    def test_search_perfect_book_author(self):
+        books = self.search.search_perfect_book("sęp szarzyński")
+        assert len(books) == 1
+        assert books[0].book_id == self.book.id
+
+    def test_search_perfect_book_title(self):
+        books = self.search.search_perfect_book("fraszka anusie")
+        assert len(books) == 1
+        assert books[0].book_id == self.book.id
+
+    def test_search_perfect_parts(self):
+        books = self.search.search_perfect_parts("Jakoż hamować")
+        assert len(books) == 2
+        for b in books:
+            b.book_id == self.book.id
+        a = SearchResult.aggregate(books)
+        # just one fragment hit.
+        assert len(filter(lambda x: x[1], a[0].hits)) == 1
+        print a[0].process_hits()
+
+    def test_search_perfect_author_title(self):
+        books = self.search.search_perfect_book("szarzyński anusie")
+        assert books == []
+
+        books = self.search.search_book("szarzyński anusie")
+        assert len(books) == 1
+
+        books = self.search.search_book("szarzyński fraszka")
+        assert len(books) == 1
+
+    def test_search_everywhere(self):
+        books = self.search.search_everywhere("szarzyński kochanek")
+        print 'szarzyński kochanek %s' % [b.hits for b in books]
+
+        books = self.search.search_everywhere("szarzyński narcyz")
+        print 'szarzyński narcyz %s' % [b.hits for b in books]
+
+        books = self.search.search_everywhere("anusie narcyz")
+        print 'anusie narcyz %s' % [b.hits for b in books]
+
+        # theme content cross
+        books = self.search.search_everywhere("wzrok  boginie")
+        print 'wzrok boginie %s' % [b.hits for b in books]
+
+        books = self.search.search_everywhere("anusie płynęły zdroje")
+        print 'anusie płynęły zdroje %s' % [b.hits for b in books]
diff --git a/apps/search/urls.py b/apps/search/urls.py
new file mode 100644 (file)
index 0000000..607f094
--- /dev/null
@@ -0,0 +1,11 @@
+# -*- coding: utf-8 -*-
+# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('search.views',
+    url(r'^$', 'main', name='search'),
+    url(r'^hint/$', 'hint'),
+)
+
diff --git a/apps/search/views.py b/apps/search/views.py
new file mode 100644 (file)
index 0000000..d056a18
--- /dev/null
@@ -0,0 +1,165 @@
+# -*- coding: utf-8 -*-
+
+from django.conf import settings
+from django.shortcuts import render_to_response, get_object_or_404
+from django.template import RequestContext
+from django.contrib.auth.decorators import login_required
+from django.views.decorators import cache
+from django.http import HttpResponse, HttpResponseRedirect, Http404, HttpResponsePermanentRedirect
+from django.utils.translation import ugettext as _
+
+from catalogue.utils import get_random_hash
+from catalogue.models import Book, Tag, Fragment
+from catalogue.fields import dumps
+from catalogue.views import JSONResponse
+from search import Search, JVM, SearchResult
+from lucene import StringReader
+from suggest.forms import PublishingSuggestForm
+
+import enchant
+
+dictionary = enchant.Dict('pl_PL')
+
+
+def match_word_re(word):
+    if 'sqlite' in settings.DATABASES['default']['ENGINE']:
+        return r"\b%s\b" % word
+    elif 'mysql' in settings.DATABASES['default']['ENGINE']:
+        return "[[:<:]]%s[[:>:]]" % word
+
+
+def did_you_mean(query, tokens):
+    change = {}
+    for t in tokens:
+        print("%s ok? %s, sug: %s" % (t, dictionary.check(t), dictionary.suggest(t)))
+        authors = Tag.objects.filter(category='author', name__iregex=match_word_re(t))
+        if len(authors) > 0:
+            continue
+        
+        if not dictionary.check(t):
+            try:
+                change[t] = dictionary.suggest(t)[0]
+            except IndexError:
+                pass
+
+    if change == {}:
+        return None
+
+    for frm, to in change.items():
+        query = query.replace(frm, to)
+
+    return query
+
+
+def hint(request):
+    prefix = request.GET.get('term', '')
+    if len(prefix) < 2:
+        return JSONResponse([])
+    JVM.attachCurrentThread()
+    s = Search()
+
+    hint = s.hint()
+    try:
+        tags = request.GET.get('tags', '')
+        hint.tags(Tag.get_tag_list(tags))
+    except:
+        pass
+
+    # tagi beda ograniczac tutaj
+    # ale tagi moga byc na ksiazce i na fragmentach
+    # jezeli tagi dot tylko ksiazki, to wazne zeby te nowe byly w tej samej ksiazce
+    # jesli zas dotycza themes, to wazne, zeby byly w tym samym fragmencie.
+
+    tags = s.hint_tags(prefix)
+    books = s.hint_books(prefix)
+
+    # TODO DODAC TU HINTY
+
+    return JSONResponse(
+        [{'label': t.name,
+          'category': _(t.category),
+          'id': t.id,
+          'url': t.get_absolute_url()}
+          for t in tags] + \
+          [{'label': b.title,
+            'category': _('book'),
+            'id': b.id,
+            'url': b.get_absolute_url()}
+            for b in books])
+
+
+def main(request):
+    results = {}
+    JVM.attachCurrentThread()  # where to put this?
+    srch = Search()
+
+    results = None
+    query = None
+    fuzzy = False
+
+    if 'q' in request.GET:
+        tags = request.GET.get('tags', '')
+        query = request.GET['q']
+        book_id = request.GET.get('book', None)
+        book = None
+        if book_id is not None:
+            book = get_object_or_404(Book, id=book_id)
+
+        hint = srch.hint()
+        try:
+            tag_list = Tag.get_tag_list(tags)
+        except:
+            tag_list = []
+
+        if len(query) < 2:
+            return render_to_response('catalogue/search_too_short.html', {'tags': tag_list, 'prefix': query},
+                                      context_instance=RequestContext(request))
+
+        hint.tags(tag_list)
+        if book:
+            hint.books(book)
+
+        toks = StringReader(query)
+        fuzzy = 'fuzzy' in request.GET
+        if fuzzy:
+            fuzzy = 0.7
+
+        results = SearchResult.aggregate(srch.search_perfect_book(toks, fuzzy=fuzzy, hint=hint),
+                                         srch.search_book(toks, fuzzy=fuzzy, hint=hint),
+                                         srch.search_perfect_parts(toks, fuzzy=fuzzy, hint=hint),
+                                         srch.search_everywhere(toks, fuzzy=fuzzy, hint=hint))
+
+        for r in results:
+            r.process_hits()
+
+        results.sort(reverse=True)
+
+        for r in results:
+            print "-----"
+            for h in r.hits:
+                print "- %s" % h
+
+                # Did you mean?
+        suggestion = did_you_mean(query, srch.get_tokens(toks, field="SIMPLE"))
+
+        if len(results) == 1:
+            if len(results[0].hits) == 0:
+                return HttpResponseRedirect(results[0].book.get_absolute_url())
+            elif len(results[0].hits) == 1 and results[0].hits[0] is not None:
+                frag = Fragment.objects.get(anchor=results[0].hits[0])
+                return HttpResponseRedirect(frag.get_absolute_url())
+        elif len(results) == 0:
+            form = PublishingSuggestForm(initial={"books": query + ", "})
+            return render_to_response('catalogue/search_no_hits.html',
+                                      {'tags': tag_list,
+                                       'prefix': query,
+                                       "form": form,
+                                       'did_you_mean': suggestion},
+                context_instance=RequestContext(request))
+
+        return render_to_response('catalogue/search_multiple_hits.html',
+                                  {'tags': tag_list,
+                                   'prefix': query,
+                                   'results': results,
+                                   'did_you_mean': suggestion},
+            context_instance=RequestContext(request))
index d91c8f4..1e0d2e5 100644 (file)
@@ -9,24 +9,17 @@ from django.utils.translation import ugettext_lazy as _
 from django.template.loader import render_to_string
 from PIL import Image
 
-from sorl.thumbnail.fields import ImageWithThumbnailsField
 from sponsors.fields import JSONField
 from django.core.files.base import ContentFile
 
-THUMB_WIDTH=120
-THUMB_HEIGHT=120
+THUMB_WIDTH = 120
+THUMB_HEIGHT = 120
+
 
 class Sponsor(models.Model):
     name = models.CharField(_('name'), max_length=120)
     _description = models.CharField(_('description'), blank=True, max_length=255)
-    logo = ImageWithThumbnailsField(
-        _('logo'),
-        upload_to='sponsorzy/sponsor/logo',
-        thumbnail={
-            'size': (THUMB_WIDTH, THUMB_HEIGHT),
-            'extension': 'png',
-            'options': ['pad', 'detail'],
-        })
+    logo = models.ImageField(_('logo'), upload_to='sponsorzy/sponsor/logo')
     url = models.URLField(_('url'), blank=True, verify_exists=False)
 
     def __unicode__(self):
@@ -65,10 +58,21 @@ class SponsorPage(models.Model):
         for column in self.get_sponsors_value():
             sponsor_ids.extend(column['sponsors'])
         sponsors = Sponsor.objects.in_bulk(sponsor_ids)
-        sprite = Image.new('RGBA', (THUMB_WIDTH, len(sponsors)*THUMB_HEIGHT))
+        sprite = Image.new('RGBA', (THUMB_WIDTH, len(sponsors) * THUMB_HEIGHT))
         for i, sponsor_id in enumerate(sponsor_ids):
-            simg = Image.open(sponsors[sponsor_id].logo.thumbnail.dest)
-            sprite.paste(simg, (0, i*THUMB_HEIGHT))
+            simg = Image.open(sponsors[sponsor_id].logo.path)
+            if simg.size[0] > THUMB_WIDTH or simg.size[1] > THUMB_HEIGHT:
+                size = (
+                    min(THUMB_WIDTH, 
+                        simg.size[0] * THUMB_HEIGHT / simg.size[1]),
+                    min(THUMB_HEIGHT,
+                        simg.size[1] * THUMB_WIDTH / simg.size[0])
+                )
+                simg = simg.resize(size, Image.ANTIALIAS)
+            sprite.paste(simg, (
+                    (THUMB_WIDTH - simg.size[0]) / 2,
+                    i * THUMB_HEIGHT + (THUMB_HEIGHT - simg.size[1]) / 2,
+                    ))
         imgstr = StringIO()
         sprite.save(imgstr, 'png')
 
diff --git a/apps/sponsors/processors.py b/apps/sponsors/processors.py
deleted file mode 100644 (file)
index 112241d..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-# -*- coding: utf-8 -*-
-# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
-# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
-#
-from PIL import Image, ImageFilter, ImageChops
-
-
-def add_padding(image, requested_size, opts):
-    if 'pad' in opts:
-        padded_image = Image.new('RGBA', requested_size, '#fff')
-        width, height = image.size
-        requested_width, requested_height = requested_size
-        print 'whatever'
-        padded_image.paste(image, (0, (requested_height - height) / 2))
-        return padded_image
-    return image
-
-add_padding.valid_options = ('pad',)
index 1277bd2..fe19d30 100644 (file)
@@ -66,3 +66,8 @@
     background-color: #EEE;
     cursor: default;
 }
+
+.sponsors-sponsor img {
+    max-width: 120px;
+    max-height: 120px;
+}
index e4b30bb..fc13873 100644 (file)
@@ -23,7 +23,7 @@ class SponsorPageWidget(forms.Textarea):
 
     def render(self, name, value, attrs=None):
         output = [super(SponsorPageWidget, self).render(name, value, attrs)]
-        sponsors = [(unicode(obj), obj.pk, obj.logo.thumbnail) for obj in models.Sponsor.objects.all()]
+        sponsors = [(unicode(obj), obj.pk, obj.logo.url) for obj in models.Sponsor.objects.all()]
         sponsors_js = ', '.join('{name: "%s", id: %d, image: "%s"}' % sponsor for sponsor in sponsors)
         output.append(u'<script type="text/javascript">addEvent(window, "load", function(e) {')
         # TODO: "id_" is hard-coded here. This should instead use the correct
index 14cec03..b7c614f 100644 (file)
@@ -9,14 +9,49 @@ from django.core.validators import email_re
 from django.utils.translation import ugettext_lazy as _
 from django.utils.translation import ugettext
 
-
-from suggest.models import PublishingSuggestion
+from suggest.models import PublishingSuggestion, Suggestion
 
 
 class SuggestForm(forms.Form):
     contact = forms.CharField(label=_('Contact'), max_length=120, required=False)
     description = forms.CharField(label=_('Description'), widget=forms.Textarea, required=True)
 
+    def save(self, request):
+        contact = self.cleaned_data['contact']
+        description = self.cleaned_data['description']
+
+        suggestion = Suggestion(contact=contact,
+            description=description, ip=request.META['REMOTE_ADDR'])
+        if request.user.is_authenticated():
+            suggestion.user = request.user
+        suggestion.save()
+
+        mail_managers(u'Nowa sugestia na stronie WolneLektury.pl', u'''\
+Zgłoszono nową sugestię w serwisie WolneLektury.pl.
+http://%(site)s%(url)s
+
+Użytkownik: %(user)s
+Kontakt: %(contact)s
+
+%(description)s''' % {
+            'site': Site.objects.get_current().domain,
+            'url': reverse('admin:suggest_suggestion_change', args=[suggestion.id]),
+            'user': str(request.user) if request.user.is_authenticated() else '',
+            'contact': contact,
+            'description': description,
+            }, fail_silently=True)
+
+        if email_re.match(contact):
+            send_mail(u'[WolneLektury] ' + _(u'Thank you for your suggestion.'),
+                    _(u"""\
+Thank you for your comment on WolneLektury.pl.
+The suggestion has been referred to the project coordinator.""") +
+u"""
+
+-- 
+""" + _(u'''Message sent automatically. Please do not reply.'''),
+                    'no-reply@wolnelektury.pl', [contact], fail_silently=True)
+
 
 class PublishingSuggestForm(forms.Form):
     contact = forms.CharField(label=_('Contact'), max_length=120, required=False)
index a6ed283..3e71000 100755 (executable)
@@ -1,14 +1,16 @@
 {% load i18n %}
-<h2>{% trans "Didn't find a book? Make a suggestion." %}</h2>
+<h1>{% trans "Didn't find a book? Make a suggestion." %}</h1>
+
 <form id='suggest-publishing-form' action="{% url suggest_publishing %}" method="post" accept-charset="utf-8" class="cuteform">
+{% csrf_token %}
 <ol>
-    <li><span class="error">{{ pubsuggest_form.contact.errors }}</span><label for="id_contact">{{ pubsuggest_form.contact.label }}</label> {{ pubsuggest_form.contact }}</li>
+    <li><span class="error">{{ form.contact.errors }}</span><label for="id_contact">{{ form.contact.label }}</label> {{ form.contact }}</li>
 
     <li>{% trans "I'd like to find in WolneLektury.pl these…" %}</li>
 
-    <li><span class="error">{{ pubsuggest_form.books.errors }}</span><label for="id_books">{{ pubsuggest_form.books.label }}:</label> {{ pubsuggest_form.books }}</li>
+    <li><span class="error">{{ form.books.errors }}</span><label for="id_books">{{ form.books.label }}:</label> {{ form.books }}</li>
 
-    <li><span class="error">{{ pubsuggest_form.audiobooks.errors }}</span><label for="id_audiobooks">{{ pubsuggest_form.audiobooks.label }}:</label> {{ pubsuggest_form.audiobooks }}</li>
+    <li><span class="error">{{ form.audiobooks.errors }}</span><label for="id_audiobooks">{{ form.audiobooks.label }}:</label> {{ form.audiobooks }}</li>
 
     <li><input type="submit" value="{% trans "Send report" %}"/></li>
 </ol>
diff --git a/apps/suggest/templates/publishing_suggest_full.html b/apps/suggest/templates/publishing_suggest_full.html
deleted file mode 100755 (executable)
index c5d8c28..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-{% extends "base.html" %}
-{% load i18n %}
-
-{% block title %}Sugestia do planu wydawniczego w WolneLektury.pl{% endblock %}
-
-{% block metadescription %}Sugestia do planu wydawniczego.{% endblock %}
-
-{% block bodyid %}suggest-publishing{% endblock %}
-
-{% block body %}
-    <form action="{% url search %}" method="get" accept-charset="utf-8" id="search-form">
-        <p>{{ form.q }} <input type="submit" value="{% trans "Search" %}" /> <strong>{% trans "or" %}</strong> <a href="{% url main_page %}">{% trans "return to main page" %}</a></p>
-    </form>
-
-    <div id="books-list">
-    </div>
-
-    <div class="column-right block-form">
-        {% include "publishing_suggest.html" %}
-        {% if response_data.message %}
-            <p>{{ response_data.message }}</p>
-        {% endif %}
-    </div>
-{% endblock %}
diff --git a/apps/suggest/templates/suggest.html b/apps/suggest/templates/suggest.html
deleted file mode 100644 (file)
index c7fdd81..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-{% load i18n %}
-<h2>{% trans "Report a bug or suggestion" %}</h2>
-<form id='suggest-form' action="{% url suggest.views.report %}" method="post" accept-charset="utf-8" class="cuteform">
-<ol>
-    <li><label for="id_contact">{{ form.contact.label }}</label> {{ form.contact }}</li>
-       <li><label for="id_description">{{ form.description.label }}</label> {{ form.description }}</li>
-    <li><input type="submit" value="{% trans "Send report" %}"/></li>
-</ol>
-</form>
\ No newline at end of file
index b769e49..ae4ac15 100644 (file)
@@ -3,21 +3,10 @@
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 from django.conf.urls.defaults import *
-from django.views.generic.simple import direct_to_template
-from suggest.forms import SuggestForm, PublishingSuggestForm
-from suggest.views import PublishingSuggestionFormView
+from suggest import views
 
 urlpatterns = patterns('',
-    url(r'^$', 'django.views.generic.simple.direct_to_template',
-        {'template': 'suggest.html', 'extra_context': {'form': SuggestForm }}, name='suggest'),
-    url(r'^wyslij/$', 'suggest.views.report', name='report'),
-
-    #url(r'^plan/$', 'suggest.views.publishing', name='suggest_publishing'),
-    url(r'^plan/$', PublishingSuggestionFormView(), name='suggest_publishing'),
-    #url(r'^plan_block/$', 'django.views.generic.simple.direct_to_template',
-    #    {'template': 'publishing_suggest.html', 
-    #            'extra_context': {'pubsuggest_form': PublishingSuggestForm }},
-    #    name='suggest_publishing'),
-    #url(r'^plan/wyslij/$', 'suggest.views.publishing_commit', name='suggest_publishing_commit'),
+    url(r'^$', views.SuggestionFormView(), name='suggest'),
+    url(r'^plan/$', views.PublishingSuggestionFormView(), name='suggest_publishing'),
 )
 
index 24ee12c..15b65f2 100644 (file)
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
-from django.core.mail import send_mail, mail_managers
-from django.core.urlresolvers import reverse
-from django.core.validators import email_re
-from django.http import HttpResponse, HttpResponseRedirect
-from django.utils.translation import ugettext as _
-from django.views.decorators import cache
-from django.views.decorators.http import require_POST
-from django.contrib.sites.models import Site
-from django.shortcuts import render_to_response
-from django.template import RequestContext
+from django.utils.translation import ugettext_lazy as _
 
-from catalogue.forms import SearchForm
+from ajaxable.utils import AjaxableFormView
 from suggest import forms
 from suggest.models import Suggestion, PublishingSuggestion
 
 
-# FIXME - shouldn't be in catalogue
-from catalogue.views import LazyEncoder
-
-
-class AjaxableFormView(object):
-    formClass = None
-    template = None
-    ajax_template = None
-    formname = None
-
-    def __call__(self, request):
-        """
-            A view displaying a form, or JSON if `ajax' GET param is set.
-        """
-        ajax = request.GET.get('ajax', False)
-        if request.method == "POST":
-            form = self.formClass(request.POST)
-            if form.is_valid():
-                form.save(request)
-                response_data = {'success': True, 'message': _('Report was sent successfully.')}
-            else:
-                response_data = {'success': False, 'errors': form.errors}
-            if ajax:
-                return HttpResponse(LazyEncoder(ensure_ascii=False).encode(response_data))
-        else:
-            form = self.formClass()
-            response_data = None
-
-        template = self.ajax_template if ajax else self.template
-        return render_to_response(template, {
-                self.formname: form, 
-                "form": SearchForm(),
-                "response_data": response_data,
-            },
-            context_instance=RequestContext(request))
-
-
 class PublishingSuggestionFormView(AjaxableFormView):
-    formClass = forms.PublishingSuggestForm
-    ajax_template = "publishing_suggest.html"
-    template = "publishing_suggest_full.html"
-    formname = "pubsuggest_form"
-
-
-@require_POST
-@cache.never_cache
-def report(request):
-    suggest_form = forms.SuggestForm(request.POST)
-    if suggest_form.is_valid():
-        contact = suggest_form.cleaned_data['contact']
-        description = suggest_form.cleaned_data['description']
-
-        suggestion = Suggestion(contact=contact,
-            description=description, ip=request.META['REMOTE_ADDR'])
-        if request.user.is_authenticated():
-            suggestion.user = request.user
-        suggestion.save()
-
-        mail_managers(u'Nowa sugestia na stronie WolneLektury.pl', u'''\
-Zgłoszono nową sugestię w serwisie WolneLektury.pl.
-http://%(site)s%(url)s
-
-Użytkownik: %(user)s
-Kontakt: %(contact)s
-
-%(description)s''' % {
-            'site': Site.objects.get_current().domain,
-            'url': reverse('admin:suggest_suggestion_change', args=[suggestion.id]),
-            'user': str(request.user) if request.user.is_authenticated() else '',
-            'contact': contact,
-            'description': description,
-            }, fail_silently=True)
-
-        if email_re.match(contact):
-            send_mail(u'[WolneLektury] ' + _(u'Thank you for your suggestion.'),
-                    _(u"""\
-Thank you for your comment on WolneLektury.pl.
-The suggestion has been referred to the project coordinator.""") +
-u"""
+    form_class = forms.PublishingSuggestForm
+    title = _('Report a bug or suggestion')
+    template = "publishing_suggest.html"
+    success_message = _('Report was sent successfully.')
 
--- 
-""" + _(u'''Message sent automatically. Please do not reply.'''),
-                    'no-reply@wolnelektury.pl', [contact], fail_silently=True)
 
-        response_data = {'success': True, 'message': _('Report was sent successfully.')}
-    else:
-        response_data = {'success': False, 'errors': suggest_form.errors}
-    return HttpResponse(LazyEncoder(ensure_ascii=False).encode(response_data))
+class SuggestionFormView(AjaxableFormView):
+    form_class = forms.SuggestForm
+    title = _('Report a bug or suggestion')
+    submit = _('Send report')
+    success_message = _('Report was sent successfully.')
index 84ec0eb..e394602 160000 (submodule)
@@ -1 +1 @@
-Subproject commit 84ec0eba63d0933b3f22a7884c46be6b796ee165
+Subproject commit e394602de9243608d1e99a3de448a75646f1a77f
index bc8b138..81cc393 100644 (file)
@@ -17,7 +17,7 @@ Feedparser>=4.1
 # PIL 
 PIL>=1.1.6
 mutagen>=1.17
-sorl-thumbnail>=3.2,<10
+sorl-thumbnail>=11.09,<12
 
 # home-brewed & dependencies
 lxml>=2.2.2
@@ -28,3 +28,6 @@ lxml>=2.2.2
 # celery tasks
 django-celery
 django-kombu
+
+# spell checking
+pyenchant
index 776d9d9..754e635 100755 (executable)
@@ -3,9 +3,11 @@
 ROOT=$(git rev-parse --show-toplevel)
 
 find $ROOT -name '*.py' | xargs etags -o ${ROOT}/TAGS
-find $ROOT/../librarian -name '*.py' | xargs etags -a -o ${ROOT}/TAGS
 if [ -n "$VIRTUAL_ENV" ]; then
   find ${VIRTUAL_ENV}/lib -name '*.py' |xargs etags -a -o ${ROOT}/TAGS
 else
     echo "No Virtual env enabled, will not add it to TAGS"
-fi
\ No newline at end of file
+fi
+
+find $ROOT/wolnelektury/static/css -name '*.css' |xargs etags -a -o ${ROOT}/TAGS
+find $ROOT/wolnelektury/static/js -name '*.js' |xargs etags -a -o ${ROOT}/TAGS
diff --git a/scripts/setmainpage.py b/scripts/setmainpage.py
deleted file mode 100755 (executable)
index 3244243..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
-# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
-#
-from django.core.management import setup_environ
-from wolnelektury import settings
-import sys
-
-setup_environ(settings)
-
-from catalogue.models import Tag
-
-
-MAIN_PAGE_THEMES = [
-    u'Obywatel',
-    u'Car',
-    u'Błoto',
-    u'Krew',
-    u'Danse macabre',
-    u'Obcy',
-    u'Matka',
-    u'Gotycyzm',
-]
-
-
-for tag in Tag.objects.all():
-    if tag.category in ('epoch', 'genre', 'author', 'kind'):
-        tag.main_page = True
-    elif tag.category == 'theme' and tag.name in MAIN_PAGE_THEMES:
-        tag.main_page = True
-    else:
-        tag.main_page = False
-
-    tag.save()
-    sys.stderr.write('.')
-
-
index f40dee2..1326a31 100644 (file)
Binary files a/wolnelektury/locale/pl/LC_MESSAGES/django.mo and b/wolnelektury/locale/pl/LC_MESSAGES/django.mo differ
index c256843..15a5dcc 100644 (file)
@@ -7,8 +7,8 @@ msgid ""
 msgstr ""
 "Project-Id-Version: WolneLektury\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2012-01-10 13:04+0100\n"
-"PO-Revision-Date: 2012-01-10 13:04+0100\n"
+"POT-Creation-Date: 2011-10-11 15:45+0200\n"
+"PO-Revision-Date: 2011-10-11 15:46+0100\n"
 "Last-Translator: Radek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
 "Language: pl\n"
@@ -129,24 +129,24 @@ msgstr ""
 #: templates/base.html.py:120
 #: templates/base.html:126
 #: templates/base.html.py:132
-#: templates/catalogue/book_detail.html:211
+#: templates/catalogue/book_detail.html:208
 #: templates/catalogue/book_fragments.html:33
 #: templates/catalogue/differentiate_tags.html:23
 #: templates/catalogue/search_multiple_hits.html:29
 #: templates/catalogue/search_too_short.html:19
-#: templates/catalogue/tagged_object_list.html:143
+#: templates/catalogue/tagged_object_list.html:142
 msgid "Close"
 msgstr "Zamknij"
 
 #: templates/base.html:122
 #: templates/base.html.py:128
 #: templates/base.html:134
-#: templates/catalogue/book_detail.html:213
+#: templates/catalogue/book_detail.html:210
 #: templates/catalogue/book_fragments.html:35
 #: templates/catalogue/differentiate_tags.html:25
 #: templates/catalogue/search_multiple_hits.html:31
 #: templates/catalogue/search_too_short.html:21
-#: templates/catalogue/tagged_object_list.html:145
+#: templates/catalogue/tagged_object_list.html:144
 msgid "Loading"
 msgstr "Ładowanie"
 
@@ -197,7 +197,7 @@ msgstr "Szukaj"
 #: templates/catalogue/book_list.html:13
 #: templates/catalogue/main_page.html:31
 #: templates/catalogue/search_form.html:3
-#: templates/catalogue/tagged_object_list.html:45
+#: templates/catalogue/tagged_object_list.html:44
 #: templates/info/base.html:12
 #: templates/lesmianator/lesmianator.html:14
 #: templates/lessons/document_list.html:34
@@ -273,12 +273,10 @@ msgstr "Pobierz plik PDF"
 #: templates/catalogue/book_detail.html:53
 #: templates/catalogue/book_detail.html:56
 #: templates/catalogue/book_detail.html:59
-#: templates/catalogue/book_detail.html:62
 #: templates/catalogue/tagged_object_list.html:37
 #: templates/catalogue/tagged_object_list.html:38
 #: templates/catalogue/tagged_object_list.html:39
 #: templates/catalogue/tagged_object_list.html:40
-#: templates/catalogue/tagged_object_list.html:41
 msgid "for reading"
 msgstr "do czytania"
 
@@ -292,110 +290,104 @@ msgid "Download EPUB"
 msgstr "Pobierz plik EPUB"
 
 #: templates/catalogue/book_detail.html:53
-#: templates/catalogue/book_detail.html:56
 #: templates/catalogue/tagged_object_list.html:38
-#: templates/catalogue/tagged_object_list.html:39
 msgid "on mobile devices"
 msgstr "na urządzeniach mobilnych"
 
 #: templates/catalogue/book_detail.html:56
-msgid "Download MOBI"
-msgstr "Pobierz plik MOBI"
-
-#: templates/catalogue/book_detail.html:59
 msgid "Download TXT"
 msgstr "Pobierz plik TXT"
 
-#: templates/catalogue/book_detail.html:59
-#: templates/catalogue/tagged_object_list.html:41
+#: templates/catalogue/book_detail.html:56
+#: templates/catalogue/tagged_object_list.html:40
 msgid "on small displays, for example mobile phones"
 msgstr "na małych ekranach, np. na komórce"
 
-#: templates/catalogue/book_detail.html:62
+#: templates/catalogue/book_detail.html:59
 msgid "Download ODT"
 msgstr "Pobierz plik ODT"
 
-#: templates/catalogue/book_detail.html:62
-#: templates/catalogue/tagged_object_list.html:40
+#: templates/catalogue/book_detail.html:59
+#: templates/catalogue/tagged_object_list.html:39
 msgid "and editing using"
 msgstr "i edytowania przy pomocy"
 
-#: templates/catalogue/book_detail.html:67
+#: templates/catalogue/book_detail.html:64
 msgid "Audiobooks"
 msgstr "Audiobooki"
 
-#: templates/catalogue/book_detail.html:81
+#: templates/catalogue/book_detail.html:78
 msgid "Artist"
 msgstr "Czyta"
 
-#: templates/catalogue/book_detail.html:82
+#: templates/catalogue/book_detail.html:79
 msgid "Director"
 msgstr "Reżyseruje"
 
-#: templates/catalogue/book_detail.html:110
+#: templates/catalogue/book_detail.html:107
 msgid "Audiobooks were prepared as a part of the projects:"
 msgstr "Audiobooki przygotowane w ramach projektów:"
 
-#: templates/catalogue/book_detail.html:115
+#: templates/catalogue/book_detail.html:112
 #, python-format
 msgid "%(cs)s, funded by %(fb)s"
 msgstr "%(cs)s, finansowanego przez %(fb)s"
 
-#: templates/catalogue/book_detail.html:127
+#: templates/catalogue/book_detail.html:124
 #, python-format
 msgid "Audiobooks were prepared as a part of the %(cs)s project funded by %(fb)s."
 msgstr "Audiobooki przygotowane w ramach projektu %(cs)s finansowanego przez %(fb)s."
 
-#: templates/catalogue/book_detail.html:129
+#: templates/catalogue/book_detail.html:126
 #, python-format
 msgid "Audiobooks were prepared as a part of the %(cs)s project."
 msgstr "Audiobooki przygotowane w ramach projektu %(cs)s."
 
-#: templates/catalogue/book_detail.html:155
+#: templates/catalogue/book_detail.html:152
 msgid "Details"
 msgstr "O utworze"
 
-#: templates/catalogue/book_detail.html:158
+#: templates/catalogue/book_detail.html:155
 msgid "Author"
 msgstr "Autor"
 
-#: templates/catalogue/book_detail.html:164
+#: templates/catalogue/book_detail.html:161
 msgid "Epoch"
 msgstr "Epoka"
 
-#: templates/catalogue/book_detail.html:170
+#: templates/catalogue/book_detail.html:167
 msgid "Kind"
 msgstr "Rodzaj"
 
-#: templates/catalogue/book_detail.html:176
+#: templates/catalogue/book_detail.html:173
 msgid "Genre"
 msgstr "Gatunek"
 
-#: templates/catalogue/book_detail.html:182
+#: templates/catalogue/book_detail.html:179
 msgid "Other resources"
 msgstr "W innych miejscach"
 
-#: templates/catalogue/book_detail.html:185
+#: templates/catalogue/book_detail.html:182
 msgid "Source of the book"
 msgstr "Źródło lektury"
 
-#: templates/catalogue/book_detail.html:188
+#: templates/catalogue/book_detail.html:185
 msgid "Book on the Editor's Platform"
 msgstr "Utwór na Platformie Redakcyjnej"
 
-#: templates/catalogue/book_detail.html:191
+#: templates/catalogue/book_detail.html:188
 msgid "Book description on Lektury.Gazeta.pl"
 msgstr "Opis lektury w Lektury.Gazeta.pl"
 
-#: templates/catalogue/book_detail.html:194
+#: templates/catalogue/book_detail.html:191
 msgid "Book description on Wikipedia"
 msgstr "Opis lektury w Wikipedii"
 
-#: templates/catalogue/book_detail.html:197
+#: templates/catalogue/book_detail.html:194
 msgid "View XML source"
 msgstr "Źródłowy plik XML"
 
-#: templates/catalogue/book_detail.html:201
+#: templates/catalogue/book_detail.html:198
 msgid "Work's themes "
 msgstr "Motywy w utworze"
 
@@ -516,7 +508,7 @@ msgid "Table of contents"
 msgstr "Spis treści"
 
 #: templates/catalogue/book_text.html:22
-#: templates/catalogue/tagged_object_list.html:134
+#: templates/catalogue/tagged_object_list.html:133
 msgid "Themes"
 msgstr "Motywy"
 
@@ -528,10 +520,6 @@ msgstr "Nota red."
 msgid "Infobox"
 msgstr "Informacje"
 
-#: templates/catalogue/collection.html:6
-msgid "in WolneLektury.pl"
-msgstr "w WolneLektury.pl"
-
 #: templates/catalogue/daisy_list.html:6
 msgid "Listing of all DAISY files on WolneLektury.pl"
 msgstr "Spis wszystkich plików DAISY w WolneLektury.pl"
@@ -563,15 +551,15 @@ msgid "Show full category"
 msgstr "Zobacz całą kategorię"
 
 #: templates/catalogue/folded_tag_list.html:13
-#: templates/catalogue/main_page.html:103
-#: templates/catalogue/main_page.html:108
-#: templates/catalogue/main_page.html:147
-#: templates/catalogue/main_page.html:342
+#: templates/catalogue/main_page.html:111
+#: templates/catalogue/main_page.html:116
+#: templates/catalogue/main_page.html:155
+#: templates/catalogue/main_page.html:350
 msgid "See more"
 msgstr "Zobacz więcej"
 
 #: templates/catalogue/folded_tag_list.html:22
-#: templates/catalogue/main_page.html:310
+#: templates/catalogue/main_page.html:318
 msgid "Hide"
 msgstr "Zwiń"
 
@@ -580,7 +568,7 @@ msgid "Shelves containing fragment"
 msgstr "Półki zawierające fragment"
 
 #: templates/catalogue/fragment_sets.html:4
-#: templates/catalogue/main_page.html:76
+#: templates/catalogue/main_page.html:85
 msgid "You do not own any shelves. You can create one below, if you want to."
 msgstr "Nie posiadasz żadnych półek. Jeśli chcesz, możesz utworzyć nową półkę poniżej."
 
@@ -627,143 +615,143 @@ msgstr[2] ""
 msgid "Browse books by categories"
 msgstr "Przeglądaj lektury według wybranych kategorii"
 
-#: templates/catalogue/main_page.html:59
+#: templates/catalogue/main_page.html:69
 msgid "Books for every school level"
 msgstr "Lektury na każdy poziom edukacji"
 
-#: templates/catalogue/main_page.html:61
+#: templates/catalogue/main_page.html:71
 msgid "primary school"
 msgstr "szkoła podstawowa"
 
-#: templates/catalogue/main_page.html:62
+#: templates/catalogue/main_page.html:72
 msgid "gymnasium"
 msgstr "gimnazjum"
 
-#: templates/catalogue/main_page.html:63
+#: templates/catalogue/main_page.html:73
 msgid "high school"
 msgstr "szkoła średnia"
 
-#: templates/catalogue/main_page.html:67
+#: templates/catalogue/main_page.html:76
 #: templates/catalogue/user_shelves.html:2
 msgid "Your shelves with books"
 msgstr "Twoje półki z lekturami"
 
-#: templates/catalogue/main_page.html:72
+#: templates/catalogue/main_page.html:81
 msgid "delete"
 msgstr "usuń"
 
-#: templates/catalogue/main_page.html:81
+#: templates/catalogue/main_page.html:90
 #: templates/catalogue/user_shelves.html:15
 msgid "Create shelf"
 msgstr "Utwórz półkę"
 
-#: templates/catalogue/main_page.html:86
+#: templates/catalogue/main_page.html:94
 msgid "Create your own book set. You can share it with friends by sending them link to your shelf."
 msgstr "Stwórz własny zestaw lektur. Możesz się nim później podzielić z innymi, przesyłając im link do Twojej półki."
 
-#: templates/catalogue/main_page.html:87
+#: templates/catalogue/main_page.html:95
 msgid "You need to "
 msgstr "Aby zarządzać swoimi półkami, musisz się"
 
-#: templates/catalogue/main_page.html:87
+#: templates/catalogue/main_page.html:95
 msgid "sign in"
 msgstr "zalogować"
 
-#: templates/catalogue/main_page.html:87
+#: templates/catalogue/main_page.html:95
 msgid "to manage your shelves."
 msgstr "."
 
-#: templates/catalogue/main_page.html:93
+#: templates/catalogue/main_page.html:101
 msgid "Twórzże się!"
 msgstr ""
 
-#: templates/catalogue/main_page.html:95
 #: templates/catalogue/main_page.html:103
+#: templates/catalogue/main_page.html:111
 msgid "Wolne Lektury Widget"
 msgstr "Widżet Wolne Lektury"
 
-#: templates/catalogue/main_page.html:96
+#: templates/catalogue/main_page.html:104
 msgid "Place our widget - search engine for Wolne Lektury which gives access to free books and audiobooks - on your homepage! Just copy the HTML code below onto your page:"
 msgstr "Umieść widżet – wyszukiwarkę Wolnych Lektur umożliwiającą dostęp do darmowych lektur i audiobooków – na swojej stronie WWW! Po prostu skopiuj poniższy kod HTML na swoją stronę:"
 
-#: templates/catalogue/main_page.html:97
+#: templates/catalogue/main_page.html:105
 msgid "Insert this element in place where you want display the widget"
 msgstr "Umieść ten element w miejscu gdzie chcesz wyświetlić widżet"
 
-#: templates/catalogue/main_page.html:100
+#: templates/catalogue/main_page.html:108
 msgid "Place this element just before closing body tag: &lt;/body&gt;"
 msgstr "Umieść ten element tuż przed zamknięciem taga body: &lt;/body&gt;"
 
-#: templates/catalogue/main_page.html:106
-#: templates/catalogue/main_page.html:108
+#: templates/catalogue/main_page.html:114
+#: templates/catalogue/main_page.html:116
 #: templates/lessons/document_list.html:32
 msgid "Hand-outs for teachers"
 msgstr "Materiały pomocnicze dla nauczycieli"
 
-#: templates/catalogue/main_page.html:107
+#: templates/catalogue/main_page.html:115
 msgid "Lessons' prospects and other ideas for using Wolnelektury.pl for teaching."
 msgstr "Scenariusze lekcji i inne pomysły na wykorzytanie serwisu WolneLektury.pl podczas nauczania."
 
-#: templates/catalogue/main_page.html:114
-#: templates/catalogue/tagged_object_list.html:113
+#: templates/catalogue/main_page.html:122
+#: templates/catalogue/tagged_object_list.html:112
 msgid "Authors"
 msgstr "Autorzy"
 
-#: templates/catalogue/main_page.html:118
-#: templates/catalogue/tagged_object_list.html:117
+#: templates/catalogue/main_page.html:126
+#: templates/catalogue/tagged_object_list.html:116
 msgid "Kinds"
 msgstr "Rodzaje"
 
-#: templates/catalogue/main_page.html:122
-#: templates/catalogue/tagged_object_list.html:121
+#: templates/catalogue/main_page.html:130
+#: templates/catalogue/tagged_object_list.html:120
 msgid "Genres"
 msgstr "Gatunki"
 
-#: templates/catalogue/main_page.html:126
-#: templates/catalogue/tagged_object_list.html:125
+#: templates/catalogue/main_page.html:134
+#: templates/catalogue/tagged_object_list.html:124
 msgid "Epochs"
 msgstr "Epoki"
 
-#: templates/catalogue/main_page.html:132
-#: templates/catalogue/main_page.html:147
+#: templates/catalogue/main_page.html:140
+#: templates/catalogue/main_page.html:155
 msgid "Themes and topics"
 msgstr "Motywy i tematy"
 
-#: templates/catalogue/main_page.html:135
+#: templates/catalogue/main_page.html:143
 msgid "Themes groups"
 msgstr "Rodziny motywów"
 
-#: templates/catalogue/main_page.html:320
+#: templates/catalogue/main_page.html:328
 msgid "News"
 msgstr "Aktualności"
 
-#: templates/catalogue/main_page.html:324
+#: templates/catalogue/main_page.html:332
 msgid "See our blog"
 msgstr "Zobacz nasz blog"
 
-#: templates/catalogue/main_page.html:327
-#: templates/catalogue/main_page.html:333
+#: templates/catalogue/main_page.html:335
+#: templates/catalogue/main_page.html:341
 msgid "You can help us!"
 msgstr "Możesz nam pomóc!"
 
-#: templates/catalogue/main_page.html:329
+#: templates/catalogue/main_page.html:337
 msgid "Become a volunteer &ndash; an editor, developer or translator."
 msgstr "Zostań naszym redaktorem, programistą lub tłumaczem – wolontariuszem."
 
-#: templates/catalogue/main_page.html:330
+#: templates/catalogue/main_page.html:338
 msgid "Gain new skills and experience."
 msgstr "Zdobądź nowe umiejętności i doświadczenie."
 
-#: templates/catalogue/main_page.html:331
+#: templates/catalogue/main_page.html:339
 msgid "Join an open project of creating an innovative online library."
 msgstr "Weź udział w otwartym projekcie i twórz innowacyjną bibliotekę internetową."
 
-#: templates/catalogue/main_page.html:336
-#: templates/catalogue/main_page.html:342
+#: templates/catalogue/main_page.html:344
+#: templates/catalogue/main_page.html:350
 msgid "About us"
 msgstr "O projekcie"
 
-#: templates/catalogue/main_page.html:338
+#: templates/catalogue/main_page.html:346
 msgid ""
 "\n"
 "\t\t\tInternet library with school readings “Wolne Lektury” (<a href=\"http://wolnelektury.pl\">www.wolnelektury.pl</a>) is a project made by Modern Poland Foundation. It started in 2007 and shares school readings, which are recommended by Ministry of National Education and are in public domain.\n"
@@ -772,7 +760,7 @@ msgstr ""
 "\n"
 "Biblioteka internetowa z lekturami szkolnymi „Wolne Lektury” (<a href=\"http://wolnelektury.pl\">www.wolnelektury.pl</a>) to projekt realizowany przez fundację Nowoczesna Polska. Działa od 2007 roku i udostępnia w swoich zbiorach lektury szkolne, które są zalecane do użytku przez Ministerstwo Edukacji Narodowej i które trafiły już do domeny publicznej."
 
-#: templates/catalogue/main_page.html:351
+#: templates/catalogue/main_page.html:359
 msgid ""
 "\n"
 "Portions of this page are modifications based on work created and <a href=\"http://code.google.com/policies.html\">shared by Google</a> and used\n"
@@ -802,7 +790,7 @@ msgid "Search in WolneLektury.pl"
 msgstr "Wyszukiwanie w WolneLektury.pl"
 
 #: templates/catalogue/search_no_hits.html:14
-#: templates/catalogue/tagged_object_list.html:103
+#: templates/catalogue/tagged_object_list.html:101
 msgid "Sorry! Search cirteria did not match any resources."
 msgstr "Przepraszamy! Brak wyników spełniających kryteria podane w zapytaniu."
 
@@ -836,93 +824,93 @@ msgstr "Pobierz wszystkie książki z tej półki"
 msgid "Choose books' formats which you want to download:"
 msgstr "Wybierz formaty książek, które chcesz pobrać:"
 
+#: templates/catalogue/tagged_object_list.html:41
 #: templates/catalogue/tagged_object_list.html:42
-#: templates/catalogue/tagged_object_list.html:43
 msgid "for listening"
 msgstr "do słuchania"
 
-#: templates/catalogue/tagged_object_list.html:42
+#: templates/catalogue/tagged_object_list.html:41
 msgid "on favourite MP3 player"
 msgstr "w ulubionym odtwarzaczu MP3"
 
-#: templates/catalogue/tagged_object_list.html:43
+#: templates/catalogue/tagged_object_list.html:42
 msgid "open format"
 msgstr "otwarty format"
 
-#: templates/catalogue/tagged_object_list.html:43
+#: templates/catalogue/tagged_object_list.html:42
 msgid "Xiph.org Foundation"
 msgstr "fundacji Xiph.Org"
 
-#: templates/catalogue/tagged_object_list.html:45
+#: templates/catalogue/tagged_object_list.html:44
 #: templates/lessons/ajax_document_detail.html:3
 msgid "Download"
 msgstr "Pobierz"
 
-#: templates/catalogue/tagged_object_list.html:45
+#: templates/catalogue/tagged_object_list.html:44
 msgid "Updating list of books' formats on the shelf"
 msgstr "Uaktualnianie listy formatów książek na półce."
 
-#: templates/catalogue/tagged_object_list.html:45
+#: templates/catalogue/tagged_object_list.html:44
 msgid "cancel"
 msgstr "anuluj"
 
-#: templates/catalogue/tagged_object_list.html:50
+#: templates/catalogue/tagged_object_list.html:49
 msgid "Share this shelf"
 msgstr "Podziel się tą półką"
 
-#: templates/catalogue/tagged_object_list.html:52
+#: templates/catalogue/tagged_object_list.html:51
 msgid "Copy this link and share it with other people to let them see your shelf."
 msgstr "Skopiuj ten link i przekaż go osobom, z którymi chcesz się podzielić tą półką."
 
-#: templates/catalogue/tagged_object_list.html:62
+#: templates/catalogue/tagged_object_list.html:61
 #: templates/pdcounter/author_detail.html:27
 msgid "Read work's study of this author on Lektury.Gazeta.pl"
 msgstr "Przeczytaj omówienia utworów autora w serwisie Lektury.Gazeta.pl"
 
-#: templates/catalogue/tagged_object_list.html:64
+#: templates/catalogue/tagged_object_list.html:63
 #, python-format
 msgid "Read study of epoch %(last_tag)s on Lektury.Gazeta.pl"
 msgstr "Przeczytaj omówienia z epoki %(last_tag)s w serwisie Lektury.Gazeta.pl"
 
-#: templates/catalogue/tagged_object_list.html:66
+#: templates/catalogue/tagged_object_list.html:65
 #, python-format
 msgid "Read study of kind %(last_tag)s on Lektury.Gazeta.pl"
 msgstr "Przeczytaj omówienia z rodzaju %(last_tag)s w serwisie Lektury.Gazeta.pl"
 
-#: templates/catalogue/tagged_object_list.html:68
+#: templates/catalogue/tagged_object_list.html:67
 #, python-format
 msgid "Read study of genre %(last_tag)s on Lektury.Gazeta.pl"
 msgstr "Przeczytaj omówienia z gatunku %(last_tag)s w serwisie Lektury.Gazeta.pl"
 
-#: templates/catalogue/tagged_object_list.html:70
+#: templates/catalogue/tagged_object_list.html:69
 msgid "Read related study on Lektury.Gazeta.pl"
 msgstr "Przeczytaj powiązane omówienia w serwisie Lektury.Gazeta.pl"
 
-#: templates/catalogue/tagged_object_list.html:78
+#: templates/catalogue/tagged_object_list.html:77
 #: templates/pdcounter/author_detail.html:32
 msgid "Read article about this author on Wikipedia"
 msgstr "Przeczytaj artykuł o autorze w Wikipedii"
 
-#: templates/catalogue/tagged_object_list.html:80
+#: templates/catalogue/tagged_object_list.html:79
 #, python-format
 msgid "Read article about epoch %(last_tag)s on Wikipedia"
 msgstr "Przeczytaj artykuł o epoce %(last_tag)s w Wikipedii"
 
-#: templates/catalogue/tagged_object_list.html:82
+#: templates/catalogue/tagged_object_list.html:81
 #, python-format
 msgid "Read article about kind %(last_tag)s on Wikipedia"
 msgstr "Przeczytaj artykuł o rodzaju %(last_tag)s w Wikipedii"
 
-#: templates/catalogue/tagged_object_list.html:84
+#: templates/catalogue/tagged_object_list.html:83
 #, python-format
 msgid "Read article about genre %(last_tag)s on Wikipedia"
 msgstr "Przeczytaj artykuł o gatunku %(last_tag)s w Wikipedii"
 
-#: templates/catalogue/tagged_object_list.html:86
+#: templates/catalogue/tagged_object_list.html:85
 msgid "Read related article on Wikipedia"
 msgstr "Przeczytaj powiązany artykuł w Wikipedii"
 
-#: templates/catalogue/tagged_object_list.html:96
+#: templates/catalogue/tagged_object_list.html:95
 msgid "Delete"
 msgstr "Usuń"
 
@@ -1076,6 +1064,9 @@ msgstr "Zaloguj się"
 #~ "to profesjonalne nagrania tekstów literackich z naszego zbioru dostępne "
 #~ "na wolnej licencji w formatach MP3, Ogg Vorbis oraz w systemie DAISY."
 
+#~ msgid "Download MP3"
+#~ msgstr "Pobierz plik MP3"
+
 #~ msgid "Download Ogg Vorbis"
 #~ msgstr "Pobierz plik Ogg Vorbis"
 
index e34b905..a69b050 100644 (file)
@@ -60,6 +60,7 @@ USE_I18N = True
 # Example: "/home/media/media.lawrence.com/"
 MEDIA_ROOT = path.join(PROJECT_DIR, '../media/')
 STATIC_ROOT = path.join(PROJECT_DIR, 'static/')
+SEARCH_INDEX = path.join(MEDIA_ROOT, 'search/')
 
 # URL that handles the media served from MEDIA_ROOT. Make sure to use a
 # trailing slash if there is a path component (optional in other cases).
@@ -88,6 +89,7 @@ TEMPLATE_CONTEXT_PROCESSORS = (
     'django.core.context_processors.media',
     'django.core.context_processors.request',
     'wolnelektury.context_processors.extra_settings',
+    'search.context_processors.search_form',
 )
 
 MIDDLEWARE_CLASSES = [
@@ -110,7 +112,7 @@ TEMPLATE_DIRS = [
     path.join(PROJECT_DIR, 'templates'),
 ]
 
-LOGIN_URL = '/uzytkownicy/login/'
+LOGIN_URL = '/uzytkownicy/zaloguj/'
 
 LOGIN_REDIRECT_URL = '/'
 
@@ -137,6 +139,7 @@ INSTALLED_APPS = [
     'modeltranslation',
 
     # our
+    'ajaxable',
     'api',
     'catalogue',
     'chunks',
@@ -151,6 +154,8 @@ INSTALLED_APPS = [
     'sponsors',
     'stats',
     'suggest',
+    'picture',
+    'search',
 ]
 
 CACHES = {
@@ -172,13 +177,33 @@ CACHE_MIDDLEWARE_ANONYMOUS_ONLY=True
 # CSS and JavaScript file groups
 COMPRESS_CSS = {
     'all': {
-        'source_filenames': ('css/master.css', 'css/jquery.autocomplete.css', 'css/jquery.countdown.css', 'css/master.plain.css', 'css/sponsors.css', 'css/facelist_2-0.css',),
+        #'source_filenames': ('css/master.css', 'css/jquery.autocomplete.css', 'css/master.plain.css', 'css/facelist_2-0.css',),
+        'source_filenames': [
+            'css/jquery.countdown.css', 
+
+            'css/base.css',
+            'css/header.css',
+            'css/main_page.css',
+            'css/dialogs.css',
+            'css/picture_box.css',
+            'css/book_box.css',
+            'css/catalogue.css',
+            'css/sponsors.css',
+            
+            'css/ui-lightness/jquery-ui-1.8.16.custom.css',
+        ],
         'output_filename': 'css/all.min?.css',
     },
     'book': {
         'source_filenames': ('css/master.book.css',),
         'output_filename': 'css/book.min?.css',
     },
+    'player': {
+        'source_filenames': [
+            'jplayer/jplayer.blue.monday.css', 
+        ],
+        'output_filename': 'css/player.min?.css',
+    },
     'simple': {
         'source_filenames': ('css/simple.css',),
         'output_filename': 'css/simple.min?.css',
@@ -186,20 +211,37 @@ COMPRESS_CSS = {
 }
 
 COMPRESS_JS = {
-    'jquery': {
-        'source_filenames': ('js/jquery.js',),
-        'output_filename': 'js/jquery.min.js',
-    },
-    'all': {
-        'source_filenames': ('js/jquery.autocomplete.js', 'js/jquery.form.js',
+    'base': {
+        'source_filenames': (
+            'js/jquery.cycle.min.js',
+            'js/jquery.jqmodal.js',
+            'js/jquery.form.js',
             'js/jquery.countdown.js', 'js/jquery.countdown-pl.js',
             'js/jquery.countdown-de.js', 'js/jquery.countdown-uk.js',
             'js/jquery.countdown-es.js', 'js/jquery.countdown-lt.js',
             'js/jquery.countdown-ru.js', 'js/jquery.countdown-fr.js',
-            'js/jquery.cycle.min.js',
-            'js/jquery.jqmodal.js', 'js/jquery.labelify.js', 'js/catalogue.js',
+
+            'js/jquery-ui-1.8.16.custom.min.js',
+
+            'js/locale.js',
+            'js/dialogs.js',
+            'js/sponsors.js',
+            'js/base.js',
+            'js/pdcounter.js',
+
+            'js/search.js',
+
+            'js/jquery.labelify.js',
             ),
-        'output_filename': 'js/all?.min.js',
+        'output_filename': 'js/base?.min.js',
+    },
+    'player': {
+        'source_filenames': [
+            'jplayer/jquery.jplayer.min.js', 
+            'jplayer/jplayer.playlist.min.js', 
+            'js/player.js', 
+        ],
+        'output_filename': 'js/player.min?.js',
     },
     'book': {
         'source_filenames': ('js/jquery.eventdelegation.js', 'js/jquery.scrollto.js', 'js/jquery.highlightfade.js', 'js/book.js',),
@@ -240,12 +282,16 @@ MAX_TAG_LIST = 6
 NO_BUILD_EPUB = False
 NO_BUILD_TXT = False
 NO_BUILD_PDF = False
-NO_BUILD_MOBI = False
+NO_BUILD_MOBI = True
+NO_SEARCH_INDEX = False
 
 ALL_EPUB_ZIP = 'wolnelektury_pl_epub'
 ALL_PDF_ZIP = 'wolnelektury_pl_pdf'
 ALL_MOBI_ZIP = 'wolnelektury_pl_mobi'
 
+CATALOGUE_DEFAULT_LANGUAGE = 'pol'
+PUBLISH_PLAN_FEED = 'http://redakcja.wolnelektury.pl/documents/track/editor-proofreading/'
+
 PAGINATION_INVALID_PAGE_RAISES_404 = True
 
 import djcelery
@@ -259,6 +305,7 @@ BROKER_PASSWORD = "guest"
 BROKER_VHOST = "/"
 
 
+
 # Load localsettings, if they exist
 try:
     from localsettings import *
diff --git a/wolnelektury/static/css/base.css b/wolnelektury/static/css/base.css
new file mode 100755 (executable)
index 0000000..5ce2f5a
--- /dev/null
@@ -0,0 +1,186 @@
+/* Logo font */
+@font-face {
+    /* IE version */
+    font-family: WL-Logo;
+    src: url(/static/fonts/WL.eot);
+}
+@font-face {
+  font-family: WL-Nav;
+  src: url(/static/fonts/WL-Nav.ttf) format("truetype");
+}
+
+
+html {
+    margin: 0;
+    padding: 0;
+}
+
+body {
+    margin: 0;
+    background: #f7f7f7;
+    font-size: .625em;
+    font-family: Georgia;
+    /*line-height: 1.4em;*/
+}
+
+
+a {
+    color: #1199a2; /* #01adba; */
+    text-decoration: none;
+}
+
+h1 {
+    font-size: 3.5em;
+    font-weight: normal;
+    margin-top: .4em
+}
+
+.left-column {
+    width: 47em;
+    float: left;
+}
+.right-column {
+    float:right;
+    width: 47em;
+}
+.normal-text {
+    font-size: 1.3em;
+    line-height: 1.3em;
+}
+
+h2 {
+    margin: 0;
+    font-size: 1em;
+    font-weight: normal;
+}
+
+
+.mono {
+    font-family: "Andale Mono", "Lucida Sans Typewriter", "Courier New";
+    font-weight: bold;
+}
+
+.accent1 {
+    color: #191919;
+}
+
+.accent2 {
+    color: #242424;
+}
+
+.accent3 {
+    color: #575c63;
+}
+
+
+.accent4 {
+    color: #707b7a;
+}
+
+.contrast {
+    #1199a2;
+}
+
+/* #281d1c */
+
+.clearboth {
+    clear: both;
+}
+
+#header-content, div#main-content, div#half-header-content, #footer-content {
+    width: 97.5em;
+    margin: auto;
+}
+
+
+.page-desc {
+    margin-left: 1.5em;
+}
+
+.inline-tag-lists p span:first-child {
+    color: #281d1c;
+}
+
+.inline-tag-lists {
+    font-size: 1.1em;
+}
+
+#themes-list-toggle:after {
+    padding-left: 1em;
+    content: "↓";
+    font-family: WL-Nav;
+    vertical-align: middle;
+}
+#themes-list-wrapper {
+    position: relative;
+    font-size: 1.1em;
+}
+#themes-list {
+    position: absolute;
+    display: none;
+    border: 1px solid #ddd;
+    padding: 1em;
+    background: #fff;
+    -moz-box-shadow: 2px 2px 2px #ddd;
+    -webkit-box-shadow: 2px 2px 2px #ddd;
+    box-shadow: 2px 2px 2px #ddd;
+    z-index: 500;
+}
+#themes-list ul {
+    list-style: none;
+    padding: 0;
+    margin: 0;
+    font-size: 1.1em;
+    -moz-column-width: 12em;
+    width: 48em;
+}
+
+
+a.cite {
+    display: block;
+    color: black;
+    background: white;
+    padding: 3em 2em .1em 8em;
+}
+.cite-body {
+    font-size: 1.8em;
+    line-height: 1.3em;
+}
+.cite p {
+    color: #444;
+    font-size: 1.1em;
+    margin-top: 1.6em;
+}
+
+.see-also {
+    margin-left: 8em;
+    float: left;
+    width: 14.3em;
+}
+.download {
+    margin-left: 2em;
+    float: left;
+}
+
+.see-also, .download {
+    margin-top: 2em;
+    margin-bottom: 2em;
+}
+.see-also h2, .download h2 {
+    font-size: 1.1em;
+}
+.see-also ul, .download ul {
+    list-style: none;
+    padding: 0;
+    margin: 0;
+    font-size: 1.1em;
+}
+
+
+#footer {
+    color: #777;
+    eborder-top: 1px solid #ddd;
+    margin-top: 5em;
+    padding-top:3em;
+    background: #fff;
+}
diff --git a/wolnelektury/static/css/book_box.css b/wolnelektury/static/css/book_box.css
new file mode 100755 (executable)
index 0000000..12adda1
--- /dev/null
@@ -0,0 +1,225 @@
+.book-wide-box, .book-mini-box, .book-box {
+    display: inline-block;
+    margin: 0;
+    vertical-align: top;
+}
+
+
+.book-box {
+    width: 48.75em;
+}
+
+.book-mini-box {
+    width: 16.15em;
+}
+
+.book-wide-box {
+    width: 98.5em;
+    margin-left: -0.1em;
+}
+
+.book-mini-box a, .book-box-inner {
+    display: block;
+    color: black;
+    border: 1px solid #ddd;
+    height: 20em;
+    padding: .8em 1em;
+    margin: .1em;
+    background: #fff;
+    -moz-box-shadow: 2px 2px 2px #ddd;
+    -webkit-box-shadow: 2px 2px 2px #ddd;
+    box-shadow: 2px 2px 2px #ddd;
+}
+
+.book-mini-box a {
+    height: 27.1em;
+    margin: .1em;
+    overflow: hidden;
+}
+.book-box-inner {
+    height: 19.75em;
+    margin: .5em;
+}
+
+.book-wide-box .book-box-inner {
+    height: 24.4em;
+}
+
+.book-mini-box img, .book-box img, .book-wide-box img {
+    width: 13.9em;
+    height: 19.3em;
+}
+.book-mini-box img {
+    margin-bottom: 1.8em;
+}
+.book-box img, .book-wide-box img {
+    float: left;
+    margin-right: 1.5em;
+}
+
+.book-mini-box .desc {
+    margin-left:0em;
+}
+.book-mini-box .author {
+    font-size: 1.1em;
+    color: #707b7a;
+    display: block;
+}
+.book-mini-box .title {
+    font-size: 1.4em;
+    color: #242424;
+}
+
+
+.book-box-body {
+    height: 17em;
+    overflow: hidden;
+}
+
+.book-wide-box .book-box-body {
+    height: 21.8em;
+}
+
+.book-box-head {
+    min-height: 7em;
+    margin-top: 1.4em;
+    margin-bottom: 1em;
+}
+.book-box-head .author {
+    font-size: 1.1em;
+}
+.book-box-head .title {
+    font-size: 2.4em;
+    margin-top: .3em;
+}
+.book-box-body .tags {
+    font-size: 1.1em;
+}
+.book-box-tag {
+    margin-right: .5em;
+    margin-left: .4em;
+}
+.book-box-download {
+    position: relative;
+}
+
+.book-box-download a {
+    position: relative;
+    z-index: 1;
+}
+
+.book-box-formats {
+    display: none;
+    position: absolute;
+
+    width: 16.363em;
+    border: 1px solid #ddd;
+    padding: 3.454em 1.727em .818em 1.727em;
+    background: #fff;
+    -moz-box-shadow: 2px 2px 2px #ddd;
+    -webkit-box-shadow: 2px 2px 2px #ddd;
+    box-shadow: 2px 2px 2px #ddd;
+
+    z-index: 0;
+    top: -1.454em;
+    left: -1.727em;
+}
+.book-box-formats span {
+    display: block;
+}
+
+.book-box-download:hover .book-box-formats span:first-child {
+    margin-top: 1.454em;
+}
+
+.book-box-download:hover .book-box-formats {
+    display: block;
+}
+
+.book-box-tools {
+    font-size: 1.1em;
+}
+
+.book-wide-box .book-box-tools {
+    margin-left: 14em;
+}
+
+.book-box-tools a.downarrow:before {
+    content: "\2609";
+    font-family: WL-Nav;
+    font-size: 2.25em;
+    margin-right: .15em;
+    vertical-align: middle;
+}
+
+.book-box-audiobook a:before {
+    content: "\266B";
+    font-family: WL-Nav;
+    font-size: 2.25em;
+    margin-right: .15em;
+    vertical-align: middle;
+}
+
+ul.book-box-tools {
+    margin: 0;
+    padding: 0;
+}
+
+.book-box-tools li {
+    display: inline-block;
+}
+
+.book-box-read {
+    width: 11.5em;
+}
+.book-box-download {
+    width: 8.5em;
+}
+.book-box-audiobook {
+    width: 7em;
+}
+
+.book-wide-box .right-column {
+    float: right;
+    width: 41.5em;
+}
+
+.book-wide-box blockquote.cite-body {
+    /* @ 18pt */
+    width: 100%; /*23.055em;*/
+    height: 7.222em;
+    background-color: #f7f7f7;
+    margin: 0;
+    position: relative;
+    top: -0.444em;
+    right: -0.555em;
+    vertical-align: center;
+}
+
+.book-wide-box blockquote div {
+    padding: 0.888em;
+}
+
+ul.inline-items, ul.inline-items li {
+    margin: 0;
+    padding: 0;
+}
+
+ul.inline-items li {
+    display: inline-block;
+}
+
+.book-wide-box #other-tools {
+    float: left;
+    width: 14.5em;
+    margin: 6em 0 0 1.5em;
+    
+}
+
+.book-wide-box #other-download {
+    float: left;
+    width: 22.5em;
+    margin: 6em 1.5em 0em 1.5em
+}
+
diff --git a/wolnelektury/static/css/catalogue.css b/wolnelektury/static/css/catalogue.css
new file mode 100755 (executable)
index 0000000..ff7ea4b
--- /dev/null
@@ -0,0 +1,69 @@
+.work-list {
+    margin: 0;
+    padding: 0;
+    list-style: none;
+}
+
+.work-item {
+    margin: 0;
+    padding: 0;
+}
+
+.books .work-item  {
+    display: inline-block;
+}
+
+
+#tagged-object-list .left-column, #tagged-object-list .right-column {
+    width: 48em;
+}
+
+
+/* listing of all books */
+#book-list {
+   padding-left: 50px;
+}
+#book-list-nav {
+    position: absolute;
+    right: 50px;
+    width: 200px;
+    border-left: 1px #cfcfcf solid;
+    padding: 10px;
+    font-size: 1.2em;
+}
+
+#book-list-nav ul {
+    list-style-type: none;
+    margin: 5px;
+    padding: 0;
+}
+
+.book-list-show-index {
+    display: none;
+    margin: 0;
+    padding: 0;
+}
+
+
+#book-a-list #book-list ol {
+    padding-left: 1em;
+    margin: 0.3em 0 1.2em 0;
+    list-style: none;
+}
+
+#book-a-list #book-list h2 a {
+    color: black;
+}
+
+#book-list-up {
+    position: fixed;
+    bottom: 50px;
+    right: 50px;
+    border-left: 1px #cfcfcf solid;
+    padding: 10px;
+    background-color: white;    
+}
+
+
+
+fragment
\ No newline at end of file
diff --git a/wolnelektury/static/css/dialogs.css b/wolnelektury/static/css/dialogs.css
new file mode 100755 (executable)
index 0000000..35136e0
--- /dev/null
@@ -0,0 +1,104 @@
+.cuteform {
+    font-size: 1.1em;
+}
+.cuteform ol, .cuteform ul {
+    padding: 0;
+    margin: 0;
+    list-style: none;
+    font-style: 1.1em;
+}
+
+.cuteform ol li, .cuteform ul li {
+    margin-top: 0.7em;
+}
+
+.cuteform label {
+    display: block;
+}
+
+.cuteform span.help-text {
+    display: block;
+    font-size: 0.8em;
+    color: #999;
+}
+
+.cuteform .error {
+    color: #BF3024;
+    display: block;
+}
+.cuteform .errorlist {
+    color: #BF3024;
+}
+
+
+.jqmOverlay { background-color: #000; }
+
+
+.dialog-window {
+    position: absolute;
+    display: none;
+    background-color: transparent;
+    margin-top: -0.5em;
+    margin-left: 1em;
+}
+
+.dialog-window div.header {
+    font-size: 1.1em;
+    width: 4em;
+    background-color: #FFF;
+    border-right: 0.3em solid #DDD;
+    padding: 0.5em 1em 0.5em 1em;
+    right: 0;
+    left: auto;
+    float: right;
+    text-align: center;
+}
+
+
+.dialog-window div.target {
+    background-color: #FFF;
+    color: black;
+    border-right: 0.3em solid #DDD;
+    border-bottom: 0.3em solid #DDD;
+    padding: 1em;
+    clear: both;
+}
+
+.dialog-window h1 {
+    font-size: 1.2em;
+}
+
+.dialog-window textarea, .dialog-window input {
+    width: 100%;
+}
+
+#login-window {
+    width: 26em;
+}
+#register-window {
+    width: 26em;
+}
+
+#suggest-window {
+    width: 26em;
+}
+
+#suggest-window textarea {
+    height: 6em;
+}
+
+#suggest-publishing-window {
+    width: 29em;
+}
+
+#suggest-publishing-window textarea {
+    height: 3em;
+}
+
+#custom-pdf-window {
+    width: 24em;   
+}
+
+#custom-pdf-window label {
+    display: inline;
+}
diff --git a/wolnelektury/static/css/header.css b/wolnelektury/static/css/header.css
new file mode 100755 (executable)
index 0000000..9435ad7
--- /dev/null
@@ -0,0 +1,207 @@
+/* Logo font */
+@font-face {
+    /* IE version */
+    font-family: WL-Logo;
+    src: url(/static/fonts/WL.eot);
+}
+@font-face {
+  font-family: WL-Logo;
+  src: url(/static/fonts/WL.ttf) format("truetype");
+}
+
+
+#header {
+    height: 3em;
+    padding-top: 1.9em;
+    padding-bottom: 0;
+    color: #989898;
+    background: #191919;
+}
+
+#half-header {
+    padding-bottom: 0;
+    background: url('/static/img/bg-header.png');
+    background-position: center;
+    background-size: 100%;
+}
+
+#half-header-content {
+    background: #191919;
+}
+
+
+#user-info {
+    float: right;
+    margin: 0;
+}
+
+#logo {
+    position: absolute;
+    top: 1.9em;
+    margin-left: 1.5em;
+}
+
+#logo a {
+    font-family: WL-Logo;
+    font-size: 2.05em;
+    color:#f7f7f7;
+}
+
+#tagline {
+    display: inline-block;
+    margin-left: 25.5em;
+}
+#tagline span {
+    font-size: 1.1em;
+}
+
+#search-area {
+    margin: 0;
+    background: #444;
+    margin-left: 24em;
+    width: 73.5em;
+}
+
+#search-field {
+    display: inline-block;
+    width: 63.1em;
+    padding-left: .5em;
+    padding-right: 0;
+    padding-top: 0.6em;
+    padding-bottom: 0;
+}
+
+#search {
+    font-size: 1.3em;
+    padding: 0;
+    /*height: 3.3em;
+    width: 62.6em;
+    padding-left: .5em;
+    -webkit-border-radius: .5em;
+    -moz-border-radius: .5em;
+    border: none;
+    border-radius: .5em;
+    -webkit-box-shadow:0 0 .5em #444 inset;
+    -moz-box-shadow:0 0 .5em #444 inset;
+    box-shadow: 0 0 .5em #444 inset;*/
+    height: 2.54em;
+    width: 47.47em;
+    padding-left: 1em;
+    -webkit-border-radius: .38em;
+    -moz-border-radius: .38em;
+    border: none;
+    border-radius: .38em;
+    -webkit-box-shadow:0 0 .38em #444 inset;
+    -moz-box-shadow:0 0 .38em #444 inset;
+    box-shadow: 0 0 .5em #444 inset;
+
+    font-family: Georgia;
+    background-color: #fff;
+    color: #000;
+}
+#search.blur {
+    font-family: Georgia;
+    font-style: italic;
+    color: #888;
+}
+
+#search-button {
+    display: inline-block;
+    background: #02adb7;
+    padding: 0;
+    margin: 0;
+    width: 9.4em;
+    float: right;
+}
+#search-button button {
+    font-size: 1em;
+    height: 4.5em;
+    border: none;
+    background: #02adb7;
+    color: white;
+    width: 100%;
+    padding: 0;
+}
+
+#search-button button span {
+    font-size: 1.1em;
+    position:relative;
+}
+
+
+#nav-line {
+    background-color: #e2e2e2;
+    height: 4.9em;
+}
+
+ul#catalogue {
+    list-style: none;
+    padding: 0;
+    margin: 0 0 0 .6em;
+}
+ul#catalogue li {
+    background-color: #e2e2e2;
+    float: left;
+}
+ul#catalogue a {
+    display: block;
+    padding-left: 1.4em;
+    padding-right: 1.4em;
+    /* must match grid-line */
+    height: 3.1em;
+    padding-top: 1.8em;
+}
+ul#catalogue span {
+    font-size: 1.1em;
+}
+
+
+#lang-button {
+    color: #aaa;
+}
+#lang-button:after {
+    padding-left: 1em;
+    content: "↓";
+    font-family: WL-Nav;
+    vertical-align: middle;
+}
+#lang-menu {
+    position: relative;
+    float: right;
+    display: block;
+    padding-left: 2.5em;
+    padding-right: 2em;
+    /* must match grid-line */
+    height: 3em;
+    padding-top: 1.9em;
+    background: #f7f7f7;
+}
+
+#lang-menu-items button {
+    display: none;
+    background: #f7f7f7;
+    color: #777;
+    cursor: pointer;
+    width: 100%;
+    border: solid #ddd;
+    border-width: 0 0 1px 0;
+    padding: .5em 0;
+    margin: 0;
+}
+
+#lang-menu:hover button {
+    display: block;
+}
+
+#lang-menu:hover #lang-menu-items {
+    position: absolute;
+    width: 100%;
+    padding: 0;
+    left: 0;
+    /* must match grid-line height */
+    top: 3.9em;
+}
+
+#lang-menu .active {
+    color: #000;
+}
diff --git a/wolnelektury/static/css/main_page.css b/wolnelektury/static/css/main_page.css
new file mode 100755 (executable)
index 0000000..9124ee0
--- /dev/null
@@ -0,0 +1,116 @@
+#big-cite {
+    background-color: white;
+    padding: 10.75em 10em 8.5em 18.2em;
+    margin: 0;
+}
+
+#big-cite a {
+    color: black;
+    display: block;
+}
+
+#big-cite h2 {
+    margin: 0;
+    font-size: 1.1em;
+    color: #575c63;
+}
+
+
+#big-cite-text {
+    margin: .05em;
+    font-size: 2.8em;
+    line-height: 1.2em;
+    color: #191919;
+}
+
+
+#big-cite-source {
+    color: #00a1ac;
+    margin: 0;
+    font-size: 1.1em;
+    margin: 1.1em 0.2em;
+}
+
+
+#promo-box {
+    float: right;
+    width: 32em;
+    margin-top: -5.1em;
+}
+#promo-box-header {
+    padding-top: 2em;
+    height: 3.1em;
+    padding-bottom: 0;
+    padding-left: 2.5em;
+    padding-right: 2.5em;
+    background: #191919;
+    color: white;
+}
+#promo-box-header h2 {
+    font-size: 1.1em;
+    padding-top: .1em;
+}
+#promo-box-body {
+    border-bottom: 2px solid #efefef;
+    padding: 2em 2.8em;
+    height: 30em;
+    background: #efefef;
+}
+#promo-box-title {
+    color: #02ADB7;
+    height: 2.75em;
+    margin: 0;
+}
+#promo-box-title span {
+    font-size: 1.1em;
+}
+#promo-box-body p {
+    margin-top: 0;
+}
+#promo-box-content {
+    font-size: 1.2em;
+    line-height: 1.55em;
+    color: #989898;
+}
+
+.main-last {
+    padding-top: 1.9em;
+    height: 3.2em;
+    padding-left: 1.9em;
+}
+.main-last span {
+    font-size: 1.1em;
+}
+
+
+.infopages-box {
+    width: 20.6em;
+    display: inline-block;
+    margin: .5em 0 0 0;
+    padding: 0 1.7em;
+    vertical-align: top;
+    color: #989898;
+}
+.infopages-box h2 {
+    color: #02ADB7;
+    height: 2.8em;
+    padding-top: 2.5em;
+}
+.infopages-box h2 span {
+    font-size: 1.1em;
+}
+.infopages-box a {
+    color: black;
+}
+
+.infopages-box ol, .infopages-box ul {
+    font-size: 1.1em;
+    list-style: none;
+    padding: 0;
+    margin: 0;
+    line-height: 1.45em;
+}
+
+.social-links {
+    margin-top: 1em;
+}
\ No newline at end of file
index e788e55..85f1a40 100644 (file)
@@ -413,6 +413,11 @@ p .ac_input {
     padding: 0 10px 0 10px;    
 }
 
+#formats .wrap div.download .custom-pdf {
+    text-align: left;
+}
+
+
 #czytamysluchajac {
     margin-top: 2.5em;
 }
@@ -1190,4 +1195,22 @@ div.shown-tags p, div.all-tags p {
 /* report */
 .stats td {
     vertical-align: top;
-}
\ No newline at end of file
+}
+
+/* ============ */
+/* = Pictures = */
+/* ============ */
+
+
+#picture-list .picture .title {
+    font-weight: bold;
+}
+
+#picture-list .picture {
+    background-color: white;
+    padding: 0.8em;
+    margin: 0.8em;
+    border: black 1px solid;
+    width: 600px;
+}
+
diff --git a/wolnelektury/static/css/picture_box.css b/wolnelektury/static/css/picture_box.css
new file mode 100755 (executable)
index 0000000..06f3e62
--- /dev/null
@@ -0,0 +1,90 @@
+.picture-mini-box, .picture-box {
+    display: inline-block;
+    margin: 0;
+    vertical-align: top;
+}
+
+
+.picture-box {
+    width: 37.5em;
+}
+
+.picture-mini-box {
+    width: 12.5em;
+}
+
+.picture-mini-box a, .picture-box-inner {
+    display: block;
+    color: black;
+    border: 1px solid #ddd;
+    height: 20em;
+    padding: .75em;
+    margin: .1em;
+    background: #fff;
+    -moz-box-shadow: 2px 2px 2px #ddd;
+    -webkit-box-shadow: 2px 2px 2px #ddd;
+    box-shadow: 2px 2px 2px #ddd;
+    overflow: hidden;
+}
+
+.picture-mini-box a {
+    height: 20em;
+    margin: .1em;
+}
+.picture-box-inner {
+    height: 14.4em;
+    margin: .5em;
+}
+
+.picture-mini-box img, .picture-box img {
+    width: 10.8em;
+    height: 14.4em;
+}
+.picture-mini-box img {
+    margin-bottom: .3em;
+}
+.picture-box img {
+    float: left;
+    margin-right: 1.5em;
+}
+
+.picture-mini-box .author {
+    color: #777;
+}
+
+
+.picture-box-body {
+    height: 13em;
+    overflow: hidden;
+}
+.picture-box-head {
+    min-height: 7em;
+}
+.picture-box-tag {
+    font-size: .8em;
+    margin-right: .5em;
+}
+.picture-box-download {
+    position: relative;
+}
+.picture-box-formats {
+    display: none;
+    top: -2em;
+    position: absolute;
+    height: 2em;
+    width: 100em;
+}
+.picture-box-formats a {
+    margin-right: 1em;
+}
+.picture-box-download:hover .picture-box-formats {
+    display: block;
+}
+
+.picture-box-tools a:before {
+    content: "⇩";
+    font-family: WL-Nav;
+    font-size: 2em;
+    margin-right: .25em;
+    vertical-align: middle;
+}
index a1798b1..1624f5f 100644 (file)
@@ -1,4 +1,5 @@
 .sponsors-page {
+    background: white;
     margin-top: 6px;
 }
 
@@ -7,6 +8,10 @@
     width: 150px;
 }
 
+.sponsor-logos {
+    height: 130px;
+}
+
 .sponsors-page img {
     float: left;
 }
diff --git a/wolnelektury/static/css/ui-lightness/images/ui-bg_diagonals-thick_18_b81900_40x40.png b/wolnelektury/static/css/ui-lightness/images/ui-bg_diagonals-thick_18_b81900_40x40.png
new file mode 100644 (file)
index 0000000..954e22d
Binary files /dev/null and b/wolnelektury/static/css/ui-lightness/images/ui-bg_diagonals-thick_18_b81900_40x40.png differ
diff --git a/wolnelektury/static/css/ui-lightness/images/ui-bg_diagonals-thick_20_666666_40x40.png b/wolnelektury/static/css/ui-lightness/images/ui-bg_diagonals-thick_20_666666_40x40.png
new file mode 100644 (file)
index 0000000..64ece57
Binary files /dev/null and b/wolnelektury/static/css/ui-lightness/images/ui-bg_diagonals-thick_20_666666_40x40.png differ
diff --git a/wolnelektury/static/css/ui-lightness/images/ui-bg_flat_10_000000_40x100.png b/wolnelektury/static/css/ui-lightness/images/ui-bg_flat_10_000000_40x100.png
new file mode 100644 (file)
index 0000000..abdc010
Binary files /dev/null and b/wolnelektury/static/css/ui-lightness/images/ui-bg_flat_10_000000_40x100.png differ
diff --git a/wolnelektury/static/css/ui-lightness/images/ui-bg_glass_100_f6f6f6_1x400.png b/wolnelektury/static/css/ui-lightness/images/ui-bg_glass_100_f6f6f6_1x400.png
new file mode 100644 (file)
index 0000000..9b383f4
Binary files /dev/null and b/wolnelektury/static/css/ui-lightness/images/ui-bg_glass_100_f6f6f6_1x400.png differ
diff --git a/wolnelektury/static/css/ui-lightness/images/ui-bg_glass_100_fdf5ce_1x400.png b/wolnelektury/static/css/ui-lightness/images/ui-bg_glass_100_fdf5ce_1x400.png
new file mode 100644 (file)
index 0000000..a23baad
Binary files /dev/null and b/wolnelektury/static/css/ui-lightness/images/ui-bg_glass_100_fdf5ce_1x400.png differ
diff --git a/wolnelektury/static/css/ui-lightness/images/ui-bg_glass_65_ffffff_1x400.png b/wolnelektury/static/css/ui-lightness/images/ui-bg_glass_65_ffffff_1x400.png
new file mode 100644 (file)
index 0000000..42ccba2
Binary files /dev/null and b/wolnelektury/static/css/ui-lightness/images/ui-bg_glass_65_ffffff_1x400.png differ
diff --git a/wolnelektury/static/css/ui-lightness/images/ui-bg_gloss-wave_35_f6a828_500x100.png b/wolnelektury/static/css/ui-lightness/images/ui-bg_gloss-wave_35_f6a828_500x100.png
new file mode 100644 (file)
index 0000000..1b1972b
Binary files /dev/null and b/wolnelektury/static/css/ui-lightness/images/ui-bg_gloss-wave_35_f6a828_500x100.png differ
diff --git a/wolnelektury/static/css/ui-lightness/images/ui-bg_highlight-soft_100_eeeeee_1x100.png b/wolnelektury/static/css/ui-lightness/images/ui-bg_highlight-soft_100_eeeeee_1x100.png
new file mode 100644 (file)
index 0000000..f127367
Binary files /dev/null and b/wolnelektury/static/css/ui-lightness/images/ui-bg_highlight-soft_100_eeeeee_1x100.png differ
diff --git a/wolnelektury/static/css/ui-lightness/images/ui-bg_highlight-soft_75_ffe45c_1x100.png b/wolnelektury/static/css/ui-lightness/images/ui-bg_highlight-soft_75_ffe45c_1x100.png
new file mode 100644 (file)
index 0000000..359397a
Binary files /dev/null and b/wolnelektury/static/css/ui-lightness/images/ui-bg_highlight-soft_75_ffe45c_1x100.png differ
diff --git a/wolnelektury/static/css/ui-lightness/images/ui-icons_222222_256x240.png b/wolnelektury/static/css/ui-lightness/images/ui-icons_222222_256x240.png
new file mode 100644 (file)
index 0000000..b273ff1
Binary files /dev/null and b/wolnelektury/static/css/ui-lightness/images/ui-icons_222222_256x240.png differ
diff --git a/wolnelektury/static/css/ui-lightness/images/ui-icons_228ef1_256x240.png b/wolnelektury/static/css/ui-lightness/images/ui-icons_228ef1_256x240.png
new file mode 100644 (file)
index 0000000..a641a37
Binary files /dev/null and b/wolnelektury/static/css/ui-lightness/images/ui-icons_228ef1_256x240.png differ
diff --git a/wolnelektury/static/css/ui-lightness/images/ui-icons_ef8c08_256x240.png b/wolnelektury/static/css/ui-lightness/images/ui-icons_ef8c08_256x240.png
new file mode 100644 (file)
index 0000000..85e63e9
Binary files /dev/null and b/wolnelektury/static/css/ui-lightness/images/ui-icons_ef8c08_256x240.png differ
diff --git a/wolnelektury/static/css/ui-lightness/images/ui-icons_ffd27a_256x240.png b/wolnelektury/static/css/ui-lightness/images/ui-icons_ffd27a_256x240.png
new file mode 100644 (file)
index 0000000..e117eff
Binary files /dev/null and b/wolnelektury/static/css/ui-lightness/images/ui-icons_ffd27a_256x240.png differ
diff --git a/wolnelektury/static/css/ui-lightness/images/ui-icons_ffffff_256x240.png b/wolnelektury/static/css/ui-lightness/images/ui-icons_ffffff_256x240.png
new file mode 100644 (file)
index 0000000..42f8f99
Binary files /dev/null and b/wolnelektury/static/css/ui-lightness/images/ui-icons_ffffff_256x240.png differ
diff --git a/wolnelektury/static/css/ui-lightness/jquery-ui-1.8.16.custom.css b/wolnelektury/static/css/ui-lightness/jquery-ui-1.8.16.custom.css
new file mode 100644 (file)
index 0000000..da10fff
--- /dev/null
@@ -0,0 +1,342 @@
+/*
+ * jQuery UI CSS Framework 1.8.16
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Theming/API
+ */
+
+/* Layout helpers
+----------------------------------*/
+.ui-helper-hidden { display: none; }
+.ui-helper-hidden-accessible { position: absolute !important; clip: rect(1px 1px 1px 1px); clip: rect(1px,1px,1px,1px); }
+.ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; }
+.ui-helper-clearfix:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; }
+.ui-helper-clearfix { display: inline-block; }
+/* required comment for clearfix to work in Opera \*/
+* html .ui-helper-clearfix { height:1%; }
+.ui-helper-clearfix { display:block; }
+/* end clearfix */
+.ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); }
+
+
+/* Interaction Cues
+----------------------------------*/
+.ui-state-disabled { cursor: default !important; }
+
+
+/* Icons
+----------------------------------*/
+
+/* states and images */
+.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; }
+
+
+/* Misc visuals
+----------------------------------*/
+
+/* Overlays */
+.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
+
+
+/*
+ * jQuery UI CSS Framework 1.8.16
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Theming/API
+ *
+ * To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Trebuchet%20MS,%20Tahoma,%20Verdana,%20Arial,%20sans-serif&fwDefault=bold&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=f6a828&bgTextureHeader=12_gloss_wave.png&bgImgOpacityHeader=35&borderColorHeader=e78f08&fcHeader=ffffff&iconColorHeader=ffffff&bgColorContent=eeeeee&bgTextureContent=03_highlight_soft.png&bgImgOpacityContent=100&borderColorContent=dddddd&fcContent=333333&iconColorContent=222222&bgColorDefault=f6f6f6&bgTextureDefault=02_glass.png&bgImgOpacityDefault=100&borderColorDefault=cccccc&fcDefault=1c94c4&iconColorDefault=ef8c08&bgColorHover=fdf5ce&bgTextureHover=02_glass.png&bgImgOpacityHover=100&borderColorHover=fbcb09&fcHover=c77405&iconColorHover=ef8c08&bgColorActive=ffffff&bgTextureActive=02_glass.png&bgImgOpacityActive=65&borderColorActive=fbd850&fcActive=eb8f00&iconColorActive=ef8c08&bgColorHighlight=ffe45c&bgTextureHighlight=03_highlight_soft.png&bgImgOpacityHighlight=75&borderColorHighlight=fed22f&fcHighlight=363636&iconColorHighlight=228ef1&bgColorError=b81900&bgTextureError=08_diagonals_thick.png&bgImgOpacityError=18&borderColorError=cd0a0a&fcError=ffffff&iconColorError=ffd27a&bgColorOverlay=666666&bgTextureOverlay=08_diagonals_thick.png&bgImgOpacityOverlay=20&opacityOverlay=50&bgColorShadow=000000&bgTextureShadow=01_flat.png&bgImgOpacityShadow=10&opacityShadow=20&thicknessShadow=5px&offsetTopShadow=-5px&offsetLeftShadow=-5px&cornerRadiusShadow=5px
+ */
+
+
+/* Component containers
+----------------------------------*/
+.ui-widget { font-family: Trebuchet MS, Tahoma, Verdana, Arial, sans-serif; font-size: 1.1em; }
+.ui-widget .ui-widget { font-size: 1em; }
+.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Trebuchet MS, Tahoma, Verdana, Arial, sans-serif; font-size: 1em; }
+.ui-widget-content { border: 1px solid #dddddd; background: #eeeeee url(images/ui-bg_highlight-soft_100_eeeeee_1x100.png) 50% top repeat-x; color: #333333; }
+.ui-widget-content a { color: #333333; }
+.ui-widget-header { border: 1px solid #e78f08; background: #f6a828 url(images/ui-bg_gloss-wave_35_f6a828_500x100.png) 50% 50% repeat-x; color: #ffffff; font-weight: bold; }
+.ui-widget-header a { color: #ffffff; }
+
+/* Interaction states
+----------------------------------*/
+.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #cccccc; background: #f6f6f6 url(images/ui-bg_glass_100_f6f6f6_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #1c94c4; }
+.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #1c94c4; text-decoration: none; }
+.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 1px solid #fbcb09; background: #fdf5ce url(images/ui-bg_glass_100_fdf5ce_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #c77405; }
+.ui-state-hover a, .ui-state-hover a:hover { color: #c77405; text-decoration: none; }
+.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { border: 1px solid #fbd850; background: #ffffff url(images/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #eb8f00; }
+.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #eb8f00; text-decoration: none; }
+.ui-widget :active { outline: none; }
+
+/* Interaction Cues
+----------------------------------*/
+.ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight  {border: 1px solid #fed22f; background: #ffe45c url(images/ui-bg_highlight-soft_75_ffe45c_1x100.png) 50% top repeat-x; color: #363636; }
+.ui-state-highlight a, .ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a { color: #363636; }
+.ui-state-error, .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error {border: 1px solid #cd0a0a; background: #b81900 url(images/ui-bg_diagonals-thick_18_b81900_40x40.png) 50% 50% repeat; color: #ffffff; }
+.ui-state-error a, .ui-widget-content .ui-state-error a, .ui-widget-header .ui-state-error a { color: #ffffff; }
+.ui-state-error-text, .ui-widget-content .ui-state-error-text, .ui-widget-header .ui-state-error-text { color: #ffffff; }
+.ui-priority-primary, .ui-widget-content .ui-priority-primary, .ui-widget-header .ui-priority-primary { font-weight: bold; }
+.ui-priority-secondary, .ui-widget-content .ui-priority-secondary,  .ui-widget-header .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; }
+.ui-state-disabled, .ui-widget-content .ui-state-disabled, .ui-widget-header .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; }
+
+/* Icons
+----------------------------------*/
+
+/* states and images */
+.ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_222222_256x240.png); }
+.ui-widget-content .ui-icon {background-image: url(images/ui-icons_222222_256x240.png); }
+.ui-widget-header .ui-icon {background-image: url(images/ui-icons_ffffff_256x240.png); }
+.ui-state-default .ui-icon { background-image: url(images/ui-icons_ef8c08_256x240.png); }
+.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_ef8c08_256x240.png); }
+.ui-state-active .ui-icon {background-image: url(images/ui-icons_ef8c08_256x240.png); }
+.ui-state-highlight .ui-icon {background-image: url(images/ui-icons_228ef1_256x240.png); }
+.ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_ffd27a_256x240.png); }
+
+/* positioning */
+.ui-icon-carat-1-n { background-position: 0 0; }
+.ui-icon-carat-1-ne { background-position: -16px 0; }
+.ui-icon-carat-1-e { background-position: -32px 0; }
+.ui-icon-carat-1-se { background-position: -48px 0; }
+.ui-icon-carat-1-s { background-position: -64px 0; }
+.ui-icon-carat-1-sw { background-position: -80px 0; }
+.ui-icon-carat-1-w { background-position: -96px 0; }
+.ui-icon-carat-1-nw { background-position: -112px 0; }
+.ui-icon-carat-2-n-s { background-position: -128px 0; }
+.ui-icon-carat-2-e-w { background-position: -144px 0; }
+.ui-icon-triangle-1-n { background-position: 0 -16px; }
+.ui-icon-triangle-1-ne { background-position: -16px -16px; }
+.ui-icon-triangle-1-e { background-position: -32px -16px; }
+.ui-icon-triangle-1-se { background-position: -48px -16px; }
+.ui-icon-triangle-1-s { background-position: -64px -16px; }
+.ui-icon-triangle-1-sw { background-position: -80px -16px; }
+.ui-icon-triangle-1-w { background-position: -96px -16px; }
+.ui-icon-triangle-1-nw { background-position: -112px -16px; }
+.ui-icon-triangle-2-n-s { background-position: -128px -16px; }
+.ui-icon-triangle-2-e-w { background-position: -144px -16px; }
+.ui-icon-arrow-1-n { background-position: 0 -32px; }
+.ui-icon-arrow-1-ne { background-position: -16px -32px; }
+.ui-icon-arrow-1-e { background-position: -32px -32px; }
+.ui-icon-arrow-1-se { background-position: -48px -32px; }
+.ui-icon-arrow-1-s { background-position: -64px -32px; }
+.ui-icon-arrow-1-sw { background-position: -80px -32px; }
+.ui-icon-arrow-1-w { background-position: -96px -32px; }
+.ui-icon-arrow-1-nw { background-position: -112px -32px; }
+.ui-icon-arrow-2-n-s { background-position: -128px -32px; }
+.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; }
+.ui-icon-arrow-2-e-w { background-position: -160px -32px; }
+.ui-icon-arrow-2-se-nw { background-position: -176px -32px; }
+.ui-icon-arrowstop-1-n { background-position: -192px -32px; }
+.ui-icon-arrowstop-1-e { background-position: -208px -32px; }
+.ui-icon-arrowstop-1-s { background-position: -224px -32px; }
+.ui-icon-arrowstop-1-w { background-position: -240px -32px; }
+.ui-icon-arrowthick-1-n { background-position: 0 -48px; }
+.ui-icon-arrowthick-1-ne { background-position: -16px -48px; }
+.ui-icon-arrowthick-1-e { background-position: -32px -48px; }
+.ui-icon-arrowthick-1-se { background-position: -48px -48px; }
+.ui-icon-arrowthick-1-s { background-position: -64px -48px; }
+.ui-icon-arrowthick-1-sw { background-position: -80px -48px; }
+.ui-icon-arrowthick-1-w { background-position: -96px -48px; }
+.ui-icon-arrowthick-1-nw { background-position: -112px -48px; }
+.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; }
+.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; }
+.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; }
+.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; }
+.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; }
+.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; }
+.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; }
+.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; }
+.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; }
+.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; }
+.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; }
+.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; }
+.ui-icon-arrowreturn-1-w { background-position: -64px -64px; }
+.ui-icon-arrowreturn-1-n { background-position: -80px -64px; }
+.ui-icon-arrowreturn-1-e { background-position: -96px -64px; }
+.ui-icon-arrowreturn-1-s { background-position: -112px -64px; }
+.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; }
+.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; }
+.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; }
+.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; }
+.ui-icon-arrow-4 { background-position: 0 -80px; }
+.ui-icon-arrow-4-diag { background-position: -16px -80px; }
+.ui-icon-extlink { background-position: -32px -80px; }
+.ui-icon-newwin { background-position: -48px -80px; }
+.ui-icon-refresh { background-position: -64px -80px; }
+.ui-icon-shuffle { background-position: -80px -80px; }
+.ui-icon-transfer-e-w { background-position: -96px -80px; }
+.ui-icon-transferthick-e-w { background-position: -112px -80px; }
+.ui-icon-folder-collapsed { background-position: 0 -96px; }
+.ui-icon-folder-open { background-position: -16px -96px; }
+.ui-icon-document { background-position: -32px -96px; }
+.ui-icon-document-b { background-position: -48px -96px; }
+.ui-icon-note { background-position: -64px -96px; }
+.ui-icon-mail-closed { background-position: -80px -96px; }
+.ui-icon-mail-open { background-position: -96px -96px; }
+.ui-icon-suitcase { background-position: -112px -96px; }
+.ui-icon-comment { background-position: -128px -96px; }
+.ui-icon-person { background-position: -144px -96px; }
+.ui-icon-print { background-position: -160px -96px; }
+.ui-icon-trash { background-position: -176px -96px; }
+.ui-icon-locked { background-position: -192px -96px; }
+.ui-icon-unlocked { background-position: -208px -96px; }
+.ui-icon-bookmark { background-position: -224px -96px; }
+.ui-icon-tag { background-position: -240px -96px; }
+.ui-icon-home { background-position: 0 -112px; }
+.ui-icon-flag { background-position: -16px -112px; }
+.ui-icon-calendar { background-position: -32px -112px; }
+.ui-icon-cart { background-position: -48px -112px; }
+.ui-icon-pencil { background-position: -64px -112px; }
+.ui-icon-clock { background-position: -80px -112px; }
+.ui-icon-disk { background-position: -96px -112px; }
+.ui-icon-calculator { background-position: -112px -112px; }
+.ui-icon-zoomin { background-position: -128px -112px; }
+.ui-icon-zoomout { background-position: -144px -112px; }
+.ui-icon-search { background-position: -160px -112px; }
+.ui-icon-wrench { background-position: -176px -112px; }
+.ui-icon-gear { background-position: -192px -112px; }
+.ui-icon-heart { background-position: -208px -112px; }
+.ui-icon-star { background-position: -224px -112px; }
+.ui-icon-link { background-position: -240px -112px; }
+.ui-icon-cancel { background-position: 0 -128px; }
+.ui-icon-plus { background-position: -16px -128px; }
+.ui-icon-plusthick { background-position: -32px -128px; }
+.ui-icon-minus { background-position: -48px -128px; }
+.ui-icon-minusthick { background-position: -64px -128px; }
+.ui-icon-close { background-position: -80px -128px; }
+.ui-icon-closethick { background-position: -96px -128px; }
+.ui-icon-key { background-position: -112px -128px; }
+.ui-icon-lightbulb { background-position: -128px -128px; }
+.ui-icon-scissors { background-position: -144px -128px; }
+.ui-icon-clipboard { background-position: -160px -128px; }
+.ui-icon-copy { background-position: -176px -128px; }
+.ui-icon-contact { background-position: -192px -128px; }
+.ui-icon-image { background-position: -208px -128px; }
+.ui-icon-video { background-position: -224px -128px; }
+.ui-icon-script { background-position: -240px -128px; }
+.ui-icon-alert { background-position: 0 -144px; }
+.ui-icon-info { background-position: -16px -144px; }
+.ui-icon-notice { background-position: -32px -144px; }
+.ui-icon-help { background-position: -48px -144px; }
+.ui-icon-check { background-position: -64px -144px; }
+.ui-icon-bullet { background-position: -80px -144px; }
+.ui-icon-radio-off { background-position: -96px -144px; }
+.ui-icon-radio-on { background-position: -112px -144px; }
+.ui-icon-pin-w { background-position: -128px -144px; }
+.ui-icon-pin-s { background-position: -144px -144px; }
+.ui-icon-play { background-position: 0 -160px; }
+.ui-icon-pause { background-position: -16px -160px; }
+.ui-icon-seek-next { background-position: -32px -160px; }
+.ui-icon-seek-prev { background-position: -48px -160px; }
+.ui-icon-seek-end { background-position: -64px -160px; }
+.ui-icon-seek-start { background-position: -80px -160px; }
+/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */
+.ui-icon-seek-first { background-position: -80px -160px; }
+.ui-icon-stop { background-position: -96px -160px; }
+.ui-icon-eject { background-position: -112px -160px; }
+.ui-icon-volume-off { background-position: -128px -160px; }
+.ui-icon-volume-on { background-position: -144px -160px; }
+.ui-icon-power { background-position: 0 -176px; }
+.ui-icon-signal-diag { background-position: -16px -176px; }
+.ui-icon-signal { background-position: -32px -176px; }
+.ui-icon-battery-0 { background-position: -48px -176px; }
+.ui-icon-battery-1 { background-position: -64px -176px; }
+.ui-icon-battery-2 { background-position: -80px -176px; }
+.ui-icon-battery-3 { background-position: -96px -176px; }
+.ui-icon-circle-plus { background-position: 0 -192px; }
+.ui-icon-circle-minus { background-position: -16px -192px; }
+.ui-icon-circle-close { background-position: -32px -192px; }
+.ui-icon-circle-triangle-e { background-position: -48px -192px; }
+.ui-icon-circle-triangle-s { background-position: -64px -192px; }
+.ui-icon-circle-triangle-w { background-position: -80px -192px; }
+.ui-icon-circle-triangle-n { background-position: -96px -192px; }
+.ui-icon-circle-arrow-e { background-position: -112px -192px; }
+.ui-icon-circle-arrow-s { background-position: -128px -192px; }
+.ui-icon-circle-arrow-w { background-position: -144px -192px; }
+.ui-icon-circle-arrow-n { background-position: -160px -192px; }
+.ui-icon-circle-zoomin { background-position: -176px -192px; }
+.ui-icon-circle-zoomout { background-position: -192px -192px; }
+.ui-icon-circle-check { background-position: -208px -192px; }
+.ui-icon-circlesmall-plus { background-position: 0 -208px; }
+.ui-icon-circlesmall-minus { background-position: -16px -208px; }
+.ui-icon-circlesmall-close { background-position: -32px -208px; }
+.ui-icon-squaresmall-plus { background-position: -48px -208px; }
+.ui-icon-squaresmall-minus { background-position: -64px -208px; }
+.ui-icon-squaresmall-close { background-position: -80px -208px; }
+.ui-icon-grip-dotted-vertical { background-position: 0 -224px; }
+.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; }
+.ui-icon-grip-solid-vertical { background-position: -32px -224px; }
+.ui-icon-grip-solid-horizontal { background-position: -48px -224px; }
+.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }
+.ui-icon-grip-diagonal-se { background-position: -80px -224px; }
+
+
+/* Misc visuals
+----------------------------------*/
+
+/* Corner radius */
+.ui-corner-all, .ui-corner-top, .ui-corner-left, .ui-corner-tl { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; -khtml-border-top-left-radius: 4px; border-top-left-radius: 4px; }
+.ui-corner-all, .ui-corner-top, .ui-corner-right, .ui-corner-tr { -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; -khtml-border-top-right-radius: 4px; border-top-right-radius: 4px; }
+.ui-corner-all, .ui-corner-bottom, .ui-corner-left, .ui-corner-bl { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; -khtml-border-bottom-left-radius: 4px; border-bottom-left-radius: 4px; }
+.ui-corner-all, .ui-corner-bottom, .ui-corner-right, .ui-corner-br { -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; -khtml-border-bottom-right-radius: 4px; border-bottom-right-radius: 4px; }
+
+/* Overlays */
+.ui-widget-overlay { background: #666666 url(images/ui-bg_diagonals-thick_20_666666_40x40.png) 50% 50% repeat; opacity: .50;filter:Alpha(Opacity=50); }
+.ui-widget-shadow { margin: -5px 0 0 -5px; padding: 5px; background: #000000 url(images/ui-bg_flat_10_000000_40x100.png) 50% 50% repeat-x; opacity: .20;filter:Alpha(Opacity=20); -moz-border-radius: 5px; -khtml-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; }/*
+ * jQuery UI Autocomplete 1.8.16
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Autocomplete#theming
+ */
+.ui-autocomplete { position: absolute; cursor: default; }      
+
+/* workarounds */
+* html .ui-autocomplete { width:1px; } /* without this, the menu expands to 100% in IE6 */
+
+/*
+ * jQuery UI Menu 1.8.16
+ *
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Menu#theming
+ */
+.ui-menu {
+       list-style:none;
+       padding: 2px;
+       margin: 0;
+       display:block;
+       float: left;
+}
+.ui-menu .ui-menu {
+       margin-top: -3px;
+}
+.ui-menu .ui-menu-item {
+       margin:0;
+       padding: 0;
+       zoom: 1;
+       float: left;
+       clear: left;
+       width: 100%;
+}
+.ui-menu .ui-menu-item a {
+       text-decoration:none;
+       display:block;
+       padding:.2em .4em;
+       line-height:1.5;
+       zoom:1;
+}
+.ui-menu .ui-menu-item a.ui-state-hover,
+.ui-menu .ui-menu-item a.ui-state-active {
+       font-weight: normal;
+       margin: -1px;
+}
diff --git a/wolnelektury/static/fonts/WL-Nav.ttf b/wolnelektury/static/fonts/WL-Nav.ttf
new file mode 100644 (file)
index 0000000..e73dedf
Binary files /dev/null and b/wolnelektury/static/fonts/WL-Nav.ttf differ
diff --git a/wolnelektury/static/fonts/WL.eot b/wolnelektury/static/fonts/WL.eot
new file mode 100644 (file)
index 0000000..53fedbd
Binary files /dev/null and b/wolnelektury/static/fonts/WL.eot differ
diff --git a/wolnelektury/static/fonts/WL.ttf b/wolnelektury/static/fonts/WL.ttf
new file mode 100644 (file)
index 0000000..6a8934a
Binary files /dev/null and b/wolnelektury/static/fonts/WL.ttf differ
diff --git a/wolnelektury/static/img/bg-header.png b/wolnelektury/static/img/bg-header.png
new file mode 100644 (file)
index 0000000..f7e572e
Binary files /dev/null and b/wolnelektury/static/img/bg-header.png differ
diff --git a/wolnelektury/static/jplayer/Jplayer.swf b/wolnelektury/static/jplayer/Jplayer.swf
new file mode 100644 (file)
index 0000000..4d50c86
Binary files /dev/null and b/wolnelektury/static/jplayer/Jplayer.swf differ
diff --git a/wolnelektury/static/jplayer/jplayer.blue.monday.css b/wolnelektury/static/jplayer/jplayer.blue.monday.css
new file mode 100644 (file)
index 0000000..0d90a22
--- /dev/null
@@ -0,0 +1,623 @@
+/*\r
+ * Skin for jPlayer Plugin (jQuery JavaScript Library)\r
+ * http://www.happyworm.com/jquery/jplayer\r
+ *\r
+ * Skin Name: Blue Monday\r
+ *\r
+ * Copyright (c) 2010-2011 Happyworm Ltd\r
+ * Dual licensed under the MIT and GPL licenses.\r
+ *  - http://www.opensource.org/licenses/mit-license.php\r
+ *  - http://www.gnu.org/copyleft/gpl.html\r
+ *\r
+ * Author: Silvia Benvenuti\r
+ * Skin Version: 4.0 (jPlayer 2.1.0)\r
+ * Date: 1st September 2011\r
+ */\r
+\r
+div.jp-audio,\r
+div.jp-video {\r
+\r
+       /* Edit the font-size to counteract inherited font sizing.\r
+        * Eg. 1.25em = 1 / 0.8em\r
+        */\r
+\r
+       font-size:1.25em; /* 1.25em for testing in site pages */ /* No parent CSS that can effect the size in the demos ZIP */\r
+\r
+       font-family:Verdana, Arial, sans-serif;\r
+       line-height:1.6;\r
+       color: #666;\r
+       border:1px solid #009be3;\r
+       background-color:#eee;\r
+       position:relative;\r
+}\r
+div.jp-audio {\r
+       width:420px;\r
+}\r
+div.jp-video-270p {\r
+       width:480px;\r
+}\r
+div.jp-video-360p {\r
+       width:640px;\r
+}\r
+div.jp-video-full {\r
+       /* Rules for IE6 (full-screen) */\r
+       width:480px;\r
+       height:270px;\r
+       /* Rules for IE7 (full-screen) - Otherwise the relative container causes other page items that are not position:static (default) to appear over the video/gui. */\r
+       position:static !important; position:relative\r
+}\r
+\r
+div.jp-video-full div.jp-jplayer {\r
+       top: 0;\r
+       left: 0;\r
+       position: fixed !important; position: relative; /* Rules for IE6 (full-screen) */\r
+       overflow: hidden;\r
+       z-index:1000;\r
+}\r
+\r
+div.jp-video-full div.jp-gui {\r
+       position: fixed !important; position: static; /* Rules for IE6 (full-screen) */\r
+       top: 0;\r
+       left: 0;\r
+       width:100%;\r
+       height:100%;\r
+       z-index:1000;\r
+}\r
+\r
+div.jp-video-full div.jp-interface {\r
+       position: absolute !important; position: relative; /* Rules for IE6 (full-screen) */\r
+       bottom: 0;\r
+       left: 0;\r
+       z-index:1000;\r
+}\r
+\r
+div.jp-interface {\r
+       position: relative;\r
+       background-color:#eee;\r
+       width:100%;\r
+}\r
+\r
+div.jp-audio div.jp-type-single div.jp-interface {\r
+       height:80px;\r
+}\r
+div.jp-audio div.jp-type-playlist div.jp-interface {\r
+       height:80px;\r
+}\r
+\r
+div.jp-video div.jp-interface {\r
+       border-top:1px solid #009be3;\r
+}\r
+\r
+/* @group CONTROLS */\r
+\r
+div.jp-controls-holder {\r
+       clear: both;\r
+       width:440px;\r
+       margin:0 auto;\r
+       position: relative;\r
+       overflow:hidden;\r
+       top:-8px; /* This negative value depends on the size of the text in jp-currentTime and jp-duration */\r
+}\r
+\r
+div.jp-interface ul.jp-controls {\r
+       list-style-type:none;\r
+       margin:0;\r
+       padding: 0;
+       overflow:hidden;
+}
+
+div.jp-audio ul.jp-controls {
+       width: 380px;\r
+       padding:20px 20px 0 20px;\r
+}
+
+div.jp-video div.jp-type-single ul.jp-controls {
+       width: 78px;\r
+       margin-left: 200px;\r
+}
+
+div.jp-video div.jp-type-playlist ul.jp-controls {
+       width: 134px;\r
+       margin-left: 172px;\r
+}
+div.jp-video ul.jp-controls,\r
+div.jp-interface ul.jp-controls li {\r
+       display:inline;
+       float: left;
+}\r
+\r
+div.jp-interface ul.jp-controls a {\r
+       display:block;\r
+       overflow:hidden;\r
+       text-indent:-9999px;\r
+}\r
+a.jp-play,\r
+a.jp-pause {\r
+       width:40px;\r
+       height:40px;\r
+}\r
+\r
+a.jp-play {\r
+       background: url("/static/jplayer/jplayer.blue.monday.jpg") 0 0 no-repeat;\r
+}\r
+a.jp-play:hover {\r
+       background: url("/static/jplayer/jplayer.blue.monday.jpg") -41px 0 no-repeat;\r
+}\r
+a.jp-pause {\r
+       background: url("/static/jplayer/jplayer.blue.monday.jpg") 0 -42px no-repeat;\r
+       display: none;\r
+}\r
+a.jp-pause:hover {\r
+       background: url("/static/jplayer/jplayer.blue.monday.jpg") -41px -42px no-repeat;\r
+}\r
+
+a.jp-stop, a.jp-previous, a.jp-next {\r
+       width:28px;\r
+       height:28px;
+       margin-top:6px;\r
+}\r
+
+a.jp-stop {\r
+       background: url("/static/jplayer/jplayer.blue.monday.jpg") 0 -83px no-repeat;\r
+       margin-left:10px;
+}
+\r
+a.jp-stop:hover {\r
+       background: url("/static/jplayer/jplayer.blue.monday.jpg") -29px -83px no-repeat;\r
+}
+\r
+a.jp-previous {\r
+       background: url("/static/jplayer/jplayer.blue.monday.jpg") 0 -112px no-repeat;\r
+}\r
+a.jp-previous:hover {\r
+       background: url("/static/jplayer/jplayer.blue.monday.jpg") -29px -112px no-repeat;\r
+}
+
+a.jp-next {\r
+       background: url("/static/jplayer/jplayer.blue.monday.jpg") 0 -141px no-repeat;\r
+}\r
+a.jp-next:hover {\r
+       background: url("/static/jplayer/jplayer.blue.monday.jpg") -29px -141px no-repeat;\r
+}\r
+\r
+/* @end */\r
+\r
+/* @group progress bar */\r
+\r
+div.jp-progress {\r
+       overflow:hidden;\r
+       background-color: #ddd;\r
+}\r
+div.jp-audio div.jp-progress {\r
+       position: absolute;\r
+       top:32px;\r
+       height:15px;\r
+}\r
+div.jp-audio div.jp-type-single div.jp-progress {\r
+       left:110px;\r
+       width:186px;\r
+}\r
+div.jp-audio div.jp-type-playlist div.jp-progress {\r
+       left:166px;\r
+       width:130px;\r
+}\r
+div.jp-video div.jp-progress {\r
+       top:0px;\r
+       left:0px;\r
+       width:100%;\r
+       height:10px;\r
+}\r
+div.jp-seek-bar {\r
+       background: url("/static/jplayer/jplayer.blue.monday.jpg") 0 -202px repeat-x;\r
+       width:0px;\r
+       height:100%;\r
+       cursor: pointer;\r
+}\r
+div.jp-play-bar {\r
+       background: url("/static/jplayer/jplayer.blue.monday.jpg") 0 -218px repeat-x ;\r
+       width:0px;\r
+       height:100%;\r
+}\r
+\r
+/* The seeking class is added/removed inside jPlayer */\r
+div.jp-seeking-bg {\r
+       background: url("/static/jplayer/jplayer.blue.monday.seeking.gif");\r
+}\r
+\r
+/* @end */\r
+\r
+/* @group volume controls */\r
+\r
+\r
+a.jp-mute,\r
+a.jp-unmute,\r
+a.jp-volume-max {\r
+       width:18px;\r
+       height:15px;
+       margin-top:12px;
+}
+
+div.jp-audio div.jp-type-single a.jp-mute,\r
+div.jp-audio div.jp-type-single a.jp-unmute {
+       margin-left: 210px;     
+}
+
+div.jp-audio div.jp-type-playlist a.jp-mute,\r
+div.jp-audio div.jp-type-playlist a.jp-unmute {
+       margin-left: 154px;\r
+}
+\r
+div.jp-audio a.jp-volume-max {\r
+       margin-left: 56px;      \r
+}\r
+\r
+div.jp-video a.jp-mute,\r
+div.jp-video a.jp-unmute,\r
+div.jp-video a.jp-volume-max {\r
+       position: absolute;\r
+       top:12px;\r
+       margin-top:0;\r
+}\r
+\r
+div.jp-video a.jp-mute,\r
+div.jp-video a.jp-unmute {
+       left: 50px;\r
+}
+\r\r
+div.jp-video a.jp-volume-max {\r
+       left: 134px;\r
+}\r
+\r
+a.jp-mute {\r
+       background: url("/static/jplayer/jplayer.blue.monday.jpg") 0 -170px no-repeat;\r
+}\r
+a.jp-mute:hover {\r
+       background: url("/static/jplayer/jplayer.blue.monday.jpg") -19px -170px no-repeat;\r
+}\r
+a.jp-unmute {\r
+       background: url("/static/jplayer/jplayer.blue.monday.jpg") -60px -170px no-repeat;\r
+       display: none;\r
+}\r
+a.jp-unmute:hover {\r
+       background: url("/static/jplayer/jplayer.blue.monday.jpg") -79px -170px no-repeat;\r
+}
+\ra.jp-volume-max {\r
+       background: url("/static/jplayer/jplayer.blue.monday.jpg") 0 -186px no-repeat;\r
+}\r
+a.jp-volume-max:hover {\r
+       background: url("/static/jplayer/jplayer.blue.monday.jpg") -19px -186px no-repeat;\r
+}\r
+
+div.jp-volume-bar {\r
+       position: absolute;\r
+       overflow:hidden;\r
+       background: url("/static/jplayer/jplayer.blue.monday.jpg") 0 -250px repeat-x;\r
+       width:46px;\r
+       height:5px;\r
+       cursor: pointer;\r
+}\r
+div.jp-audio div.jp-volume-bar {\r
+       top:37px;\r
+       left:330px;\r
+}\r
+div.jp-video div.jp-volume-bar {\r
+       top:17px;\r
+       left:72px;\r
+}\r
+div.jp-volume-bar-value {\r
+       background: url("/static/jplayer/jplayer.blue.monday.jpg") 0 -256px repeat-x;\r
+       width:0px;\r
+       height:5px;\r
+}\r
+\r
+/* @end */\r
+\r
+/* @group current time and duration */\r
+\r
+div.jp-audio div.jp-time-holder {\r
+       position:absolute;\r
+       top:50px;\r
+}\r
+div.jp-audio div.jp-type-single div.jp-time-holder {\r
+       left:110px;\r
+       width:186px;\r
+}\r
+div.jp-audio div.jp-type-playlist div.jp-time-holder {\r
+       left:166px;\r
+       width:130px;\r
+}\r
+\r
+div.jp-current-time,\r
+div.jp-duration {\r
+       width:60px;\r
+       font-size:.64em;\r
+       font-style:oblique;
+}\r
+div.jp-current-time {\r
+       float: left;\r
+       display:inline;\r
+}\r
+div.jp-duration {\r
+       float: right;\r
+       display:inline;\r
+       text-align: right;\r
+}\r
+\r
+div.jp-video div.jp-current-time {\r
+       margin-left:20px;\r
+}\r
+div.jp-video div.jp-duration {\r
+       margin-right:20px;\r
+}\r
+\r
+/* @end */\r
+\r
+/* @group playlist */\r
+\r
+div.jp-title {\r
+       font-weight:bold;\r
+       text-align:center;\r
+}\r
+\r
+div.jp-title,\r
+div.jp-playlist {\r
+       width:100%;\r
+       background-color:#ccc;\r
+       border-top:1px solid #009be3;\r
+}\r
+div.jp-type-single div.jp-title,\r
+div.jp-type-playlist div.jp-title,\r
+div.jp-type-single div.jp-playlist {\r
+       border-top:none;\r
+}\r
+div.jp-title ul,\r
+div.jp-playlist ul {\r
+       list-style-type:none;\r
+       margin:0;\r
+       padding:0 20px;\r
+       font-size:.72em;\r
+}\r
+\r
+div.jp-title li {\r
+       padding:5px 0;\r
+       font-weight:bold;\r
+}\r
+div.jp-playlist li {\r
+       padding:5px 0 4px 20px;\r
+       border-bottom:1px solid #eee;\r
+}\r
+\r
+div.jp-playlist li div {\r
+       display:inline;\r
+}\r
+\r
+/* Note that the first-child (IE6) and last-child (IE6/7/8) selectors do not work on IE */\r
+\r
+div.jp-type-playlist div.jp-playlist li:last-child {\r
+       padding:5px 0 5px 20px;\r
+       border-bottom:none;\r
+}\r
+div.jp-type-playlist div.jp-playlist li.jp-playlist-current {\r
+       list-style-type:square;\r
+       list-style-position:inside;\r
+       padding-left:7px;\r
+}\r
+div.jp-type-playlist div.jp-playlist a {\r
+       color: #333;\r
+       text-decoration: none;\r
+}\r
+div.jp-type-playlist div.jp-playlist a:hover {\r
+       color:#0d88c1;\r
+}\r
+div.jp-type-playlist div.jp-playlist a.jp-playlist-current {\r
+       color:#0d88c1;\r
+}\r
+\r
+div.jp-type-playlist div.jp-playlist a.jp-playlist-item-remove {\r
+       float:right;\r
+       display:inline;\r
+       text-align:right;\r
+       margin-right:10px;\r
+       font-weight:bold;\r
+       color:#666;\r
+}\r
+div.jp-type-playlist div.jp-playlist a.jp-playlist-item-remove:hover {\r
+       color:#0d88c1;\r
+}\r
+div.jp-type-playlist div.jp-playlist span.jp-free-media {\r
+       float:right;\r
+       display:inline;\r
+       text-align:right;\r
+       margin-right:10px;\r
+}\r
+div.jp-type-playlist div.jp-playlist span.jp-free-media a{\r
+       color:#666;\r
+}\r
+div.jp-type-playlist div.jp-playlist span.jp-free-media a:hover{\r
+       color:#0d88c1;\r
+}\r
+span.jp-artist {\r
+       font-size:.8em;\r
+       color:#666;\r
+}\r
+\r
+/* @end */\r
+\r
+div.jp-video-play {\r
+       position:absolute;\r
+       top:0;\r
+       left:0;\r
+       width:100%;\r
+       cursor:pointer;\r
+       background-color:rgba(0,0,0,0); /* Makes IE9 work with the active area over the whole video area. IE6/7/8 only have the button as active area. */\r
+}\r
+div.jp-video-270p div.jp-video-play {\r
+       height:270px;\r
+}\r
+div.jp-video-360p div.jp-video-play {\r
+       height:360px;\r
+}\r
+div.jp-video-full div.jp-video-play {\r
+       height:100%;\r
+       z-index:1000;\r
+}\r
+a.jp-video-play-icon {\r
+       position:relative;\r
+       display:block;\r
+       width: 112px;\r
+       height: 100px;\r
+\r
+       margin-left:-56px;\r
+       margin-top:-50px;\r
+       left:50%;\r
+       top:50%;\r
+\r
+       background: url("/static/jplayer/jplayer.blue.monday.video.play.png") 0 0 no-repeat;\r
+       text-indent:-9999px;\r
+}\r
+div.jp-video-play:hover a.jp-video-play-icon {\r
+       background: url("/static/jplayer/jplayer.blue.monday.video.play.png") 0 -100px no-repeat;\r
+}\r
+\r
+\r
+\r
+\r
+\r
+div.jp-jplayer audio,\r
+div.jp-jplayer {\r
+       width:0px;\r
+       height:0px;\r
+}\r
+\r
+div.jp-jplayer {\r
+       background-color: #000000;\r
+}
+
+
+
+
+
+/* @group TOGGLES */
+\r
+/* The audio toggles are nested inside jp-time-holder */\r
+
+ul.jp-toggles {
+       list-style-type:none;
+       padding:0;
+       margin:0 auto;\r
+       overflow:hidden;
+}
+
+div.jp-audio .jp-type-single ul.jp-toggles {
+       width:25px;
+}
+div.jp-audio .jp-type-playlist ul.jp-toggles {\r
+       width:55px;\r
+       margin: 0;\r
+       position: absolute;\r
+       left: 325px;\r
+       top: 50px;\r
+}\r
+
+div.jp-video ul.jp-toggles {
+       margin-top:10px;\r
+       width:100px;\r
+}
+
+ul.jp-toggles li {
+       display:block;
+       float:right;
+}
+
+ul.jp-toggles li a {
+       display:block;
+       width:25px;
+       height:18px;
+       text-indent:-9999px;\r
+       line-height:100%; /* need this for IE6 */
+}
+
+a.jp-full-screen {
+       background: url("/static/jplayer/jplayer.blue.monday.jpg") 0 -310px no-repeat;
+       margin-left: 20px;
+}
+
+a.jp-full-screen:hover {
+       background: url("/static/jplayer/jplayer.blue.monday.jpg") -30px -310px no-repeat;
+}
+
+a.jp-restore-screen {
+       background: url("/static/jplayer/jplayer.blue.monday.jpg") -60px -310px no-repeat;
+       margin-left: 20px;\r
+}
+
+a.jp-restore-screen:hover {
+       background: url("/static/jplayer/jplayer.blue.monday.jpg") -90px -310px no-repeat;
+}
+
+a.jp-repeat {
+       background: url("/static/jplayer/jplayer.blue.monday.jpg") 0 -290px no-repeat;
+}
+
+a.jp-repeat:hover {
+       background: url("/static/jplayer/jplayer.blue.monday.jpg") -30px -290px no-repeat;
+}
+
+a.jp-repeat-off {
+       background: url("/static/jplayer/jplayer.blue.monday.jpg") -60px -290px no-repeat;
+}
+
+a.jp-repeat-off:hover {
+       background: url("/static/jplayer/jplayer.blue.monday.jpg") -90px -290px no-repeat;
+}
+
+a.jp-shuffle {
+       background: url("/static/jplayer/jplayer.blue.monday.jpg") 0 -270px no-repeat;
+       margin-left: 5px;
+}
+
+a.jp-shuffle:hover {
+       background: url("/static/jplayer/jplayer.blue.monday.jpg") -30px -270px no-repeat;
+}
+
+a.jp-shuffle-off {
+       background: url("/static/jplayer/jplayer.blue.monday.jpg") -60px -270px no-repeat;
+       margin-left: 5px;\r
+}
+
+a.jp-shuffle-off:hover {
+       background: url("/static/jplayer/jplayer.blue.monday.jpg") -90px -270px no-repeat;
+}
+
+
+/* @end */\r
+\r
+/* @group NO SOLUTION error feedback */\r
+\r
+.jp-no-solution {\r
+       position:absolute;\r
+       width:390px;\r
+       margin-left:-202px;\r
+       left:50%;\r
+       top: 10px;\r
+\r
+       padding:5px;\r
+       font-size:.8em;\r
+       background-color:#eee;\r
+       border:2px solid #009be3;\r
+       color:#000;\r
+       display:none;\r
+}\r
+\r
+.jp-no-solution a {\r
+       color:#000;\r
+}\r
+\r
+.jp-no-solution span {\r
+       font-size:1em;\r
+       display:block;\r
+       text-align:center;\r
+       font-weight:bold;\r
+}\r
+\r
+/* @end */\r
diff --git a/wolnelektury/static/jplayer/jplayer.blue.monday.jpg b/wolnelektury/static/jplayer/jplayer.blue.monday.jpg
new file mode 100644 (file)
index 0000000..adab53f
Binary files /dev/null and b/wolnelektury/static/jplayer/jplayer.blue.monday.jpg differ
diff --git a/wolnelektury/static/jplayer/jplayer.blue.monday.seeking.gif b/wolnelektury/static/jplayer/jplayer.blue.monday.seeking.gif
new file mode 100644 (file)
index 0000000..dbd2105
Binary files /dev/null and b/wolnelektury/static/jplayer/jplayer.blue.monday.seeking.gif differ
diff --git a/wolnelektury/static/jplayer/jplayer.blue.monday.video.play.png b/wolnelektury/static/jplayer/jplayer.blue.monday.video.play.png
new file mode 100644 (file)
index 0000000..8e97df0
Binary files /dev/null and b/wolnelektury/static/jplayer/jplayer.blue.monday.video.play.png differ
diff --git a/wolnelektury/static/jplayer/jplayer.playlist.min.js b/wolnelektury/static/jplayer/jplayer.playlist.min.js
new file mode 100644 (file)
index 0000000..42c0e22
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * Playlist Object for the jPlayer Plugin
+ * http://www.jplayer.org
+ *
+ * Copyright (c) 2009 - 2011 Happyworm Ltd
+ * Dual licensed under the MIT and GPL licenses.
+ *  - http://www.opensource.org/licenses/mit-license.php
+ *  - http://www.gnu.org/copyleft/gpl.html
+ *
+ * Author: Mark J Panaghiston
+ * Version: 2.1.0 (jPlayer 2.1.0)
+ * Date: 1st September 2011
+ */
+
+(function(b,f){jPlayerPlaylist=function(a,c,d){var e=this;this.current=0;this.removing=this.shuffled=this.loop=!1;this.cssSelector=b.extend({},this._cssSelector,a);this.options=b.extend(!0,{},this._options,d);this.playlist=[];this.original=[];this._initPlaylist(c);this.cssSelector.title=this.cssSelector.cssSelectorAncestor+" .jp-title";this.cssSelector.playlist=this.cssSelector.cssSelectorAncestor+" .jp-playlist";this.cssSelector.next=this.cssSelector.cssSelectorAncestor+" .jp-next";this.cssSelector.previous=
+this.cssSelector.cssSelectorAncestor+" .jp-previous";this.cssSelector.shuffle=this.cssSelector.cssSelectorAncestor+" .jp-shuffle";this.cssSelector.shuffleOff=this.cssSelector.cssSelectorAncestor+" .jp-shuffle-off";this.options.cssSelectorAncestor=this.cssSelector.cssSelectorAncestor;this.options.repeat=function(a){e.loop=a.jPlayer.options.loop};b(this.cssSelector.jPlayer).bind(b.jPlayer.event.ready,function(){e._init()});b(this.cssSelector.jPlayer).bind(b.jPlayer.event.ended,function(){e.next()});
+b(this.cssSelector.jPlayer).bind(b.jPlayer.event.play,function(){b(this).jPlayer("pauseOthers")});b(this.cssSelector.jPlayer).bind(b.jPlayer.event.resize,function(a){a.jPlayer.options.fullScreen?b(e.cssSelector.title).show():b(e.cssSelector.title).hide()});b(this.cssSelector.previous).click(function(){e.previous();b(this).blur();return!1});b(this.cssSelector.next).click(function(){e.next();b(this).blur();return!1});b(this.cssSelector.shuffle).click(function(){e.shuffle(!0);return!1});b(this.cssSelector.shuffleOff).click(function(){e.shuffle(!1);
+return!1}).hide();this.options.fullScreen||b(this.cssSelector.title).hide();b(this.cssSelector.playlist+" ul").empty();this._createItemHandlers();b(this.cssSelector.jPlayer).jPlayer(this.options)};jPlayerPlaylist.prototype={_cssSelector:{jPlayer:"#jquery_jplayer_1",cssSelectorAncestor:"#jp_container_1"},_options:{playlistOptions:{autoPlay:!1,loopOnPrevious:!1,shuffleOnLoop:!0,enableRemoveControls:!1,displayTime:"slow",addTime:"fast",removeTime:"fast",shuffleTime:"slow",itemClass:"jp-playlist-item",
+freeGroupClass:"jp-free-media",freeItemClass:"jp-playlist-item-free",removeItemClass:"jp-playlist-item-remove"}},option:function(a,b){if(b===f)return this.options.playlistOptions[a];this.options.playlistOptions[a]=b;switch(a){case "enableRemoveControls":this._updateControls();break;case "itemClass":case "freeGroupClass":case "freeItemClass":case "removeItemClass":this._refresh(!0),this._createItemHandlers()}return this},_init:function(){var a=this;this._refresh(function(){a.options.playlistOptions.autoPlay?
+a.play(a.current):a.select(a.current)})},_initPlaylist:function(a){this.current=0;this.removing=this.shuffled=!1;this.original=b.extend(!0,[],a);this._originalPlaylist()},_originalPlaylist:function(){var a=this;this.playlist=[];b.each(this.original,function(b){a.playlist[b]=a.original[b]})},_refresh:function(a){var c=this;if(a&&!b.isFunction(a))b(this.cssSelector.playlist+" ul").empty(),b.each(this.playlist,function(a){b(c.cssSelector.playlist+" ul").append(c._createListItem(c.playlist[a]))}),this._updateControls();
+else{var d=b(this.cssSelector.playlist+" ul").children().length?this.options.playlistOptions.displayTime:0;b(this.cssSelector.playlist+" ul").slideUp(d,function(){var d=b(this);b(this).empty();b.each(c.playlist,function(a){d.append(c._createListItem(c.playlist[a]))});c._updateControls();b.isFunction(a)&&a();c.playlist.length?b(this).slideDown(c.options.playlistOptions.displayTime):b(this).show()})}},_createListItem:function(a){var c=this,d="<li><div>";d+="<a href='javascript:;' class='"+this.options.playlistOptions.removeItemClass+
+"'>&times;</a>";if(a.free){var e=!0;d+="<span class='"+this.options.playlistOptions.freeGroupClass+"'>(";b.each(a,function(a,f){b.jPlayer.prototype.format[a]&&(e?e=!1:d+=" | ",d+="<a class='"+c.options.playlistOptions.freeItemClass+"' href='"+f+"' tabindex='1'>"+a+"</a>")});d+=")</span>"}d+="<a href='javascript:;' class='"+this.options.playlistOptions.itemClass+"' tabindex='1'>"+a.title+(a.artist?" <span class='jp-artist'>by "+a.artist+"</span>":"")+"</a>";d+="</div></li>";return d},_createItemHandlers:function(){var a=
+this;b(this.cssSelector.playlist+" a."+this.options.playlistOptions.itemClass).die("click").live("click",function(){var c=b(this).parent().parent().index();a.current!==c?a.play(c):b(a.cssSelector.jPlayer).jPlayer("play");b(this).blur();return!1});b(a.cssSelector.playlist+" a."+this.options.playlistOptions.freeItemClass).die("click").live("click",function(){b(this).parent().parent().find("."+a.options.playlistOptions.itemClass).click();b(this).blur();return!1});b(a.cssSelector.playlist+" a."+this.options.playlistOptions.removeItemClass).die("click").live("click",
+function(){var c=b(this).parent().parent().index();a.remove(c);b(this).blur();return!1})},_updateControls:function(){this.options.playlistOptions.enableRemoveControls?b(this.cssSelector.playlist+" ."+this.options.playlistOptions.removeItemClass).show():b(this.cssSelector.playlist+" ."+this.options.playlistOptions.removeItemClass).hide();this.shuffled?(b(this.cssSelector.shuffleOff).show(),b(this.cssSelector.shuffle).hide()):(b(this.cssSelector.shuffleOff).hide(),b(this.cssSelector.shuffle).show())},
+_highlight:function(a){this.playlist.length&&a!==f&&(b(this.cssSelector.playlist+" .jp-playlist-current").removeClass("jp-playlist-current"),b(this.cssSelector.playlist+" li:nth-child("+(a+1)+")").addClass("jp-playlist-current").find(".jp-playlist-item").addClass("jp-playlist-current"),b(this.cssSelector.title+" li").html(this.playlist[a].title+(this.playlist[a].artist?" <span class='jp-artist'>by "+this.playlist[a].artist+"</span>":"")))},setPlaylist:function(a){this._initPlaylist(a);this._init()},
+add:function(a,c){b(this.cssSelector.playlist+" ul").append(this._createListItem(a)).find("li:last-child").hide().slideDown(this.options.playlistOptions.addTime);this._updateControls();this.original.push(a);this.playlist.push(a);c?this.play(this.playlist.length-1):this.original.length===1&&this.select(0)},remove:function(a){var c=this;if(a===f)return this._initPlaylist([]),this._refresh(function(){b(c.cssSelector.jPlayer).jPlayer("clearMedia")}),!0;else if(this.removing)return!1;else{a=a<0?c.original.length+
+a:a;if(0<=a&&a<this.playlist.length)this.removing=!0,b(this.cssSelector.playlist+" li:nth-child("+(a+1)+")").slideUp(this.options.playlistOptions.removeTime,function(){b(this).remove();if(c.shuffled){var d=c.playlist[a];b.each(c.original,function(a){if(c.original[a]===d)return c.original.splice(a,1),!1})}else c.original.splice(a,1);c.playlist.splice(a,1);c.original.length?a===c.current?(c.current=a<c.original.length?c.current:c.original.length-1,c.select(c.current)):a<c.current&&c.current--:(b(c.cssSelector.jPlayer).jPlayer("clearMedia"),
+c.current=0,c.shuffled=!1,c._updateControls());c.removing=!1});return!0}},select:function(a){a=a<0?this.original.length+a:a;0<=a&&a<this.playlist.length?(this.current=a,this._highlight(a),b(this.cssSelector.jPlayer).jPlayer("setMedia",this.playlist[this.current])):this.current=0},play:function(a){a=a<0?this.original.length+a:a;0<=a&&a<this.playlist.length?this.playlist.length&&(this.select(a),b(this.cssSelector.jPlayer).jPlayer("play")):a===f&&b(this.cssSelector.jPlayer).jPlayer("play")},pause:function(){b(this.cssSelector.jPlayer).jPlayer("pause")},
+next:function(){var a=this.current+1<this.playlist.length?this.current+1:0;this.loop?a===0&&this.shuffled&&this.options.playlistOptions.shuffleOnLoop&&this.playlist.length>1?this.shuffle(!0,!0):this.play(a):a>0&&this.play(a)},previous:function(){var a=this.current-1>=0?this.current-1:this.playlist.length-1;(this.loop&&this.options.playlistOptions.loopOnPrevious||a<this.playlist.length-1)&&this.play(a)},shuffle:function(a,c){var d=this;a===f&&(a=!this.shuffled);(a||a!==this.shuffled)&&b(this.cssSelector.playlist+
+" ul").slideUp(this.options.playlistOptions.shuffleTime,function(){(d.shuffled=a)?d.playlist.sort(function(){return 0.5-Math.random()}):d._originalPlaylist();d._refresh(!0);c||!b(d.cssSelector.jPlayer).data("jPlayer").status.paused?d.play(0):d.select(0);b(this).slideDown(d.options.playlistOptions.shuffleTime)})}}})(jQuery);
\ No newline at end of file
diff --git a/wolnelektury/static/jplayer/jquery.jplayer.min.js b/wolnelektury/static/jplayer/jquery.jplayer.min.js
new file mode 100644 (file)
index 0000000..bcf7901
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+ * jPlayer Plugin for jQuery JavaScript Library
+ * http://www.jplayer.org
+ *
+ * Copyright (c) 2009 - 2011 Happyworm Ltd
+ * Dual licensed under the MIT and GPL licenses.
+ *  - http://www.opensource.org/licenses/mit-license.php
+ *  - http://www.gnu.org/copyleft/gpl.html
+ *
+ * Author: Mark J Panaghiston
+ * Version: 2.1.0
+ * Date: 1st September 2011
+ */
+
+(function(b,f){b.fn.jPlayer=function(a){var c=typeof a==="string",d=Array.prototype.slice.call(arguments,1),e=this,a=!c&&d.length?b.extend.apply(null,[!0,a].concat(d)):a;if(c&&a.charAt(0)==="_")return e;c?this.each(function(){var c=b.data(this,"jPlayer"),h=c&&b.isFunction(c[a])?c[a].apply(c,d):c;if(h!==c&&h!==f)return e=h,!1}):this.each(function(){var c=b.data(this,"jPlayer");c?c.option(a||{}):b.data(this,"jPlayer",new b.jPlayer(a,this))});return e};b.jPlayer=function(a,c){if(arguments.length){this.element=
+b(c);this.options=b.extend(!0,{},this.options,a);var d=this;this.element.bind("remove.jPlayer",function(){d.destroy()});this._init()}};b.jPlayer.emulateMethods="load play pause";b.jPlayer.emulateStatus="src readyState networkState currentTime duration paused ended playbackRate";b.jPlayer.emulateOptions="muted volume";b.jPlayer.reservedEvent="ready flashreset resize repeat error warning";b.jPlayer.event={ready:"jPlayer_ready",flashreset:"jPlayer_flashreset",resize:"jPlayer_resize",repeat:"jPlayer_repeat",
+click:"jPlayer_click",error:"jPlayer_error",warning:"jPlayer_warning",loadstart:"jPlayer_loadstart",progress:"jPlayer_progress",suspend:"jPlayer_suspend",abort:"jPlayer_abort",emptied:"jPlayer_emptied",stalled:"jPlayer_stalled",play:"jPlayer_play",pause:"jPlayer_pause",loadedmetadata:"jPlayer_loadedmetadata",loadeddata:"jPlayer_loadeddata",waiting:"jPlayer_waiting",playing:"jPlayer_playing",canplay:"jPlayer_canplay",canplaythrough:"jPlayer_canplaythrough",seeking:"jPlayer_seeking",seeked:"jPlayer_seeked",
+timeupdate:"jPlayer_timeupdate",ended:"jPlayer_ended",ratechange:"jPlayer_ratechange",durationchange:"jPlayer_durationchange",volumechange:"jPlayer_volumechange"};b.jPlayer.htmlEvent="loadstart,abort,emptied,stalled,loadedmetadata,loadeddata,canplay,canplaythrough,ratechange".split(",");b.jPlayer.pause=function(){b.each(b.jPlayer.prototype.instances,function(a,b){b.data("jPlayer").status.srcSet&&b.jPlayer("pause")})};b.jPlayer.timeFormat={showHour:!1,showMin:!0,showSec:!0,padHour:!1,padMin:!0,padSec:!0,
+sepHour:":",sepMin:":",sepSec:""};b.jPlayer.convertTime=function(a){var c=new Date(a*1E3),d=c.getUTCHours(),a=c.getUTCMinutes(),c=c.getUTCSeconds(),d=b.jPlayer.timeFormat.padHour&&d<10?"0"+d:d,a=b.jPlayer.timeFormat.padMin&&a<10?"0"+a:a,c=b.jPlayer.timeFormat.padSec&&c<10?"0"+c:c;return(b.jPlayer.timeFormat.showHour?d+b.jPlayer.timeFormat.sepHour:"")+(b.jPlayer.timeFormat.showMin?a+b.jPlayer.timeFormat.sepMin:"")+(b.jPlayer.timeFormat.showSec?c+b.jPlayer.timeFormat.sepSec:"")};b.jPlayer.uaBrowser=
+function(a){var a=a.toLowerCase(),b=/(opera)(?:.*version)?[ \/]([\w.]+)/,d=/(msie) ([\w.]+)/,e=/(mozilla)(?:.*? rv:([\w.]+))?/,a=/(webkit)[ \/]([\w.]+)/.exec(a)||b.exec(a)||d.exec(a)||a.indexOf("compatible")<0&&e.exec(a)||[];return{browser:a[1]||"",version:a[2]||"0"}};b.jPlayer.uaPlatform=function(a){var b=a.toLowerCase(),d=/(android)/,e=/(mobile)/,a=/(ipad|iphone|ipod|android|blackberry|playbook|windows ce|webos)/.exec(b)||[],b=/(ipad|playbook)/.exec(b)||!e.exec(b)&&d.exec(b)||[];a[1]&&(a[1]=a[1].replace(/\s/g,
+"_"));return{platform:a[1]||"",tablet:b[1]||""}};b.jPlayer.browser={};b.jPlayer.platform={};var i=b.jPlayer.uaBrowser(navigator.userAgent);if(i.browser)b.jPlayer.browser[i.browser]=!0,b.jPlayer.browser.version=i.version;i=b.jPlayer.uaPlatform(navigator.userAgent);if(i.platform)b.jPlayer.platform[i.platform]=!0,b.jPlayer.platform.mobile=!i.tablet,b.jPlayer.platform.tablet=!!i.tablet;b.jPlayer.prototype={count:0,version:{script:"2.1.0",needFlash:"2.1.0",flash:"unknown"},options:{swfPath:"js",solution:"html, flash",
+supplied:"mp3",preload:"metadata",volume:0.8,muted:!1,wmode:"opaque",backgroundColor:"#000000",cssSelectorAncestor:"#jp_container_1",cssSelector:{videoPlay:".jp-video-play",play:".jp-play",pause:".jp-pause",stop:".jp-stop",seekBar:".jp-seek-bar",playBar:".jp-play-bar",mute:".jp-mute",unmute:".jp-unmute",volumeBar:".jp-volume-bar",volumeBarValue:".jp-volume-bar-value",volumeMax:".jp-volume-max",currentTime:".jp-current-time",duration:".jp-duration",fullScreen:".jp-full-screen",restoreScreen:".jp-restore-screen",
+repeat:".jp-repeat",repeatOff:".jp-repeat-off",gui:".jp-gui",noSolution:".jp-no-solution"},fullScreen:!1,autohide:{restored:!1,full:!0,fadeIn:200,fadeOut:600,hold:1E3},loop:!1,repeat:function(a){a.jPlayer.options.loop?b(this).unbind(".jPlayerRepeat").bind(b.jPlayer.event.ended+".jPlayer.jPlayerRepeat",function(){b(this).jPlayer("play")}):b(this).unbind(".jPlayerRepeat")},nativeVideoControls:{},noFullScreen:{msie:/msie [0-6]/,ipad:/ipad.*?os [0-4]/,iphone:/iphone/,ipod:/ipod/,android_pad:/android [0-3](?!.*?mobile)/,
+android_phone:/android.*?mobile/,blackberry:/blackberry/,windows_ce:/windows ce/,webos:/webos/},noVolume:{ipad:/ipad/,iphone:/iphone/,ipod:/ipod/,android_pad:/android(?!.*?mobile)/,android_phone:/android.*?mobile/,blackberry:/blackberry/,windows_ce:/windows ce/,webos:/webos/,playbook:/playbook/},verticalVolume:!1,idPrefix:"jp",noConflict:"jQuery",emulateHtml:!1,errorAlerts:!1,warningAlerts:!1},optionsAudio:{size:{width:"0px",height:"0px",cssClass:""},sizeFull:{width:"0px",height:"0px",cssClass:""}},
+optionsVideo:{size:{width:"480px",height:"270px",cssClass:"jp-video-270p"},sizeFull:{width:"100%",height:"100%",cssClass:"jp-video-full"}},instances:{},status:{src:"",media:{},paused:!0,format:{},formatType:"",waitForPlay:!0,waitForLoad:!0,srcSet:!1,video:!1,seekPercent:0,currentPercentRelative:0,currentPercentAbsolute:0,currentTime:0,duration:0,readyState:0,networkState:0,playbackRate:1,ended:0},internal:{ready:!1},solution:{html:!0,flash:!0},format:{mp3:{codec:'audio/mpeg; codecs="mp3"',flashCanPlay:!0,
+media:"audio"},m4a:{codec:'audio/mp4; codecs="mp4a.40.2"',flashCanPlay:!0,media:"audio"},oga:{codec:'audio/ogg; codecs="vorbis"',flashCanPlay:!1,media:"audio"},wav:{codec:'audio/wav; codecs="1"',flashCanPlay:!1,media:"audio"},webma:{codec:'audio/webm; codecs="vorbis"',flashCanPlay:!1,media:"audio"},fla:{codec:"audio/x-flv",flashCanPlay:!0,media:"audio"},m4v:{codec:'video/mp4; codecs="avc1.42E01E, mp4a.40.2"',flashCanPlay:!0,media:"video"},ogv:{codec:'video/ogg; codecs="theora, vorbis"',flashCanPlay:!1,
+media:"video"},webmv:{codec:'video/webm; codecs="vorbis, vp8"',flashCanPlay:!1,media:"video"},flv:{codec:"video/x-flv",flashCanPlay:!0,media:"video"}},_init:function(){var a=this;this.element.empty();this.status=b.extend({},this.status);this.internal=b.extend({},this.internal);this.internal.domNode=this.element.get(0);this.formats=[];this.solutions=[];this.require={};this.htmlElement={};this.html={};this.html.audio={};this.html.video={};this.flash={};this.css={};this.css.cs={};this.css.jq={};this.ancestorJq=
+[];this.options.volume=this._limitValue(this.options.volume,0,1);b.each(this.options.supplied.toLowerCase().split(","),function(c,d){var e=d.replace(/^\s+|\s+$/g,"");if(a.format[e]){var f=!1;b.each(a.formats,function(a,b){if(e===b)return f=!0,!1});f||a.formats.push(e)}});b.each(this.options.solution.toLowerCase().split(","),function(c,d){var e=d.replace(/^\s+|\s+$/g,"");if(a.solution[e]){var f=!1;b.each(a.solutions,function(a,b){if(e===b)return f=!0,!1});f||a.solutions.push(e)}});this.internal.instance=
+"jp_"+this.count;this.instances[this.internal.instance]=this.element;this.element.attr("id")||this.element.attr("id",this.options.idPrefix+"_jplayer_"+this.count);this.internal.self=b.extend({},{id:this.element.attr("id"),jq:this.element});this.internal.audio=b.extend({},{id:this.options.idPrefix+"_audio_"+this.count,jq:f});this.internal.video=b.extend({},{id:this.options.idPrefix+"_video_"+this.count,jq:f});this.internal.flash=b.extend({},{id:this.options.idPrefix+"_flash_"+this.count,jq:f,swf:this.options.swfPath+
+(this.options.swfPath.toLowerCase().slice(-4)!==".swf"?(this.options.swfPath&&this.options.swfPath.slice(-1)!=="/"?"/":"")+"Jplayer.swf":"")});this.internal.poster=b.extend({},{id:this.options.idPrefix+"_poster_"+this.count,jq:f});b.each(b.jPlayer.event,function(b,c){a.options[b]!==f&&(a.element.bind(c+".jPlayer",a.options[b]),a.options[b]=f)});this.require.audio=!1;this.require.video=!1;b.each(this.formats,function(b,c){a.require[a.format[c].media]=!0});this.options=this.require.video?b.extend(!0,
+{},this.optionsVideo,this.options):b.extend(!0,{},this.optionsAudio,this.options);this._setSize();this.status.nativeVideoControls=this._uaBlocklist(this.options.nativeVideoControls);this.status.noFullScreen=this._uaBlocklist(this.options.noFullScreen);this.status.noVolume=this._uaBlocklist(this.options.noVolume);this._restrictNativeVideoControls();this.htmlElement.poster=document.createElement("img");this.htmlElement.poster.id=this.internal.poster.id;this.htmlElement.poster.onload=function(){(!a.status.video||
+a.status.waitForPlay)&&a.internal.poster.jq.show()};this.element.append(this.htmlElement.poster);this.internal.poster.jq=b("#"+this.internal.poster.id);this.internal.poster.jq.css({width:this.status.width,height:this.status.height});this.internal.poster.jq.hide();this.internal.poster.jq.bind("click.jPlayer",function(){a._trigger(b.jPlayer.event.click)});this.html.audio.available=!1;if(this.require.audio)this.htmlElement.audio=document.createElement("audio"),this.htmlElement.audio.id=this.internal.audio.id,
+this.html.audio.available=!!this.htmlElement.audio.canPlayType&&this._testCanPlayType(this.htmlElement.audio);this.html.video.available=!1;if(this.require.video)this.htmlElement.video=document.createElement("video"),this.htmlElement.video.id=this.internal.video.id,this.html.video.available=!!this.htmlElement.video.canPlayType&&this._testCanPlayType(this.htmlElement.video);this.flash.available=this._checkForFlash(10);this.html.canPlay={};this.flash.canPlay={};b.each(this.formats,function(b,c){a.html.canPlay[c]=
+a.html[a.format[c].media].available&&""!==a.htmlElement[a.format[c].media].canPlayType(a.format[c].codec);a.flash.canPlay[c]=a.format[c].flashCanPlay&&a.flash.available});this.html.desired=!1;this.flash.desired=!1;b.each(this.solutions,function(c,d){if(c===0)a[d].desired=!0;else{var e=!1,f=!1;b.each(a.formats,function(b,c){a[a.solutions[0]].canPlay[c]&&(a.format[c].media==="video"?f=!0:e=!0)});a[d].desired=a.require.audio&&!e||a.require.video&&!f}});this.html.support={};this.flash.support={};b.each(this.formats,
+function(b,c){a.html.support[c]=a.html.canPlay[c]&&a.html.desired;a.flash.support[c]=a.flash.canPlay[c]&&a.flash.desired});this.html.used=!1;this.flash.used=!1;b.each(this.solutions,function(c,d){b.each(a.formats,function(b,c){if(a[d].support[c])return a[d].used=!0,!1})});this._resetActive();this._resetGate();this._cssSelectorAncestor(this.options.cssSelectorAncestor);!this.html.used&&!this.flash.used?(this._error({type:b.jPlayer.error.NO_SOLUTION,context:"{solution:'"+this.options.solution+"', supplied:'"+
+this.options.supplied+"'}",message:b.jPlayer.errorMsg.NO_SOLUTION,hint:b.jPlayer.errorHint.NO_SOLUTION}),this.css.jq.noSolution.length&&this.css.jq.noSolution.show()):this.css.jq.noSolution.length&&this.css.jq.noSolution.hide();if(this.flash.used){var c,d="jQuery="+encodeURI(this.options.noConflict)+"&id="+encodeURI(this.internal.self.id)+"&vol="+this.options.volume+"&muted="+this.options.muted;if(b.browser.msie&&Number(b.browser.version)<=8){d=['<param name="movie" value="'+this.internal.flash.swf+
+'" />','<param name="FlashVars" value="'+d+'" />','<param name="allowScriptAccess" value="always" />','<param name="bgcolor" value="'+this.options.backgroundColor+'" />','<param name="wmode" value="'+this.options.wmode+'" />'];c=document.createElement('<object id="'+this.internal.flash.id+'" classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" width="0" height="0"></object>');for(var e=0;e<d.length;e++)c.appendChild(document.createElement(d[e]))}else e=function(a,b,c){var d=document.createElement("param");
+d.setAttribute("name",b);d.setAttribute("value",c);a.appendChild(d)},c=document.createElement("object"),c.setAttribute("id",this.internal.flash.id),c.setAttribute("data",this.internal.flash.swf),c.setAttribute("type","application/x-shockwave-flash"),c.setAttribute("width","1"),c.setAttribute("height","1"),e(c,"flashvars",d),e(c,"allowscriptaccess","always"),e(c,"bgcolor",this.options.backgroundColor),e(c,"wmode",this.options.wmode);this.element.append(c);this.internal.flash.jq=b(c)}if(this.html.used){if(this.html.audio.available)this._addHtmlEventListeners(this.htmlElement.audio,
+this.html.audio),this.element.append(this.htmlElement.audio),this.internal.audio.jq=b("#"+this.internal.audio.id);if(this.html.video.available)this._addHtmlEventListeners(this.htmlElement.video,this.html.video),this.element.append(this.htmlElement.video),this.internal.video.jq=b("#"+this.internal.video.id),this.status.nativeVideoControls?this.internal.video.jq.css({width:this.status.width,height:this.status.height}):this.internal.video.jq.css({width:"0px",height:"0px"}),this.internal.video.jq.bind("click.jPlayer",
+function(){a._trigger(b.jPlayer.event.click)})}this.options.emulateHtml&&this._emulateHtmlBridge();this.html.used&&!this.flash.used&&setTimeout(function(){a.internal.ready=!0;a.version.flash="n/a";a._trigger(b.jPlayer.event.repeat);a._trigger(b.jPlayer.event.ready)},100);this._updateNativeVideoControls();this._updateInterface();this._updateButtons(!1);this._updateAutohide();this._updateVolume(this.options.volume);this._updateMute(this.options.muted);this.css.jq.videoPlay.length&&this.css.jq.videoPlay.hide();
+b.jPlayer.prototype.count++},destroy:function(){this.clearMedia();this._removeUiClass();this.css.jq.currentTime.length&&this.css.jq.currentTime.text("");this.css.jq.duration.length&&this.css.jq.duration.text("");b.each(this.css.jq,function(a,b){b.length&&b.unbind(".jPlayer")});this.internal.poster.jq.unbind(".jPlayer");this.internal.video.jq&&this.internal.video.jq.unbind(".jPlayer");this.options.emulateHtml&&this._destroyHtmlBridge();this.element.removeData("jPlayer");this.element.unbind(".jPlayer");
+this.element.empty();delete this.instances[this.internal.instance]},enable:function(){},disable:function(){},_testCanPlayType:function(a){try{return a.canPlayType(this.format.mp3.codec),!0}catch(b){return!1}},_uaBlocklist:function(a){var c=navigator.userAgent.toLowerCase(),d=!1;b.each(a,function(a,b){if(b&&b.test(c))return d=!0,!1});return d},_restrictNativeVideoControls:function(){if(this.require.audio&&this.status.nativeVideoControls)this.status.nativeVideoControls=!1,this.status.noFullScreen=!0},
+_updateNativeVideoControls:function(){if(this.html.video.available&&this.html.used)this.htmlElement.video.controls=this.status.nativeVideoControls,this._updateAutohide(),this.status.nativeVideoControls&&this.require.video?(this.internal.poster.jq.hide(),this.internal.video.jq.css({width:this.status.width,height:this.status.height})):this.status.waitForPlay&&this.status.video&&(this.internal.poster.jq.show(),this.internal.video.jq.css({width:"0px",height:"0px"}))},_addHtmlEventListeners:function(a,
+c){var d=this;a.preload=this.options.preload;a.muted=this.options.muted;a.volume=this.options.volume;a.addEventListener("progress",function(){c.gate&&(d._getHtmlStatus(a),d._updateInterface(),d._trigger(b.jPlayer.event.progress))},!1);a.addEventListener("timeupdate",function(){c.gate&&(d._getHtmlStatus(a),d._updateInterface(),d._trigger(b.jPlayer.event.timeupdate))},!1);a.addEventListener("durationchange",function(){if(c.gate)d.status.duration=this.duration,d._getHtmlStatus(a),d._updateInterface(),
+d._trigger(b.jPlayer.event.durationchange)},!1);a.addEventListener("play",function(){c.gate&&(d._updateButtons(!0),d._html_checkWaitForPlay(),d._trigger(b.jPlayer.event.play))},!1);a.addEventListener("playing",function(){c.gate&&(d._updateButtons(!0),d._seeked(),d._trigger(b.jPlayer.event.playing))},!1);a.addEventListener("pause",function(){c.gate&&(d._updateButtons(!1),d._trigger(b.jPlayer.event.pause))},!1);a.addEventListener("waiting",function(){c.gate&&(d._seeking(),d._trigger(b.jPlayer.event.waiting))},
+!1);a.addEventListener("seeking",function(){c.gate&&(d._seeking(),d._trigger(b.jPlayer.event.seeking))},!1);a.addEventListener("seeked",function(){c.gate&&(d._seeked(),d._trigger(b.jPlayer.event.seeked))},!1);a.addEventListener("volumechange",function(){if(c.gate)d.options.volume=a.volume,d.options.muted=a.muted,d._updateMute(),d._updateVolume(),d._trigger(b.jPlayer.event.volumechange)},!1);a.addEventListener("suspend",function(){c.gate&&(d._seeked(),d._trigger(b.jPlayer.event.suspend))},!1);a.addEventListener("ended",
+function(){if(c.gate){if(!b.jPlayer.browser.webkit)d.htmlElement.media.currentTime=0;d.htmlElement.media.pause();d._updateButtons(!1);d._getHtmlStatus(a,!0);d._updateInterface();d._trigger(b.jPlayer.event.ended)}},!1);a.addEventListener("error",function(){if(c.gate&&(d._updateButtons(!1),d._seeked(),d.status.srcSet))clearTimeout(d.internal.htmlDlyCmdId),d.status.waitForLoad=!0,d.status.waitForPlay=!0,d.status.video&&!d.status.nativeVideoControls&&d.internal.video.jq.css({width:"0px",height:"0px"}),
+d._validString(d.status.media.poster)&&!d.status.nativeVideoControls&&d.internal.poster.jq.show(),d.css.jq.videoPlay.length&&d.css.jq.videoPlay.show(),d._error({type:b.jPlayer.error.URL,context:d.status.src,message:b.jPlayer.errorMsg.URL,hint:b.jPlayer.errorHint.URL})},!1);b.each(b.jPlayer.htmlEvent,function(e,g){a.addEventListener(this,function(){c.gate&&d._trigger(b.jPlayer.event[g])},!1)})},_getHtmlStatus:function(a,b){var d=0,e=0,g=0,f=0;if(a.duration)this.status.duration=a.duration;d=a.currentTime;
+e=this.status.duration>0?100*d/this.status.duration:0;typeof a.seekable==="object"&&a.seekable.length>0?(g=this.status.duration>0?100*a.seekable.end(a.seekable.length-1)/this.status.duration:100,f=100*a.currentTime/a.seekable.end(a.seekable.length-1)):(g=100,f=e);b&&(e=f=d=0);this.status.seekPercent=g;this.status.currentPercentRelative=f;this.status.currentPercentAbsolute=e;this.status.currentTime=d;this.status.readyState=a.readyState;this.status.networkState=a.networkState;this.status.playbackRate=
+a.playbackRate;this.status.ended=a.ended},_resetStatus:function(){this.status=b.extend({},this.status,b.jPlayer.prototype.status)},_trigger:function(a,c,d){a=b.Event(a);a.jPlayer={};a.jPlayer.version=b.extend({},this.version);a.jPlayer.options=b.extend(!0,{},this.options);a.jPlayer.status=b.extend(!0,{},this.status);a.jPlayer.html=b.extend(!0,{},this.html);a.jPlayer.flash=b.extend(!0,{},this.flash);if(c)a.jPlayer.error=b.extend({},c);if(d)a.jPlayer.warning=b.extend({},d);this.element.trigger(a)},
+jPlayerFlashEvent:function(a,c){if(a===b.jPlayer.event.ready)if(this.internal.ready){if(this.flash.gate){if(this.status.srcSet){var d=this.status.currentTime,e=this.status.paused;this.setMedia(this.status.media);d>0&&(e?this.pause(d):this.play(d))}this._trigger(b.jPlayer.event.flashreset)}}else this.internal.ready=!0,this.internal.flash.jq.css({width:"0px",height:"0px"}),this.version.flash=c.version,this.version.needFlash!==this.version.flash&&this._error({type:b.jPlayer.error.VERSION,context:this.version.flash,
+message:b.jPlayer.errorMsg.VERSION+this.version.flash,hint:b.jPlayer.errorHint.VERSION}),this._trigger(b.jPlayer.event.repeat),this._trigger(a);if(this.flash.gate)switch(a){case b.jPlayer.event.progress:this._getFlashStatus(c);this._updateInterface();this._trigger(a);break;case b.jPlayer.event.timeupdate:this._getFlashStatus(c);this._updateInterface();this._trigger(a);break;case b.jPlayer.event.play:this._seeked();this._updateButtons(!0);this._trigger(a);break;case b.jPlayer.event.pause:this._updateButtons(!1);
+this._trigger(a);break;case b.jPlayer.event.ended:this._updateButtons(!1);this._trigger(a);break;case b.jPlayer.event.click:this._trigger(a);break;case b.jPlayer.event.error:this.status.waitForLoad=!0;this.status.waitForPlay=!0;this.status.video&&this.internal.flash.jq.css({width:"0px",height:"0px"});this._validString(this.status.media.poster)&&this.internal.poster.jq.show();this.css.jq.videoPlay.length&&this.status.video&&this.css.jq.videoPlay.show();this.status.video?this._flash_setVideo(this.status.media):
+this._flash_setAudio(this.status.media);this._updateButtons(!1);this._error({type:b.jPlayer.error.URL,context:c.src,message:b.jPlayer.errorMsg.URL,hint:b.jPlayer.errorHint.URL});break;case b.jPlayer.event.seeking:this._seeking();this._trigger(a);break;case b.jPlayer.event.seeked:this._seeked();this._trigger(a);break;case b.jPlayer.event.ready:break;default:this._trigger(a)}return!1},_getFlashStatus:function(a){this.status.seekPercent=a.seekPercent;this.status.currentPercentRelative=a.currentPercentRelative;
+this.status.currentPercentAbsolute=a.currentPercentAbsolute;this.status.currentTime=a.currentTime;this.status.duration=a.duration;this.status.readyState=4;this.status.networkState=0;this.status.playbackRate=1;this.status.ended=!1},_updateButtons:function(a){if(a!==f)this.status.paused=!a,this.css.jq.play.length&&this.css.jq.pause.length&&(a?(this.css.jq.play.hide(),this.css.jq.pause.show()):(this.css.jq.play.show(),this.css.jq.pause.hide()));this.css.jq.restoreScreen.length&&this.css.jq.fullScreen.length&&
+(this.status.noFullScreen?(this.css.jq.fullScreen.hide(),this.css.jq.restoreScreen.hide()):this.options.fullScreen?(this.css.jq.fullScreen.hide(),this.css.jq.restoreScreen.show()):(this.css.jq.fullScreen.show(),this.css.jq.restoreScreen.hide()));this.css.jq.repeat.length&&this.css.jq.repeatOff.length&&(this.options.loop?(this.css.jq.repeat.hide(),this.css.jq.repeatOff.show()):(this.css.jq.repeat.show(),this.css.jq.repeatOff.hide()))},_updateInterface:function(){this.css.jq.seekBar.length&&this.css.jq.seekBar.width(this.status.seekPercent+
+"%");this.css.jq.playBar.length&&this.css.jq.playBar.width(this.status.currentPercentRelative+"%");this.css.jq.currentTime.length&&this.css.jq.currentTime.text(b.jPlayer.convertTime(this.status.currentTime));this.css.jq.duration.length&&this.css.jq.duration.text(b.jPlayer.convertTime(this.status.duration))},_seeking:function(){this.css.jq.seekBar.length&&this.css.jq.seekBar.addClass("jp-seeking-bg")},_seeked:function(){this.css.jq.seekBar.length&&this.css.jq.seekBar.removeClass("jp-seeking-bg")},
+_resetGate:function(){this.html.audio.gate=!1;this.html.video.gate=!1;this.flash.gate=!1},_resetActive:function(){this.html.active=!1;this.flash.active=!1},setMedia:function(a){var c=this,d=!1,e=this.status.media.poster!==a.poster;this._resetMedia();this._resetGate();this._resetActive();b.each(this.formats,function(e,f){var i=c.format[f].media==="video";b.each(c.solutions,function(b,e){if(c[e].support[f]&&c._validString(a[f])){var g=e==="html";i?(g?(c.html.video.gate=!0,c._html_setVideo(a),c.html.active=
+!0):(c.flash.gate=!0,c._flash_setVideo(a),c.flash.active=!0),c.css.jq.videoPlay.length&&c.css.jq.videoPlay.show(),c.status.video=!0):(g?(c.html.audio.gate=!0,c._html_setAudio(a),c.html.active=!0):(c.flash.gate=!0,c._flash_setAudio(a),c.flash.active=!0),c.css.jq.videoPlay.length&&c.css.jq.videoPlay.hide(),c.status.video=!1);d=!0;return!1}});if(d)return!1});if(d){if((!this.status.nativeVideoControls||!this.html.video.gate)&&this._validString(a.poster))e?this.htmlElement.poster.src=a.poster:this.internal.poster.jq.show();
+this.status.srcSet=!0;this.status.media=b.extend({},a);this._updateButtons(!1);this._updateInterface()}else this._error({type:b.jPlayer.error.NO_SUPPORT,context:"{supplied:'"+this.options.supplied+"'}",message:b.jPlayer.errorMsg.NO_SUPPORT,hint:b.jPlayer.errorHint.NO_SUPPORT})},_resetMedia:function(){this._resetStatus();this._updateButtons(!1);this._updateInterface();this._seeked();this.internal.poster.jq.hide();clearTimeout(this.internal.htmlDlyCmdId);this.html.active?this._html_resetMedia():this.flash.active&&
+this._flash_resetMedia()},clearMedia:function(){this._resetMedia();this.html.active?this._html_clearMedia():this.flash.active&&this._flash_clearMedia();this._resetGate();this._resetActive()},load:function(){this.status.srcSet?this.html.active?this._html_load():this.flash.active&&this._flash_load():this._urlNotSetError("load")},play:function(a){a=typeof a==="number"?a:NaN;this.status.srcSet?this.html.active?this._html_play(a):this.flash.active&&this._flash_play(a):this._urlNotSetError("play")},videoPlay:function(){this.play()},
+pause:function(a){a=typeof a==="number"?a:NaN;this.status.srcSet?this.html.active?this._html_pause(a):this.flash.active&&this._flash_pause(a):this._urlNotSetError("pause")},pauseOthers:function(){var a=this;b.each(this.instances,function(b,d){a.element!==d&&d.data("jPlayer").status.srcSet&&d.jPlayer("pause")})},stop:function(){this.status.srcSet?this.html.active?this._html_pause(0):this.flash.active&&this._flash_pause(0):this._urlNotSetError("stop")},playHead:function(a){a=this._limitValue(a,0,100);
+this.status.srcSet?this.html.active?this._html_playHead(a):this.flash.active&&this._flash_playHead(a):this._urlNotSetError("playHead")},_muted:function(a){this.options.muted=a;this.html.used&&this._html_mute(a);this.flash.used&&this._flash_mute(a);!this.html.video.gate&&!this.html.audio.gate&&(this._updateMute(a),this._updateVolume(this.options.volume),this._trigger(b.jPlayer.event.volumechange))},mute:function(a){a=a===f?!0:!!a;this._muted(a)},unmute:function(a){a=a===f?!0:!!a;this._muted(!a)},_updateMute:function(a){if(a===
+f)a=this.options.muted;this.css.jq.mute.length&&this.css.jq.unmute.length&&(this.status.noVolume?(this.css.jq.mute.hide(),this.css.jq.unmute.hide()):a?(this.css.jq.mute.hide(),this.css.jq.unmute.show()):(this.css.jq.mute.show(),this.css.jq.unmute.hide()))},volume:function(a){a=this._limitValue(a,0,1);this.options.volume=a;this.html.used&&this._html_volume(a);this.flash.used&&this._flash_volume(a);!this.html.video.gate&&!this.html.audio.gate&&(this._updateVolume(a),this._trigger(b.jPlayer.event.volumechange))},
+volumeBar:function(a){if(this.css.jq.volumeBar.length){var b=this.css.jq.volumeBar.offset(),d=a.pageX-b.left,e=this.css.jq.volumeBar.width(),a=this.css.jq.volumeBar.height()-a.pageY+b.top,b=this.css.jq.volumeBar.height();this.options.verticalVolume?this.volume(a/b):this.volume(d/e)}this.options.muted&&this._muted(!1)},volumeBarValue:function(a){this.volumeBar(a)},_updateVolume:function(a){if(a===f)a=this.options.volume;a=this.options.muted?0:a;this.status.noVolume?(this.css.jq.volumeBar.length&&this.css.jq.volumeBar.hide(),
+this.css.jq.volumeBarValue.length&&this.css.jq.volumeBarValue.hide(),this.css.jq.volumeMax.length&&this.css.jq.volumeMax.hide()):(this.css.jq.volumeBar.length&&this.css.jq.volumeBar.show(),this.css.jq.volumeBarValue.length&&(this.css.jq.volumeBarValue.show(),this.css.jq.volumeBarValue[this.options.verticalVolume?"height":"width"](a*100+"%")),this.css.jq.volumeMax.length&&this.css.jq.volumeMax.show())},volumeMax:function(){this.volume(1);this.options.muted&&this._muted(!1)},_cssSelectorAncestor:function(a){var c=
+this;this.options.cssSelectorAncestor=a;this._removeUiClass();this.ancestorJq=a?b(a):[];a&&this.ancestorJq.length!==1&&this._warning({type:b.jPlayer.warning.CSS_SELECTOR_COUNT,context:a,message:b.jPlayer.warningMsg.CSS_SELECTOR_COUNT+this.ancestorJq.length+" found for cssSelectorAncestor.",hint:b.jPlayer.warningHint.CSS_SELECTOR_COUNT});this._addUiClass();b.each(this.options.cssSelector,function(a,b){c._cssSelector(a,b)})},_cssSelector:function(a,c){var d=this;typeof c==="string"?b.jPlayer.prototype.options.cssSelector[a]?
+(this.css.jq[a]&&this.css.jq[a].length&&this.css.jq[a].unbind(".jPlayer"),this.options.cssSelector[a]=c,this.css.cs[a]=this.options.cssSelectorAncestor+" "+c,this.css.jq[a]=c?b(this.css.cs[a]):[],this.css.jq[a].length&&this.css.jq[a].bind("click.jPlayer",function(c){d[a](c);b(this).blur();return!1}),c&&this.css.jq[a].length!==1&&this._warning({type:b.jPlayer.warning.CSS_SELECTOR_COUNT,context:this.css.cs[a],message:b.jPlayer.warningMsg.CSS_SELECTOR_COUNT+this.css.jq[a].length+" found for "+a+" method.",
+hint:b.jPlayer.warningHint.CSS_SELECTOR_COUNT})):this._warning({type:b.jPlayer.warning.CSS_SELECTOR_METHOD,context:a,message:b.jPlayer.warningMsg.CSS_SELECTOR_METHOD,hint:b.jPlayer.warningHint.CSS_SELECTOR_METHOD}):this._warning({type:b.jPlayer.warning.CSS_SELECTOR_STRING,context:c,message:b.jPlayer.warningMsg.CSS_SELECTOR_STRING,hint:b.jPlayer.warningHint.CSS_SELECTOR_STRING})},seekBar:function(a){if(this.css.jq.seekBar){var b=this.css.jq.seekBar.offset(),a=a.pageX-b.left,b=this.css.jq.seekBar.width();
+this.playHead(100*a/b)}},playBar:function(a){this.seekBar(a)},repeat:function(){this._loop(!0)},repeatOff:function(){this._loop(!1)},_loop:function(a){if(this.options.loop!==a)this.options.loop=a,this._updateButtons(),this._trigger(b.jPlayer.event.repeat)},currentTime:function(){},duration:function(){},gui:function(){},noSolution:function(){},option:function(a,c){var d=a;if(arguments.length===0)return b.extend(!0,{},this.options);if(typeof a==="string"){var e=a.split(".");if(c===f){for(var d=b.extend(!0,
+{},this.options),g=0;g<e.length;g++)if(d[e[g]]!==f)d=d[e[g]];else return this._warning({type:b.jPlayer.warning.OPTION_KEY,context:a,message:b.jPlayer.warningMsg.OPTION_KEY,hint:b.jPlayer.warningHint.OPTION_KEY}),f;return d}for(var g=d={},h=0;h<e.length;h++)h<e.length-1?(g[e[h]]={},g=g[e[h]]):g[e[h]]=c}this._setOptions(d);return this},_setOptions:function(a){var c=this;b.each(a,function(a,b){c._setOption(a,b)});return this},_setOption:function(a,c){var d=this;switch(a){case "volume":this.volume(c);
+break;case "muted":this._muted(c);break;case "cssSelectorAncestor":this._cssSelectorAncestor(c);break;case "cssSelector":b.each(c,function(a,b){d._cssSelector(a,b)});break;case "fullScreen":this.options[a]!==c&&(this._removeUiClass(),this.options[a]=c,this._refreshSize());break;case "size":!this.options.fullScreen&&this.options[a].cssClass!==c.cssClass&&this._removeUiClass();this.options[a]=b.extend({},this.options[a],c);this._refreshSize();break;case "sizeFull":this.options.fullScreen&&this.options[a].cssClass!==
+c.cssClass&&this._removeUiClass();this.options[a]=b.extend({},this.options[a],c);this._refreshSize();break;case "autohide":this.options[a]=b.extend({},this.options[a],c);this._updateAutohide();break;case "loop":this._loop(c);break;case "nativeVideoControls":this.options[a]=b.extend({},this.options[a],c);this.status.nativeVideoControls=this._uaBlocklist(this.options.nativeVideoControls);this._restrictNativeVideoControls();this._updateNativeVideoControls();break;case "noFullScreen":this.options[a]=
+b.extend({},this.options[a],c);this.status.nativeVideoControls=this._uaBlocklist(this.options.nativeVideoControls);this.status.noFullScreen=this._uaBlocklist(this.options.noFullScreen);this._restrictNativeVideoControls();this._updateButtons();break;case "noVolume":this.options[a]=b.extend({},this.options[a],c);this.status.noVolume=this._uaBlocklist(this.options.noVolume);this._updateVolume();this._updateMute();break;case "emulateHtml":this.options[a]!==c&&((this.options[a]=c)?this._emulateHtmlBridge():
+this._destroyHtmlBridge())}return this},_refreshSize:function(){this._setSize();this._addUiClass();this._updateSize();this._updateButtons();this._updateAutohide();this._trigger(b.jPlayer.event.resize)},_setSize:function(){this.options.fullScreen?(this.status.width=this.options.sizeFull.width,this.status.height=this.options.sizeFull.height,this.status.cssClass=this.options.sizeFull.cssClass):(this.status.width=this.options.size.width,this.status.height=this.options.size.height,this.status.cssClass=
+this.options.size.cssClass);this.element.css({width:this.status.width,height:this.status.height})},_addUiClass:function(){this.ancestorJq.length&&this.ancestorJq.addClass(this.status.cssClass)},_removeUiClass:function(){this.ancestorJq.length&&this.ancestorJq.removeClass(this.status.cssClass)},_updateSize:function(){this.internal.poster.jq.css({width:this.status.width,height:this.status.height});!this.status.waitForPlay&&this.html.active&&this.status.video||this.html.video.available&&this.html.used&&
+this.status.nativeVideoControls?this.internal.video.jq.css({width:this.status.width,height:this.status.height}):!this.status.waitForPlay&&this.flash.active&&this.status.video&&this.internal.flash.jq.css({width:this.status.width,height:this.status.height})},_updateAutohide:function(){var a=this,b=function(){a.css.jq.gui.fadeIn(a.options.autohide.fadeIn,function(){clearTimeout(a.internal.autohideId);a.internal.autohideId=setTimeout(function(){a.css.jq.gui.fadeOut(a.options.autohide.fadeOut)},a.options.autohide.hold)})};
+this.css.jq.gui.length&&(this.css.jq.gui.stop(!0,!0),clearTimeout(this.internal.autohideId),this.element.unbind(".jPlayerAutohide"),this.css.jq.gui.unbind(".jPlayerAutohide"),this.status.nativeVideoControls?this.css.jq.gui.hide():this.options.fullScreen&&this.options.autohide.full||!this.options.fullScreen&&this.options.autohide.restored?(this.element.bind("mousemove.jPlayer.jPlayerAutohide",b),this.css.jq.gui.bind("mousemove.jPlayer.jPlayerAutohide",b),this.css.jq.gui.hide()):this.css.jq.gui.show())},
+fullScreen:function(){this._setOption("fullScreen",!0)},restoreScreen:function(){this._setOption("fullScreen",!1)},_html_initMedia:function(){this.htmlElement.media.src=this.status.src;this.options.preload!=="none"&&this._html_load();this._trigger(b.jPlayer.event.timeupdate)},_html_setAudio:function(a){var c=this;b.each(this.formats,function(b,e){if(c.html.support[e]&&a[e])return c.status.src=a[e],c.status.format[e]=!0,c.status.formatType=e,!1});this.htmlElement.media=this.htmlElement.audio;this._html_initMedia()},
+_html_setVideo:function(a){var c=this;b.each(this.formats,function(b,e){if(c.html.support[e]&&a[e])return c.status.src=a[e],c.status.format[e]=!0,c.status.formatType=e,!1});if(this.status.nativeVideoControls)this.htmlElement.video.poster=this._validString(a.poster)?a.poster:"";this.htmlElement.media=this.htmlElement.video;this._html_initMedia()},_html_resetMedia:function(){this.htmlElement.media&&(this.htmlElement.media.id===this.internal.video.id&&!this.status.nativeVideoControls&&this.internal.video.jq.css({width:"0px",
+height:"0px"}),this.htmlElement.media.pause())},_html_clearMedia:function(){if(this.htmlElement.media)this.htmlElement.media.src="",this.htmlElement.media.load()},_html_load:function(){if(this.status.waitForLoad)this.status.waitForLoad=!1,this.htmlElement.media.load();clearTimeout(this.internal.htmlDlyCmdId)},_html_play:function(a){var b=this;this._html_load();this.htmlElement.media.play();if(!isNaN(a))try{this.htmlElement.media.currentTime=a}catch(d){this.internal.htmlDlyCmdId=setTimeout(function(){b.play(a)},
+100);return}this._html_checkWaitForPlay()},_html_pause:function(a){var b=this;a>0?this._html_load():clearTimeout(this.internal.htmlDlyCmdId);this.htmlElement.media.pause();if(!isNaN(a))try{this.htmlElement.media.currentTime=a}catch(d){this.internal.htmlDlyCmdId=setTimeout(function(){b.pause(a)},100);return}a>0&&this._html_checkWaitForPlay()},_html_playHead:function(a){var b=this;this._html_load();try{if(typeof this.htmlElement.media.seekable==="object"&&this.htmlElement.media.seekable.length>0)this.htmlElement.media.currentTime=
+a*this.htmlElement.media.seekable.end(this.htmlElement.media.seekable.length-1)/100;else if(this.htmlElement.media.duration>0&&!isNaN(this.htmlElement.media.duration))this.htmlElement.media.currentTime=a*this.htmlElement.media.duration/100;else throw"e";}catch(d){this.internal.htmlDlyCmdId=setTimeout(function(){b.playHead(a)},100);return}this.status.waitForLoad||this._html_checkWaitForPlay()},_html_checkWaitForPlay:function(){if(this.status.waitForPlay)this.status.waitForPlay=!1,this.css.jq.videoPlay.length&&
+this.css.jq.videoPlay.hide(),this.status.video&&(this.internal.poster.jq.hide(),this.internal.video.jq.css({width:this.status.width,height:this.status.height}))},_html_volume:function(a){if(this.html.audio.available)this.htmlElement.audio.volume=a;if(this.html.video.available)this.htmlElement.video.volume=a},_html_mute:function(a){if(this.html.audio.available)this.htmlElement.audio.muted=a;if(this.html.video.available)this.htmlElement.video.muted=a},_flash_setAudio:function(a){var c=this;try{if(b.each(this.formats,
+function(b,d){if(c.flash.support[d]&&a[d]){switch(d){case "m4a":case "fla":c._getMovie().fl_setAudio_m4a(a[d]);break;case "mp3":c._getMovie().fl_setAudio_mp3(a[d])}c.status.src=a[d];c.status.format[d]=!0;c.status.formatType=d;return!1}}),this.options.preload==="auto")this._flash_load(),this.status.waitForLoad=!1}catch(d){this._flashError(d)}},_flash_setVideo:function(a){var c=this;try{if(b.each(this.formats,function(b,d){if(c.flash.support[d]&&a[d]){switch(d){case "m4v":case "flv":c._getMovie().fl_setVideo_m4v(a[d])}c.status.src=
+a[d];c.status.format[d]=!0;c.status.formatType=d;return!1}}),this.options.preload==="auto")this._flash_load(),this.status.waitForLoad=!1}catch(d){this._flashError(d)}},_flash_resetMedia:function(){this.internal.flash.jq.css({width:"0px",height:"0px"});this._flash_pause(NaN)},_flash_clearMedia:function(){try{this._getMovie().fl_clearMedia()}catch(a){this._flashError(a)}},_flash_load:function(){try{this._getMovie().fl_load()}catch(a){this._flashError(a)}this.status.waitForLoad=!1},_flash_play:function(a){try{this._getMovie().fl_play(a)}catch(b){this._flashError(b)}this.status.waitForLoad=
+!1;this._flash_checkWaitForPlay()},_flash_pause:function(a){try{this._getMovie().fl_pause(a)}catch(b){this._flashError(b)}if(a>0)this.status.waitForLoad=!1,this._flash_checkWaitForPlay()},_flash_playHead:function(a){try{this._getMovie().fl_play_head(a)}catch(b){this._flashError(b)}this.status.waitForLoad||this._flash_checkWaitForPlay()},_flash_checkWaitForPlay:function(){if(this.status.waitForPlay)this.status.waitForPlay=!1,this.css.jq.videoPlay.length&&this.css.jq.videoPlay.hide(),this.status.video&&
+(this.internal.poster.jq.hide(),this.internal.flash.jq.css({width:this.status.width,height:this.status.height}))},_flash_volume:function(a){try{this._getMovie().fl_volume(a)}catch(b){this._flashError(b)}},_flash_mute:function(a){try{this._getMovie().fl_mute(a)}catch(b){this._flashError(b)}},_getMovie:function(){return document[this.internal.flash.id]},_checkForFlash:function(a){var b=!1,d;if(window.ActiveXObject)try{new ActiveXObject("ShockwaveFlash.ShockwaveFlash."+a),b=!0}catch(e){}else navigator.plugins&&
+navigator.mimeTypes.length>0&&(d=navigator.plugins["Shockwave Flash"])&&navigator.plugins["Shockwave Flash"].description.replace(/.*\s(\d+\.\d+).*/,"$1")>=a&&(b=!0);return b},_validString:function(a){return a&&typeof a==="string"},_limitValue:function(a,b,d){return a<b?b:a>d?d:a},_urlNotSetError:function(a){this._error({type:b.jPlayer.error.URL_NOT_SET,context:a,message:b.jPlayer.errorMsg.URL_NOT_SET,hint:b.jPlayer.errorHint.URL_NOT_SET})},_flashError:function(a){var c;c=this.internal.ready?"FLASH_DISABLED":
+"FLASH";this._error({type:b.jPlayer.error[c],context:this.internal.flash.swf,message:b.jPlayer.errorMsg[c]+a.message,hint:b.jPlayer.errorHint[c]});this.internal.flash.jq.css({width:"1px",height:"1px"})},_error:function(a){this._trigger(b.jPlayer.event.error,a);this.options.errorAlerts&&this._alert("Error!"+(a.message?"\n\n"+a.message:"")+(a.hint?"\n\n"+a.hint:"")+"\n\nContext: "+a.context)},_warning:function(a){this._trigger(b.jPlayer.event.warning,f,a);this.options.warningAlerts&&this._alert("Warning!"+
+(a.message?"\n\n"+a.message:"")+(a.hint?"\n\n"+a.hint:"")+"\n\nContext: "+a.context)},_alert:function(a){alert("jPlayer "+this.version.script+" : id='"+this.internal.self.id+"' : "+a)},_emulateHtmlBridge:function(){var a=this;b.each(b.jPlayer.emulateMethods.split(/\s+/g),function(b,d){a.internal.domNode[d]=function(b){a[d](b)}});b.each(b.jPlayer.event,function(c,d){var e=!0;b.each(b.jPlayer.reservedEvent.split(/\s+/g),function(a,b){if(b===c)return e=!1});e&&a.element.bind(d+".jPlayer.jPlayerHtml",
+function(){a._emulateHtmlUpdate();var b=document.createEvent("Event");b.initEvent(c,!1,!0);a.internal.domNode.dispatchEvent(b)})})},_emulateHtmlUpdate:function(){var a=this;b.each(b.jPlayer.emulateStatus.split(/\s+/g),function(b,d){a.internal.domNode[d]=a.status[d]});b.each(b.jPlayer.emulateOptions.split(/\s+/g),function(b,d){a.internal.domNode[d]=a.options[d]})},_destroyHtmlBridge:function(){var a=this;this.element.unbind(".jPlayerHtml");b.each((b.jPlayer.emulateMethods+" "+b.jPlayer.emulateStatus+
+" "+b.jPlayer.emulateOptions).split(/\s+/g),function(b,d){delete a.internal.domNode[d]})}};b.jPlayer.error={FLASH:"e_flash",FLASH_DISABLED:"e_flash_disabled",NO_SOLUTION:"e_no_solution",NO_SUPPORT:"e_no_support",URL:"e_url",URL_NOT_SET:"e_url_not_set",VERSION:"e_version"};b.jPlayer.errorMsg={FLASH:"jPlayer's Flash fallback is not configured correctly, or a command was issued before the jPlayer Ready event. Details: ",FLASH_DISABLED:"jPlayer's Flash fallback has been disabled by the browser due to the CSS rules you have used. Details: ",
+NO_SOLUTION:"No solution can be found by jPlayer in this browser. Neither HTML nor Flash can be used.",NO_SUPPORT:"It is not possible to play any media format provided in setMedia() on this browser using your current options.",URL:"Media URL could not be loaded.",URL_NOT_SET:"Attempt to issue media playback commands, while no media url is set.",VERSION:"jPlayer "+b.jPlayer.prototype.version.script+" needs Jplayer.swf version "+b.jPlayer.prototype.version.needFlash+" but found "};b.jPlayer.errorHint=
+{FLASH:"Check your swfPath option and that Jplayer.swf is there.",FLASH_DISABLED:"Check that you have not display:none; the jPlayer entity or any ancestor.",NO_SOLUTION:"Review the jPlayer options: support and supplied.",NO_SUPPORT:"Video or audio formats defined in the supplied option are missing.",URL:"Check media URL is valid.",URL_NOT_SET:"Use setMedia() to set the media URL.",VERSION:"Update jPlayer files."};b.jPlayer.warning={CSS_SELECTOR_COUNT:"e_css_selector_count",CSS_SELECTOR_METHOD:"e_css_selector_method",
+CSS_SELECTOR_STRING:"e_css_selector_string",OPTION_KEY:"e_option_key"};b.jPlayer.warningMsg={CSS_SELECTOR_COUNT:"The number of css selectors found did not equal one: ",CSS_SELECTOR_METHOD:"The methodName given in jPlayer('cssSelector') is not a valid jPlayer method.",CSS_SELECTOR_STRING:"The methodCssSelector given in jPlayer('cssSelector') is not a String or is empty.",OPTION_KEY:"The option requested in jPlayer('option') is undefined."};b.jPlayer.warningHint={CSS_SELECTOR_COUNT:"Check your css selector and the ancestor.",
+CSS_SELECTOR_METHOD:"Check your method name.",CSS_SELECTOR_STRING:"Check your css selector is a string.",OPTION_KEY:"Check your option name."}})(jQuery);
\ No newline at end of file
diff --git a/wolnelektury/static/js/base.js b/wolnelektury/static/js/base.js
new file mode 100755 (executable)
index 0000000..36a85d0
--- /dev/null
@@ -0,0 +1,93 @@
+(function($) {
+    $(function() {
+        $.fn.toggle_slide = function(p) {
+            cont = $(this);
+            short_el = p['short_el'] || $(':first-child', this);
+            long_el = p['long_el'] || short_el.next();
+            button = p['button'];
+            short_text = p['short_text'],
+            long_text = p['long_text'];
+
+            var toggle_fun = function(cont, short_el, long_el, button, short_text, long_text) {
+                var toggle = function() {
+                    if (cont.hasClass('short')) {
+                        cont.animate({"height": long_el.attr("cont_h")+'px'}, {duration: "fast" }).removeClass('short');
+                        short_el.hide();
+                        long_el.show();
+                        if (button && long_text) button.html(long_text);
+                    } else {
+                        cont.animate({"height": short_el.attr("cont_h")+'px'}, {duration: "fast" }).addClass('short');
+                        long_el.hide();
+                        short_el.show();
+                        if (button && short_text) button.html(short_text);
+                    }
+                    return false;
+                }
+                return toggle;
+            }
+            if (long_el.html().length <= short_el.html().length)
+                return;
+
+            // ensure long element shown first
+            long_el.show();short_el.hide();
+            long_el.attr("cont_h", $(this).height()).hide();
+            short_el.show().attr("cont_h", $(this).height());
+            $(this).addClass('short');
+
+            if (button && short_text) button.html(short_text);
+            if (button) button.hover(
+                function() { $(this).css({background: '#F3F3F3', cursor: 'pointer'}); },
+                function() { $(this).css({background: '#EEE'}); }
+            ).click(toggle_fun(cont, short_el, long_el, button, short_text, long_text));
+            short_el.hover(
+                function() { $(this).css({background: '#F3F3F3', cursor: 'pointer'}); },
+                function() { $(this).css({background: '#FFF'}); }
+            ).click(toggle_fun(cont, short_el, long_el, button, short_text, long_text));
+            long_el.hover(
+                function() { $(this).css({background: '#F3F3F3', cursor: 'pointer'}); },
+                function() { $(this).css({background: '#FFF'}); }
+            ).click(toggle_fun(cont, short_el, long_el, button, short_text, long_text));
+        };
+
+
+        // Fragments
+        $('.fragment-short-text').each(function() {
+            var fragment = $(this).closest('.fragment');
+            fragment.toggle_slide({
+                short_el: $(this),
+                long_el: fragment.find('.fragment-text')
+            })
+        });
+
+
+
+
+
+
+$('#themes-list-toggle').click(function(event) {
+    event.preventDefault();
+    $('#themes-list').toggle('slow');
+});
+
+
+$('.open-player').click(function(event) {
+    event.preventDefault();
+    window.open($(this).attr('href'),
+        'player',
+        'width=420, height=500'
+        );
+});
+
+
+        $('.book-list-index').click(function(){
+            $('.book-list-show-index').hide('slow');
+            if($(this).parent().next('ul:not(:hidden)').length == 0){
+               $(this).parent().next('ul').toggle('slow');
+           }
+            return false;
+        });
+
+
+    });
+})(jQuery)
+
index ee8a045..4857806 100644 (file)
@@ -90,9 +90,20 @@ function changeBannerText() {
     }
 }
 
-function autocomplete_result_handler(event, item) {
-    $(event.target).closest('form').submit();
+function autocomplete_format_item(ul, item) {
+    return $("<li></li>").data('item.autocomplete', item)
+    .append('<a href="'+item.url+'">'+item.label+ ' ('+item.category+')</a>')
+    .appendTo(ul);
 }
+
+function autocomplete_result_handler(event, ui) {
+    if (ui.item.url != undefined) {
+       location.href = ui.item.url;
+    } else {
+       $(event.target).closest('form').submit();
+    }
+}
+
 function serverTime() {
     var time = null;
     $.ajax({url: '/katalog/zegar/',
@@ -583,7 +594,10 @@ function serverTime() {
                 }
             }); 
         }*/       
-
+       $("#custom-pdf-link").toggle(
+           function(ev) { $(".custom-pdf").show(); return false; },
+           function(ev) { $(".custom-pdf").hide(); return false; }
+       );
     });
 })(jQuery)
 
diff --git a/wolnelektury/static/js/dialogs.js b/wolnelektury/static/js/dialogs.js
new file mode 100755 (executable)
index 0000000..bf9d94b
--- /dev/null
@@ -0,0 +1,64 @@
+(function($) {
+    $(function() {
+
+        // create containers for all ajaxable form links
+        $('.ajaxable').each(function() {
+            var $window = $("#ajaxable-window").clone();
+            $window.attr("id", this.id + "-window");
+            $('body').append($window);
+
+            var trigger = '#' + this.id;
+
+            var href = $(this).attr('href');
+            if (href.search('\\?') != -1)
+                href += '&ajax=1';
+            else href += '?ajax=1';
+
+            $window.jqm({
+                ajax: href,
+                ajaxText: '<p><img src="' + STATIC_URL + 'img/indicator.gif" alt="*"/> ' + gettext("Loading") + '</p>',
+                target: $('.target', $window)[0],
+                overlay: 60,
+                trigger: trigger,
+                onShow: function(hash) {
+                    var offset = $(hash.t).offset();
+                    hash.w.css({position: 'absolute', left: offset.left - hash.w.width() + $(hash.t).width(), top: offset.top});
+                    $('.header', hash.w).css({width: $(hash.t).width()});
+                    hash.w.show();
+                },
+                onLoad: function(hash) {
+                    $('form', hash.w).each(function() {
+                        if (this.action.search('[\\?&]ajax=1') != -1)
+                            return;
+                        if (this.action.search('\\?') != -1)
+                            this.action += '&ajax=1';
+                        else this.action += '?ajax=1';
+                    });
+                    $('form', hash.w).ajaxForm({
+                        dataType: 'json',
+                        target: $('.target', $window),
+                        success: function(response) {
+                            if (response.success) {
+                                $('.target', $window).text(response.message);
+                                setTimeout(function() { $window.jqmHide() }, 1000);
+                                if (response.redirect)
+                                    window.location = response.redirect;
+                            }
+                            else {
+                                $('.error', $window).remove();
+                                $.each(response.errors, function(id, errors) {
+                                    $('#id_' + id, $window).before('<span class="error">' + errors[0] + '</span>');
+                                });
+                                $('input[type=submit]', $window).removeAttr('disabled');
+                                return false;
+                            }
+                        }
+                    });
+                }
+            });
+        });
+
+
+    });
+})(jQuery)
+
diff --git a/wolnelektury/static/js/jquery-ui-1.8.16.custom.min.js b/wolnelektury/static/js/jquery-ui-1.8.16.custom.min.js
new file mode 100644 (file)
index 0000000..a9c6fa3
--- /dev/null
@@ -0,0 +1,149 @@
+/*!
+ * jQuery UI 1.8.16
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI
+ */
+(function(c,j){function k(a,b){var d=a.nodeName.toLowerCase();if("area"===d){b=a.parentNode;d=b.name;if(!a.href||!d||b.nodeName.toLowerCase()!=="map")return false;a=c("img[usemap=#"+d+"]")[0];return!!a&&l(a)}return(/input|select|textarea|button|object/.test(d)?!a.disabled:"a"==d?a.href||b:b)&&l(a)}function l(a){return!c(a).parents().andSelf().filter(function(){return c.curCSS(this,"visibility")==="hidden"||c.expr.filters.hidden(this)}).length}c.ui=c.ui||{};if(!c.ui.version){c.extend(c.ui,{version:"1.8.16",
+keyCode:{ALT:18,BACKSPACE:8,CAPS_LOCK:20,COMMA:188,COMMAND:91,COMMAND_LEFT:91,COMMAND_RIGHT:93,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,MENU:93,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38,WINDOWS:91}});c.fn.extend({propAttr:c.fn.prop||c.fn.attr,_focus:c.fn.focus,focus:function(a,b){return typeof a==="number"?this.each(function(){var d=
+this;setTimeout(function(){c(d).focus();b&&b.call(d)},a)}):this._focus.apply(this,arguments)},scrollParent:function(){var a;a=c.browser.msie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?this.parents().filter(function(){return/(relative|absolute|fixed)/.test(c.curCSS(this,"position",1))&&/(auto|scroll)/.test(c.curCSS(this,"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0):this.parents().filter(function(){return/(auto|scroll)/.test(c.curCSS(this,
+"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0);return/fixed/.test(this.css("position"))||!a.length?c(document):a},zIndex:function(a){if(a!==j)return this.css("zIndex",a);if(this.length){a=c(this[0]);for(var b;a.length&&a[0]!==document;){b=a.css("position");if(b==="absolute"||b==="relative"||b==="fixed"){b=parseInt(a.css("zIndex"),10);if(!isNaN(b)&&b!==0)return b}a=a.parent()}}return 0},disableSelection:function(){return this.bind((c.support.selectstart?"selectstart":
+"mousedown")+".ui-disableSelection",function(a){a.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}});c.each(["Width","Height"],function(a,b){function d(f,g,m,n){c.each(e,function(){g-=parseFloat(c.curCSS(f,"padding"+this,true))||0;if(m)g-=parseFloat(c.curCSS(f,"border"+this+"Width",true))||0;if(n)g-=parseFloat(c.curCSS(f,"margin"+this,true))||0});return g}var e=b==="Width"?["Left","Right"]:["Top","Bottom"],h=b.toLowerCase(),i={innerWidth:c.fn.innerWidth,innerHeight:c.fn.innerHeight,
+outerWidth:c.fn.outerWidth,outerHeight:c.fn.outerHeight};c.fn["inner"+b]=function(f){if(f===j)return i["inner"+b].call(this);return this.each(function(){c(this).css(h,d(this,f)+"px")})};c.fn["outer"+b]=function(f,g){if(typeof f!=="number")return i["outer"+b].call(this,f);return this.each(function(){c(this).css(h,d(this,f,true,g)+"px")})}});c.extend(c.expr[":"],{data:function(a,b,d){return!!c.data(a,d[3])},focusable:function(a){return k(a,!isNaN(c.attr(a,"tabindex")))},tabbable:function(a){var b=c.attr(a,
+"tabindex"),d=isNaN(b);return(d||b>=0)&&k(a,!d)}});c(function(){var a=document.body,b=a.appendChild(b=document.createElement("div"));c.extend(b.style,{minHeight:"100px",height:"auto",padding:0,borderWidth:0});c.support.minHeight=b.offsetHeight===100;c.support.selectstart="onselectstart"in b;a.removeChild(b).style.display="none"});c.extend(c.ui,{plugin:{add:function(a,b,d){a=c.ui[a].prototype;for(var e in d){a.plugins[e]=a.plugins[e]||[];a.plugins[e].push([b,d[e]])}},call:function(a,b,d){if((b=a.plugins[b])&&
+a.element[0].parentNode)for(var e=0;e<b.length;e++)a.options[b[e][0]]&&b[e][1].apply(a.element,d)}},contains:function(a,b){return document.compareDocumentPosition?a.compareDocumentPosition(b)&16:a!==b&&a.contains(b)},hasScroll:function(a,b){if(c(a).css("overflow")==="hidden")return false;b=b&&b==="left"?"scrollLeft":"scrollTop";var d=false;if(a[b]>0)return true;a[b]=1;d=a[b]>0;a[b]=0;return d},isOverAxis:function(a,b,d){return a>b&&a<b+d},isOver:function(a,b,d,e,h,i){return c.ui.isOverAxis(a,d,h)&&
+c.ui.isOverAxis(b,e,i)}})}})(jQuery);
+;/*!
+ * jQuery UI Widget 1.8.16
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Widget
+ */
+(function(b,j){if(b.cleanData){var k=b.cleanData;b.cleanData=function(a){for(var c=0,d;(d=a[c])!=null;c++)try{b(d).triggerHandler("remove")}catch(e){}k(a)}}else{var l=b.fn.remove;b.fn.remove=function(a,c){return this.each(function(){if(!c)if(!a||b.filter(a,[this]).length)b("*",this).add([this]).each(function(){try{b(this).triggerHandler("remove")}catch(d){}});return l.call(b(this),a,c)})}}b.widget=function(a,c,d){var e=a.split(".")[0],f;a=a.split(".")[1];f=e+"-"+a;if(!d){d=c;c=b.Widget}b.expr[":"][f]=
+function(h){return!!b.data(h,a)};b[e]=b[e]||{};b[e][a]=function(h,g){arguments.length&&this._createWidget(h,g)};c=new c;c.options=b.extend(true,{},c.options);b[e][a].prototype=b.extend(true,c,{namespace:e,widgetName:a,widgetEventPrefix:b[e][a].prototype.widgetEventPrefix||a,widgetBaseClass:f},d);b.widget.bridge(a,b[e][a])};b.widget.bridge=function(a,c){b.fn[a]=function(d){var e=typeof d==="string",f=Array.prototype.slice.call(arguments,1),h=this;d=!e&&f.length?b.extend.apply(null,[true,d].concat(f)):
+d;if(e&&d.charAt(0)==="_")return h;e?this.each(function(){var g=b.data(this,a),i=g&&b.isFunction(g[d])?g[d].apply(g,f):g;if(i!==g&&i!==j){h=i;return false}}):this.each(function(){var g=b.data(this,a);g?g.option(d||{})._init():b.data(this,a,new c(d,this))});return h}};b.Widget=function(a,c){arguments.length&&this._createWidget(a,c)};b.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",options:{disabled:false},_createWidget:function(a,c){b.data(c,this.widgetName,this);this.element=b(c);this.options=
+b.extend(true,{},this.options,this._getCreateOptions(),a);var d=this;this.element.bind("remove."+this.widgetName,function(){d.destroy()});this._create();this._trigger("create");this._init()},_getCreateOptions:function(){return b.metadata&&b.metadata.get(this.element[0])[this.widgetName]},_create:function(){},_init:function(){},destroy:function(){this.element.unbind("."+this.widgetName).removeData(this.widgetName);this.widget().unbind("."+this.widgetName).removeAttr("aria-disabled").removeClass(this.widgetBaseClass+
+"-disabled ui-state-disabled")},widget:function(){return this.element},option:function(a,c){var d=a;if(arguments.length===0)return b.extend({},this.options);if(typeof a==="string"){if(c===j)return this.options[a];d={};d[a]=c}this._setOptions(d);return this},_setOptions:function(a){var c=this;b.each(a,function(d,e){c._setOption(d,e)});return this},_setOption:function(a,c){this.options[a]=c;if(a==="disabled")this.widget()[c?"addClass":"removeClass"](this.widgetBaseClass+"-disabled ui-state-disabled").attr("aria-disabled",
+c);return this},enable:function(){return this._setOption("disabled",false)},disable:function(){return this._setOption("disabled",true)},_trigger:function(a,c,d){var e=this.options[a];c=b.Event(c);c.type=(a===this.widgetEventPrefix?a:this.widgetEventPrefix+a).toLowerCase();d=d||{};if(c.originalEvent){a=b.event.props.length;for(var f;a;){f=b.event.props[--a];c[f]=c.originalEvent[f]}}this.element.trigger(c,d);return!(b.isFunction(e)&&e.call(this.element[0],c,d)===false||c.isDefaultPrevented())}}})(jQuery);
+;/*!
+ * jQuery UI Mouse 1.8.16
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Mouse
+ *
+ * Depends:
+ *     jquery.ui.widget.js
+ */
+(function(b){var d=false;b(document).mouseup(function(){d=false});b.widget("ui.mouse",{options:{cancel:":input,option",distance:1,delay:0},_mouseInit:function(){var a=this;this.element.bind("mousedown."+this.widgetName,function(c){return a._mouseDown(c)}).bind("click."+this.widgetName,function(c){if(true===b.data(c.target,a.widgetName+".preventClickEvent")){b.removeData(c.target,a.widgetName+".preventClickEvent");c.stopImmediatePropagation();return false}});this.started=false},_mouseDestroy:function(){this.element.unbind("."+
+this.widgetName)},_mouseDown:function(a){if(!d){this._mouseStarted&&this._mouseUp(a);this._mouseDownEvent=a;var c=this,f=a.which==1,g=typeof this.options.cancel=="string"&&a.target.nodeName?b(a.target).closest(this.options.cancel).length:false;if(!f||g||!this._mouseCapture(a))return true;this.mouseDelayMet=!this.options.delay;if(!this.mouseDelayMet)this._mouseDelayTimer=setTimeout(function(){c.mouseDelayMet=true},this.options.delay);if(this._mouseDistanceMet(a)&&this._mouseDelayMet(a)){this._mouseStarted=
+this._mouseStart(a)!==false;if(!this._mouseStarted){a.preventDefault();return true}}true===b.data(a.target,this.widgetName+".preventClickEvent")&&b.removeData(a.target,this.widgetName+".preventClickEvent");this._mouseMoveDelegate=function(e){return c._mouseMove(e)};this._mouseUpDelegate=function(e){return c._mouseUp(e)};b(document).bind("mousemove."+this.widgetName,this._mouseMoveDelegate).bind("mouseup."+this.widgetName,this._mouseUpDelegate);a.preventDefault();return d=true}},_mouseMove:function(a){if(b.browser.msie&&
+!(document.documentMode>=9)&&!a.button)return this._mouseUp(a);if(this._mouseStarted){this._mouseDrag(a);return a.preventDefault()}if(this._mouseDistanceMet(a)&&this._mouseDelayMet(a))(this._mouseStarted=this._mouseStart(this._mouseDownEvent,a)!==false)?this._mouseDrag(a):this._mouseUp(a);return!this._mouseStarted},_mouseUp:function(a){b(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate);if(this._mouseStarted){this._mouseStarted=
+false;a.target==this._mouseDownEvent.target&&b.data(a.target,this.widgetName+".preventClickEvent",true);this._mouseStop(a)}return false},_mouseDistanceMet:function(a){return Math.max(Math.abs(this._mouseDownEvent.pageX-a.pageX),Math.abs(this._mouseDownEvent.pageY-a.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return true}})})(jQuery);
+;/*
+ * jQuery UI Position 1.8.16
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Position
+ */
+(function(c){c.ui=c.ui||{};var n=/left|center|right/,o=/top|center|bottom/,t=c.fn.position,u=c.fn.offset;c.fn.position=function(b){if(!b||!b.of)return t.apply(this,arguments);b=c.extend({},b);var a=c(b.of),d=a[0],g=(b.collision||"flip").split(" "),e=b.offset?b.offset.split(" "):[0,0],h,k,j;if(d.nodeType===9){h=a.width();k=a.height();j={top:0,left:0}}else if(d.setTimeout){h=a.width();k=a.height();j={top:a.scrollTop(),left:a.scrollLeft()}}else if(d.preventDefault){b.at="left top";h=k=0;j={top:b.of.pageY,
+left:b.of.pageX}}else{h=a.outerWidth();k=a.outerHeight();j=a.offset()}c.each(["my","at"],function(){var f=(b[this]||"").split(" ");if(f.length===1)f=n.test(f[0])?f.concat(["center"]):o.test(f[0])?["center"].concat(f):["center","center"];f[0]=n.test(f[0])?f[0]:"center";f[1]=o.test(f[1])?f[1]:"center";b[this]=f});if(g.length===1)g[1]=g[0];e[0]=parseInt(e[0],10)||0;if(e.length===1)e[1]=e[0];e[1]=parseInt(e[1],10)||0;if(b.at[0]==="right")j.left+=h;else if(b.at[0]==="center")j.left+=h/2;if(b.at[1]==="bottom")j.top+=
+k;else if(b.at[1]==="center")j.top+=k/2;j.left+=e[0];j.top+=e[1];return this.each(function(){var f=c(this),l=f.outerWidth(),m=f.outerHeight(),p=parseInt(c.curCSS(this,"marginLeft",true))||0,q=parseInt(c.curCSS(this,"marginTop",true))||0,v=l+p+(parseInt(c.curCSS(this,"marginRight",true))||0),w=m+q+(parseInt(c.curCSS(this,"marginBottom",true))||0),i=c.extend({},j),r;if(b.my[0]==="right")i.left-=l;else if(b.my[0]==="center")i.left-=l/2;if(b.my[1]==="bottom")i.top-=m;else if(b.my[1]==="center")i.top-=
+m/2;i.left=Math.round(i.left);i.top=Math.round(i.top);r={left:i.left-p,top:i.top-q};c.each(["left","top"],function(s,x){c.ui.position[g[s]]&&c.ui.position[g[s]][x](i,{targetWidth:h,targetHeight:k,elemWidth:l,elemHeight:m,collisionPosition:r,collisionWidth:v,collisionHeight:w,offset:e,my:b.my,at:b.at})});c.fn.bgiframe&&f.bgiframe();f.offset(c.extend(i,{using:b.using}))})};c.ui.position={fit:{left:function(b,a){var d=c(window);d=a.collisionPosition.left+a.collisionWidth-d.width()-d.scrollLeft();b.left=
+d>0?b.left-d:Math.max(b.left-a.collisionPosition.left,b.left)},top:function(b,a){var d=c(window);d=a.collisionPosition.top+a.collisionHeight-d.height()-d.scrollTop();b.top=d>0?b.top-d:Math.max(b.top-a.collisionPosition.top,b.top)}},flip:{left:function(b,a){if(a.at[0]!=="center"){var d=c(window);d=a.collisionPosition.left+a.collisionWidth-d.width()-d.scrollLeft();var g=a.my[0]==="left"?-a.elemWidth:a.my[0]==="right"?a.elemWidth:0,e=a.at[0]==="left"?a.targetWidth:-a.targetWidth,h=-2*a.offset[0];b.left+=
+a.collisionPosition.left<0?g+e+h:d>0?g+e+h:0}},top:function(b,a){if(a.at[1]!=="center"){var d=c(window);d=a.collisionPosition.top+a.collisionHeight-d.height()-d.scrollTop();var g=a.my[1]==="top"?-a.elemHeight:a.my[1]==="bottom"?a.elemHeight:0,e=a.at[1]==="top"?a.targetHeight:-a.targetHeight,h=-2*a.offset[1];b.top+=a.collisionPosition.top<0?g+e+h:d>0?g+e+h:0}}}};if(!c.offset.setOffset){c.offset.setOffset=function(b,a){if(/static/.test(c.curCSS(b,"position")))b.style.position="relative";var d=c(b),
+g=d.offset(),e=parseInt(c.curCSS(b,"top",true),10)||0,h=parseInt(c.curCSS(b,"left",true),10)||0;g={top:a.top-g.top+e,left:a.left-g.left+h};"using"in a?a.using.call(b,g):d.css(g)};c.fn.offset=function(b){var a=this[0];if(!a||!a.ownerDocument)return null;if(b)return this.each(function(){c.offset.setOffset(this,b)});return u.call(this)}}})(jQuery);
+;/*
+ * jQuery UI Draggable 1.8.16
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Draggables
+ *
+ * Depends:
+ *     jquery.ui.core.js
+ *     jquery.ui.mouse.js
+ *     jquery.ui.widget.js
+ */
+(function(d){d.widget("ui.draggable",d.ui.mouse,{widgetEventPrefix:"drag",options:{addClasses:true,appendTo:"parent",axis:false,connectToSortable:false,containment:false,cursor:"auto",cursorAt:false,grid:false,handle:false,helper:"original",iframeFix:false,opacity:false,refreshPositions:false,revert:false,revertDuration:500,scope:"default",scroll:true,scrollSensitivity:20,scrollSpeed:20,snap:false,snapMode:"both",snapTolerance:20,stack:false,zIndex:false},_create:function(){if(this.options.helper==
+"original"&&!/^(?:r|a|f)/.test(this.element.css("position")))this.element[0].style.position="relative";this.options.addClasses&&this.element.addClass("ui-draggable");this.options.disabled&&this.element.addClass("ui-draggable-disabled");this._mouseInit()},destroy:function(){if(this.element.data("draggable")){this.element.removeData("draggable").unbind(".draggable").removeClass("ui-draggable ui-draggable-dragging ui-draggable-disabled");this._mouseDestroy();return this}},_mouseCapture:function(a){var b=
+this.options;if(this.helper||b.disabled||d(a.target).is(".ui-resizable-handle"))return false;this.handle=this._getHandle(a);if(!this.handle)return false;if(b.iframeFix)d(b.iframeFix===true?"iframe":b.iframeFix).each(function(){d('<div class="ui-draggable-iframeFix" style="background: #fff;"></div>').css({width:this.offsetWidth+"px",height:this.offsetHeight+"px",position:"absolute",opacity:"0.001",zIndex:1E3}).css(d(this).offset()).appendTo("body")});return true},_mouseStart:function(a){var b=this.options;
+this.helper=this._createHelper(a);this._cacheHelperProportions();if(d.ui.ddmanager)d.ui.ddmanager.current=this;this._cacheMargins();this.cssPosition=this.helper.css("position");this.scrollParent=this.helper.scrollParent();this.offset=this.positionAbs=this.element.offset();this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left};d.extend(this.offset,{click:{left:a.pageX-this.offset.left,top:a.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()});
+this.originalPosition=this.position=this._generatePosition(a);this.originalPageX=a.pageX;this.originalPageY=a.pageY;b.cursorAt&&this._adjustOffsetFromHelper(b.cursorAt);b.containment&&this._setContainment();if(this._trigger("start",a)===false){this._clear();return false}this._cacheHelperProportions();d.ui.ddmanager&&!b.dropBehaviour&&d.ui.ddmanager.prepareOffsets(this,a);this.helper.addClass("ui-draggable-dragging");this._mouseDrag(a,true);d.ui.ddmanager&&d.ui.ddmanager.dragStart(this,a);return true},
+_mouseDrag:function(a,b){this.position=this._generatePosition(a);this.positionAbs=this._convertPositionTo("absolute");if(!b){b=this._uiHash();if(this._trigger("drag",a,b)===false){this._mouseUp({});return false}this.position=b.position}if(!this.options.axis||this.options.axis!="y")this.helper[0].style.left=this.position.left+"px";if(!this.options.axis||this.options.axis!="x")this.helper[0].style.top=this.position.top+"px";d.ui.ddmanager&&d.ui.ddmanager.drag(this,a);return false},_mouseStop:function(a){var b=
+false;if(d.ui.ddmanager&&!this.options.dropBehaviour)b=d.ui.ddmanager.drop(this,a);if(this.dropped){b=this.dropped;this.dropped=false}if((!this.element[0]||!this.element[0].parentNode)&&this.options.helper=="original")return false;if(this.options.revert=="invalid"&&!b||this.options.revert=="valid"&&b||this.options.revert===true||d.isFunction(this.options.revert)&&this.options.revert.call(this.element,b)){var c=this;d(this.helper).animate(this.originalPosition,parseInt(this.options.revertDuration,
+10),function(){c._trigger("stop",a)!==false&&c._clear()})}else this._trigger("stop",a)!==false&&this._clear();return false},_mouseUp:function(a){this.options.iframeFix===true&&d("div.ui-draggable-iframeFix").each(function(){this.parentNode.removeChild(this)});d.ui.ddmanager&&d.ui.ddmanager.dragStop(this,a);return d.ui.mouse.prototype._mouseUp.call(this,a)},cancel:function(){this.helper.is(".ui-draggable-dragging")?this._mouseUp({}):this._clear();return this},_getHandle:function(a){var b=!this.options.handle||
+!d(this.options.handle,this.element).length?true:false;d(this.options.handle,this.element).find("*").andSelf().each(function(){if(this==a.target)b=true});return b},_createHelper:function(a){var b=this.options;a=d.isFunction(b.helper)?d(b.helper.apply(this.element[0],[a])):b.helper=="clone"?this.element.clone().removeAttr("id"):this.element;a.parents("body").length||a.appendTo(b.appendTo=="parent"?this.element[0].parentNode:b.appendTo);a[0]!=this.element[0]&&!/(fixed|absolute)/.test(a.css("position"))&&
+a.css("position","absolute");return a},_adjustOffsetFromHelper:function(a){if(typeof a=="string")a=a.split(" ");if(d.isArray(a))a={left:+a[0],top:+a[1]||0};if("left"in a)this.offset.click.left=a.left+this.margins.left;if("right"in a)this.offset.click.left=this.helperProportions.width-a.right+this.margins.left;if("top"in a)this.offset.click.top=a.top+this.margins.top;if("bottom"in a)this.offset.click.top=this.helperProportions.height-a.bottom+this.margins.top},_getParentOffset:function(){this.offsetParent=
+this.helper.offsetParent();var a=this.offsetParent.offset();if(this.cssPosition=="absolute"&&this.scrollParent[0]!=document&&d.ui.contains(this.scrollParent[0],this.offsetParent[0])){a.left+=this.scrollParent.scrollLeft();a.top+=this.scrollParent.scrollTop()}if(this.offsetParent[0]==document.body||this.offsetParent[0].tagName&&this.offsetParent[0].tagName.toLowerCase()=="html"&&d.browser.msie)a={top:0,left:0};return{top:a.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:a.left+(parseInt(this.offsetParent.css("borderLeftWidth"),
+10)||0)}},_getRelativeOffset:function(){if(this.cssPosition=="relative"){var a=this.element.position();return{top:a.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:a.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}else return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.element.css("marginLeft"),10)||0,top:parseInt(this.element.css("marginTop"),10)||0,right:parseInt(this.element.css("marginRight"),10)||0,bottom:parseInt(this.element.css("marginBottom"),
+10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var a=this.options;if(a.containment=="parent")a.containment=this.helper[0].parentNode;if(a.containment=="document"||a.containment=="window")this.containment=[a.containment=="document"?0:d(window).scrollLeft()-this.offset.relative.left-this.offset.parent.left,a.containment=="document"?0:d(window).scrollTop()-this.offset.relative.top-this.offset.parent.top,
+(a.containment=="document"?0:d(window).scrollLeft())+d(a.containment=="document"?document:window).width()-this.helperProportions.width-this.margins.left,(a.containment=="document"?0:d(window).scrollTop())+(d(a.containment=="document"?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top];if(!/^(document|window|parent)$/.test(a.containment)&&a.containment.constructor!=Array){a=d(a.containment);var b=a[0];if(b){a.offset();var c=d(b).css("overflow")!=
+"hidden";this.containment=[(parseInt(d(b).css("borderLeftWidth"),10)||0)+(parseInt(d(b).css("paddingLeft"),10)||0),(parseInt(d(b).css("borderTopWidth"),10)||0)+(parseInt(d(b).css("paddingTop"),10)||0),(c?Math.max(b.scrollWidth,b.offsetWidth):b.offsetWidth)-(parseInt(d(b).css("borderLeftWidth"),10)||0)-(parseInt(d(b).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left-this.margins.right,(c?Math.max(b.scrollHeight,b.offsetHeight):b.offsetHeight)-(parseInt(d(b).css("borderTopWidth"),
+10)||0)-(parseInt(d(b).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top-this.margins.bottom];this.relative_container=a}}else if(a.containment.constructor==Array)this.containment=a.containment},_convertPositionTo:function(a,b){if(!b)b=this.position;a=a=="absolute"?1:-1;var c=this.cssPosition=="absolute"&&!(this.scrollParent[0]!=document&&d.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,f=/(html|body)/i.test(c[0].tagName);return{top:b.top+
+this.offset.relative.top*a+this.offset.parent.top*a-(d.browser.safari&&d.browser.version<526&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():f?0:c.scrollTop())*a),left:b.left+this.offset.relative.left*a+this.offset.parent.left*a-(d.browser.safari&&d.browser.version<526&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():f?0:c.scrollLeft())*a)}},_generatePosition:function(a){var b=this.options,c=this.cssPosition=="absolute"&&
+!(this.scrollParent[0]!=document&&d.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,f=/(html|body)/i.test(c[0].tagName),e=a.pageX,h=a.pageY;if(this.originalPosition){var g;if(this.containment){if(this.relative_container){g=this.relative_container.offset();g=[this.containment[0]+g.left,this.containment[1]+g.top,this.containment[2]+g.left,this.containment[3]+g.top]}else g=this.containment;if(a.pageX-this.offset.click.left<g[0])e=g[0]+this.offset.click.left;
+if(a.pageY-this.offset.click.top<g[1])h=g[1]+this.offset.click.top;if(a.pageX-this.offset.click.left>g[2])e=g[2]+this.offset.click.left;if(a.pageY-this.offset.click.top>g[3])h=g[3]+this.offset.click.top}if(b.grid){h=b.grid[1]?this.originalPageY+Math.round((h-this.originalPageY)/b.grid[1])*b.grid[1]:this.originalPageY;h=g?!(h-this.offset.click.top<g[1]||h-this.offset.click.top>g[3])?h:!(h-this.offset.click.top<g[1])?h-b.grid[1]:h+b.grid[1]:h;e=b.grid[0]?this.originalPageX+Math.round((e-this.originalPageX)/
+b.grid[0])*b.grid[0]:this.originalPageX;e=g?!(e-this.offset.click.left<g[0]||e-this.offset.click.left>g[2])?e:!(e-this.offset.click.left<g[0])?e-b.grid[0]:e+b.grid[0]:e}}return{top:h-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+(d.browser.safari&&d.browser.version<526&&this.cssPosition=="fixed"?0:this.cssPosition=="fixed"?-this.scrollParent.scrollTop():f?0:c.scrollTop()),left:e-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+(d.browser.safari&&d.browser.version<
+526&&this.cssPosition=="fixed"?0:this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():f?0:c.scrollLeft())}},_clear:function(){this.helper.removeClass("ui-draggable-dragging");this.helper[0]!=this.element[0]&&!this.cancelHelperRemoval&&this.helper.remove();this.helper=null;this.cancelHelperRemoval=false},_trigger:function(a,b,c){c=c||this._uiHash();d.ui.plugin.call(this,a,[b,c]);if(a=="drag")this.positionAbs=this._convertPositionTo("absolute");return d.Widget.prototype._trigger.call(this,a,b,
+c)},plugins:{},_uiHash:function(){return{helper:this.helper,position:this.position,originalPosition:this.originalPosition,offset:this.positionAbs}}});d.extend(d.ui.draggable,{version:"1.8.16"});d.ui.plugin.add("draggable","connectToSortable",{start:function(a,b){var c=d(this).data("draggable"),f=c.options,e=d.extend({},b,{item:c.element});c.sortables=[];d(f.connectToSortable).each(function(){var h=d.data(this,"sortable");if(h&&!h.options.disabled){c.sortables.push({instance:h,shouldRevert:h.options.revert});
+h.refreshPositions();h._trigger("activate",a,e)}})},stop:function(a,b){var c=d(this).data("draggable"),f=d.extend({},b,{item:c.element});d.each(c.sortables,function(){if(this.instance.isOver){this.instance.isOver=0;c.cancelHelperRemoval=true;this.instance.cancelHelperRemoval=false;if(this.shouldRevert)this.instance.options.revert=true;this.instance._mouseStop(a);this.instance.options.helper=this.instance.options._helper;c.options.helper=="original"&&this.instance.currentItem.css({top:"auto",left:"auto"})}else{this.instance.cancelHelperRemoval=
+false;this.instance._trigger("deactivate",a,f)}})},drag:function(a,b){var c=d(this).data("draggable"),f=this;d.each(c.sortables,function(){this.instance.positionAbs=c.positionAbs;this.instance.helperProportions=c.helperProportions;this.instance.offset.click=c.offset.click;if(this.instance._intersectsWith(this.instance.containerCache)){if(!this.instance.isOver){this.instance.isOver=1;this.instance.currentItem=d(f).clone().removeAttr("id").appendTo(this.instance.element).data("sortable-item",true);
+this.instance.options._helper=this.instance.options.helper;this.instance.options.helper=function(){return b.helper[0]};a.target=this.instance.currentItem[0];this.instance._mouseCapture(a,true);this.instance._mouseStart(a,true,true);this.instance.offset.click.top=c.offset.click.top;this.instance.offset.click.left=c.offset.click.left;this.instance.offset.parent.left-=c.offset.parent.left-this.instance.offset.parent.left;this.instance.offset.parent.top-=c.offset.parent.top-this.instance.offset.parent.top;
+c._trigger("toSortable",a);c.dropped=this.instance.element;c.currentItem=c.element;this.instance.fromOutside=c}this.instance.currentItem&&this.instance._mouseDrag(a)}else if(this.instance.isOver){this.instance.isOver=0;this.instance.cancelHelperRemoval=true;this.instance.options.revert=false;this.instance._trigger("out",a,this.instance._uiHash(this.instance));this.instance._mouseStop(a,true);this.instance.options.helper=this.instance.options._helper;this.instance.currentItem.remove();this.instance.placeholder&&
+this.instance.placeholder.remove();c._trigger("fromSortable",a);c.dropped=false}})}});d.ui.plugin.add("draggable","cursor",{start:function(){var a=d("body"),b=d(this).data("draggable").options;if(a.css("cursor"))b._cursor=a.css("cursor");a.css("cursor",b.cursor)},stop:function(){var a=d(this).data("draggable").options;a._cursor&&d("body").css("cursor",a._cursor)}});d.ui.plugin.add("draggable","opacity",{start:function(a,b){a=d(b.helper);b=d(this).data("draggable").options;if(a.css("opacity"))b._opacity=
+a.css("opacity");a.css("opacity",b.opacity)},stop:function(a,b){a=d(this).data("draggable").options;a._opacity&&d(b.helper).css("opacity",a._opacity)}});d.ui.plugin.add("draggable","scroll",{start:function(){var a=d(this).data("draggable");if(a.scrollParent[0]!=document&&a.scrollParent[0].tagName!="HTML")a.overflowOffset=a.scrollParent.offset()},drag:function(a){var b=d(this).data("draggable"),c=b.options,f=false;if(b.scrollParent[0]!=document&&b.scrollParent[0].tagName!="HTML"){if(!c.axis||c.axis!=
+"x")if(b.overflowOffset.top+b.scrollParent[0].offsetHeight-a.pageY<c.scrollSensitivity)b.scrollParent[0].scrollTop=f=b.scrollParent[0].scrollTop+c.scrollSpeed;else if(a.pageY-b.overflowOffset.top<c.scrollSensitivity)b.scrollParent[0].scrollTop=f=b.scrollParent[0].scrollTop-c.scrollSpeed;if(!c.axis||c.axis!="y")if(b.overflowOffset.left+b.scrollParent[0].offsetWidth-a.pageX<c.scrollSensitivity)b.scrollParent[0].scrollLeft=f=b.scrollParent[0].scrollLeft+c.scrollSpeed;else if(a.pageX-b.overflowOffset.left<
+c.scrollSensitivity)b.scrollParent[0].scrollLeft=f=b.scrollParent[0].scrollLeft-c.scrollSpeed}else{if(!c.axis||c.axis!="x")if(a.pageY-d(document).scrollTop()<c.scrollSensitivity)f=d(document).scrollTop(d(document).scrollTop()-c.scrollSpeed);else if(d(window).height()-(a.pageY-d(document).scrollTop())<c.scrollSensitivity)f=d(document).scrollTop(d(document).scrollTop()+c.scrollSpeed);if(!c.axis||c.axis!="y")if(a.pageX-d(document).scrollLeft()<c.scrollSensitivity)f=d(document).scrollLeft(d(document).scrollLeft()-
+c.scrollSpeed);else if(d(window).width()-(a.pageX-d(document).scrollLeft())<c.scrollSensitivity)f=d(document).scrollLeft(d(document).scrollLeft()+c.scrollSpeed)}f!==false&&d.ui.ddmanager&&!c.dropBehaviour&&d.ui.ddmanager.prepareOffsets(b,a)}});d.ui.plugin.add("draggable","snap",{start:function(){var a=d(this).data("draggable"),b=a.options;a.snapElements=[];d(b.snap.constructor!=String?b.snap.items||":data(draggable)":b.snap).each(function(){var c=d(this),f=c.offset();this!=a.element[0]&&a.snapElements.push({item:this,
+width:c.outerWidth(),height:c.outerHeight(),top:f.top,left:f.left})})},drag:function(a,b){for(var c=d(this).data("draggable"),f=c.options,e=f.snapTolerance,h=b.offset.left,g=h+c.helperProportions.width,n=b.offset.top,o=n+c.helperProportions.height,i=c.snapElements.length-1;i>=0;i--){var j=c.snapElements[i].left,l=j+c.snapElements[i].width,k=c.snapElements[i].top,m=k+c.snapElements[i].height;if(j-e<h&&h<l+e&&k-e<n&&n<m+e||j-e<h&&h<l+e&&k-e<o&&o<m+e||j-e<g&&g<l+e&&k-e<n&&n<m+e||j-e<g&&g<l+e&&k-e<o&&
+o<m+e){if(f.snapMode!="inner"){var p=Math.abs(k-o)<=e,q=Math.abs(m-n)<=e,r=Math.abs(j-g)<=e,s=Math.abs(l-h)<=e;if(p)b.position.top=c._convertPositionTo("relative",{top:k-c.helperProportions.height,left:0}).top-c.margins.top;if(q)b.position.top=c._convertPositionTo("relative",{top:m,left:0}).top-c.margins.top;if(r)b.position.left=c._convertPositionTo("relative",{top:0,left:j-c.helperProportions.width}).left-c.margins.left;if(s)b.position.left=c._convertPositionTo("relative",{top:0,left:l}).left-c.margins.left}var t=
+p||q||r||s;if(f.snapMode!="outer"){p=Math.abs(k-n)<=e;q=Math.abs(m-o)<=e;r=Math.abs(j-h)<=e;s=Math.abs(l-g)<=e;if(p)b.position.top=c._convertPositionTo("relative",{top:k,left:0}).top-c.margins.top;if(q)b.position.top=c._convertPositionTo("relative",{top:m-c.helperProportions.height,left:0}).top-c.margins.top;if(r)b.position.left=c._convertPositionTo("relative",{top:0,left:j}).left-c.margins.left;if(s)b.position.left=c._convertPositionTo("relative",{top:0,left:l-c.helperProportions.width}).left-c.margins.left}if(!c.snapElements[i].snapping&&
+(p||q||r||s||t))c.options.snap.snap&&c.options.snap.snap.call(c.element,a,d.extend(c._uiHash(),{snapItem:c.snapElements[i].item}));c.snapElements[i].snapping=p||q||r||s||t}else{c.snapElements[i].snapping&&c.options.snap.release&&c.options.snap.release.call(c.element,a,d.extend(c._uiHash(),{snapItem:c.snapElements[i].item}));c.snapElements[i].snapping=false}}}});d.ui.plugin.add("draggable","stack",{start:function(){var a=d(this).data("draggable").options;a=d.makeArray(d(a.stack)).sort(function(c,f){return(parseInt(d(c).css("zIndex"),
+10)||0)-(parseInt(d(f).css("zIndex"),10)||0)});if(a.length){var b=parseInt(a[0].style.zIndex)||0;d(a).each(function(c){this.style.zIndex=b+c});this[0].style.zIndex=b+a.length}}});d.ui.plugin.add("draggable","zIndex",{start:function(a,b){a=d(b.helper);b=d(this).data("draggable").options;if(a.css("zIndex"))b._zIndex=a.css("zIndex");a.css("zIndex",b.zIndex)},stop:function(a,b){a=d(this).data("draggable").options;a._zIndex&&d(b.helper).css("zIndex",a._zIndex)}})})(jQuery);
+;/*
+ * jQuery UI Autocomplete 1.8.16
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Autocomplete
+ *
+ * Depends:
+ *     jquery.ui.core.js
+ *     jquery.ui.widget.js
+ *     jquery.ui.position.js
+ */
+(function(d){var e=0;d.widget("ui.autocomplete",{options:{appendTo:"body",autoFocus:false,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null},pending:0,_create:function(){var a=this,b=this.element[0].ownerDocument,g;this.element.addClass("ui-autocomplete-input").attr("autocomplete","off").attr({role:"textbox","aria-autocomplete":"list","aria-haspopup":"true"}).bind("keydown.autocomplete",function(c){if(!(a.options.disabled||a.element.propAttr("readOnly"))){g=
+false;var f=d.ui.keyCode;switch(c.keyCode){case f.PAGE_UP:a._move("previousPage",c);break;case f.PAGE_DOWN:a._move("nextPage",c);break;case f.UP:a._move("previous",c);c.preventDefault();break;case f.DOWN:a._move("next",c);c.preventDefault();break;case f.ENTER:case f.NUMPAD_ENTER:if(a.menu.active){g=true;c.preventDefault()}case f.TAB:if(!a.menu.active)return;a.menu.select(c);break;case f.ESCAPE:a.element.val(a.term);a.close(c);break;default:clearTimeout(a.searching);a.searching=setTimeout(function(){if(a.term!=
+a.element.val()){a.selectedItem=null;a.search(null,c)}},a.options.delay);break}}}).bind("keypress.autocomplete",function(c){if(g){g=false;c.preventDefault()}}).bind("focus.autocomplete",function(){if(!a.options.disabled){a.selectedItem=null;a.previous=a.element.val()}}).bind("blur.autocomplete",function(c){if(!a.options.disabled){clearTimeout(a.searching);a.closing=setTimeout(function(){a.close(c);a._change(c)},150)}});this._initSource();this.response=function(){return a._response.apply(a,arguments)};
+this.menu=d("<ul></ul>").addClass("ui-autocomplete").appendTo(d(this.options.appendTo||"body",b)[0]).mousedown(function(c){var f=a.menu.element[0];d(c.target).closest(".ui-menu-item").length||setTimeout(function(){d(document).one("mousedown",function(h){h.target!==a.element[0]&&h.target!==f&&!d.ui.contains(f,h.target)&&a.close()})},1);setTimeout(function(){clearTimeout(a.closing)},13)}).menu({focus:function(c,f){f=f.item.data("item.autocomplete");false!==a._trigger("focus",c,{item:f})&&/^key/.test(c.originalEvent.type)&&
+a.element.val(f.value)},selected:function(c,f){var h=f.item.data("item.autocomplete"),i=a.previous;if(a.element[0]!==b.activeElement){a.element.focus();a.previous=i;setTimeout(function(){a.previous=i;a.selectedItem=h},1)}false!==a._trigger("select",c,{item:h})&&a.element.val(h.value);a.term=a.element.val();a.close(c);a.selectedItem=h},blur:function(){a.menu.element.is(":visible")&&a.element.val()!==a.term&&a.element.val(a.term)}}).zIndex(this.element.zIndex()+1).css({top:0,left:0}).hide().data("menu");
+d.fn.bgiframe&&this.menu.element.bgiframe()},destroy:function(){this.element.removeClass("ui-autocomplete-input").removeAttr("autocomplete").removeAttr("role").removeAttr("aria-autocomplete").removeAttr("aria-haspopup");this.menu.element.remove();d.Widget.prototype.destroy.call(this)},_setOption:function(a,b){d.Widget.prototype._setOption.apply(this,arguments);a==="source"&&this._initSource();if(a==="appendTo")this.menu.element.appendTo(d(b||"body",this.element[0].ownerDocument)[0]);a==="disabled"&&
+b&&this.xhr&&this.xhr.abort()},_initSource:function(){var a=this,b,g;if(d.isArray(this.options.source)){b=this.options.source;this.source=function(c,f){f(d.ui.autocomplete.filter(b,c.term))}}else if(typeof this.options.source==="string"){g=this.options.source;this.source=function(c,f){a.xhr&&a.xhr.abort();a.xhr=d.ajax({url:g,data:c,dataType:"json",autocompleteRequest:++e,success:function(h){this.autocompleteRequest===e&&f(h)},error:function(){this.autocompleteRequest===e&&f([])}})}}else this.source=
+this.options.source},search:function(a,b){a=a!=null?a:this.element.val();this.term=this.element.val();if(a.length<this.options.minLength)return this.close(b);clearTimeout(this.closing);if(this._trigger("search",b)!==false)return this._search(a)},_search:function(a){this.pending++;this.element.addClass("ui-autocomplete-loading");this.source({term:a},this.response)},_response:function(a){if(!this.options.disabled&&a&&a.length){a=this._normalize(a);this._suggest(a);this._trigger("open")}else this.close();
+this.pending--;this.pending||this.element.removeClass("ui-autocomplete-loading")},close:function(a){clearTimeout(this.closing);if(this.menu.element.is(":visible")){this.menu.element.hide();this.menu.deactivate();this._trigger("close",a)}},_change:function(a){this.previous!==this.element.val()&&this._trigger("change",a,{item:this.selectedItem})},_normalize:function(a){if(a.length&&a[0].label&&a[0].value)return a;return d.map(a,function(b){if(typeof b==="string")return{label:b,value:b};return d.extend({label:b.label||
+b.value,value:b.value||b.label},b)})},_suggest:function(a){var b=this.menu.element.empty().zIndex(this.element.zIndex()+1);this._renderMenu(b,a);this.menu.deactivate();this.menu.refresh();b.show();this._resizeMenu();b.position(d.extend({of:this.element},this.options.position));this.options.autoFocus&&this.menu.next(new d.Event("mouseover"))},_resizeMenu:function(){var a=this.menu.element;a.outerWidth(Math.max(a.width("").outerWidth(),this.element.outerWidth()))},_renderMenu:function(a,b){var g=this;
+d.each(b,function(c,f){g._renderItem(a,f)})},_renderItem:function(a,b){return d("<li></li>").data("item.autocomplete",b).append(d("<a></a>").text(b.label)).appendTo(a)},_move:function(a,b){if(this.menu.element.is(":visible"))if(this.menu.first()&&/^previous/.test(a)||this.menu.last()&&/^next/.test(a)){this.element.val(this.term);this.menu.deactivate()}else this.menu[a](b);else this.search(null,b)},widget:function(){return this.menu.element}});d.extend(d.ui.autocomplete,{escapeRegex:function(a){return a.replace(/[-[\]{}()*+?.,\\^$|#\s]/g,
+"\\$&")},filter:function(a,b){var g=new RegExp(d.ui.autocomplete.escapeRegex(b),"i");return d.grep(a,function(c){return g.test(c.label||c.value||c)})}})})(jQuery);
+(function(d){d.widget("ui.menu",{_create:function(){var e=this;this.element.addClass("ui-menu ui-widget ui-widget-content ui-corner-all").attr({role:"listbox","aria-activedescendant":"ui-active-menuitem"}).click(function(a){if(d(a.target).closest(".ui-menu-item a").length){a.preventDefault();e.select(a)}});this.refresh()},refresh:function(){var e=this;this.element.children("li:not(.ui-menu-item):has(a)").addClass("ui-menu-item").attr("role","menuitem").children("a").addClass("ui-corner-all").attr("tabindex",
+-1).mouseenter(function(a){e.activate(a,d(this).parent())}).mouseleave(function(){e.deactivate()})},activate:function(e,a){this.deactivate();if(this.hasScroll()){var b=a.offset().top-this.element.offset().top,g=this.element.scrollTop(),c=this.element.height();if(b<0)this.element.scrollTop(g+b);else b>=c&&this.element.scrollTop(g+b-c+a.height())}this.active=a.eq(0).children("a").addClass("ui-state-hover").attr("id","ui-active-menuitem").end();this._trigger("focus",e,{item:a})},deactivate:function(){if(this.active){this.active.children("a").removeClass("ui-state-hover").removeAttr("id");
+this._trigger("blur");this.active=null}},next:function(e){this.move("next",".ui-menu-item:first",e)},previous:function(e){this.move("prev",".ui-menu-item:last",e)},first:function(){return this.active&&!this.active.prevAll(".ui-menu-item").length},last:function(){return this.active&&!this.active.nextAll(".ui-menu-item").length},move:function(e,a,b){if(this.active){e=this.active[e+"All"](".ui-menu-item").eq(0);e.length?this.activate(b,e):this.activate(b,this.element.children(a))}else this.activate(b,
+this.element.children(a))},nextPage:function(e){if(this.hasScroll())if(!this.active||this.last())this.activate(e,this.element.children(".ui-menu-item:first"));else{var a=this.active.offset().top,b=this.element.height(),g=this.element.children(".ui-menu-item").filter(function(){var c=d(this).offset().top-a-b+d(this).height();return c<10&&c>-10});g.length||(g=this.element.children(".ui-menu-item:last"));this.activate(e,g)}else this.activate(e,this.element.children(".ui-menu-item").filter(!this.active||
+this.last()?":first":":last"))},previousPage:function(e){if(this.hasScroll())if(!this.active||this.first())this.activate(e,this.element.children(".ui-menu-item:last"));else{var a=this.active.offset().top,b=this.element.height();result=this.element.children(".ui-menu-item").filter(function(){var g=d(this).offset().top-a+b-d(this).height();return g<10&&g>-10});result.length||(result=this.element.children(".ui-menu-item:first"));this.activate(e,result)}else this.activate(e,this.element.children(".ui-menu-item").filter(!this.active||
+this.first()?":last":":first"))},hasScroll:function(){return this.element.height()<this.element[d.fn.prop?"prop":"attr"]("scrollHeight")},select:function(e){this._trigger("selected",e,{item:this.active})}})})(jQuery);
+;
\ No newline at end of file
index 248bb19..3aac816 100644 (file)
 /*
  * jqModal - Minimalist Modaling with jQuery
+ *   (http://dev.iceburg.net/jquery/jqModal/)
  *
- * Copyright (c) 2007 Brice Burgess <bhb@iceburg.net>, http://www.iceburg.net
- * Licensed under the MIT License:
- * http://www.opensource.org/licenses/mit-license.php
- *
- * $Version: 2007.??.?? +r12 beta
- * Requires: jQuery 1.1.3+
+ * Copyright (c) 2007,2008 Brice Burgess <bhb@iceburg.net>
+ * Dual licensed under the MIT and GPL licenses:
+ *   http://www.opensource.org/licenses/mit-license.php
+ *   http://www.gnu.org/licenses/gpl.html
+ * 
+ * $Version: 03/01/2009 +r14
  */
 (function($) {
-/**
- * Initialize a set of elements as "modals". Modals typically are popup dialogs,
- * notices, modal windows, and image containers. An expando ("_jqm") containing
- * the UUID or "serial" of the modal is added to each element. This expando helps
- * reference the modal's settings in the jqModal Hash Object (jQuery.jqm.hash)
- *
- * Accepts a parameter object with the following modal settings;
- *
- * (Integer) zIndex - Desired z-Index of the modal. This setting does not override (has no effect on) preexisting z-Index styling (set via CSS or inline style).
- * (Integer) overlay - [0-100] Translucency percentage (opacity) of the body covering overlay. Set to 0 for NO overlay, and up to 100 for a 100% opaque overlay.
- * (String) overlayClass - This class is applied to the body covering overlay. Allows CSS control of the overlay look (tint, background image, etc.).
- * (String) closeClass - A close trigger is added to all elements matching this class within the modal.
- * (Mixed) trigger - An open trigger is added to all matching elements within the DOM. Trigger can be a selector String, a jQuery collection of elements, a DOM element, or a False boolean.
- * (Mixed) ajax - If not false; The URL (string) to load content from via an AJAX request.
- *                If ajax begins with a "@", the URL is extracted from the requested attribute of the triggering element.
- * (Mixed) target - If not false; The element within the modal to load the ajax response (content) into. Allows retention of modal design (e.g. framing and close elements are not overwritten by the AJAX response).
- *                  Target may be a selector string, jQuery collection of elements, or a DOM element -- but MUST exist within (as a child of) the modal.
- * (Boolean) modal - If true, user interactivity will be locked to the modal window until closed.
- * (Boolean) toTop - If true, modal will be posistioned as a first child of the BODY element when opened, and its DOM posistion restored when closed. This aids in overcoming z-Index stacking order/containment issues where overlay covers whole page *including* modal.
- * (Mixed) onShow - User defined callback function fired when modal opened.
- * (Mixed) onHide - User defined callback function fired when modal closed.
- * (Mixed) onLoad - User defined callback function fired when ajax content loads.
- *
- * @name jqm
- * @param Map options User defined settings for the modal(s).
- * @type jQuery
- * @cat Plugins/jqModal
- */
-$.fn.jqm=function(p){
-var o = {
-zIndex: 3000,
+$.fn.jqm=function(o){
+var p={
 overlay: 50,
 overlayClass: 'jqmOverlay',
 closeClass: 'jqmClose',
 trigger: '.jqModal',
-ajax: false,
-target: false,
-modal: false,
-toTop: false,
-onShow: false,
-onHide: false,
-onLoad: false
+ajax: F,
+ajaxText: '',
+target: F,
+modal: F,
+toTop: F,
+onShow: F,
+onHide: F,
+onLoad: F
 };
-
-// For each element (aka "modal") $.jqm() has been called on;
-//  IF the _jqm expando exists, return (do nothing)
-//  ELSE increment serials and add _jqm expando to element ("serialization")
-//    *AND*...
-return this.each(function(){if(this._jqm)return;s++;this._jqm=s;
-
-// ... Add this element's serial to the jqModal Hash Object
-//  Hash is globally accessible via jQuery.jqm.hash. It consists of;
-//   c: {obj} config/options
-//   a: {bool} active state (true: active/visible, false: inactive/hidden)
-//   w: {JQ DOM Element} The modal element (window/dialog/notice/etc. container)
-//   s: {int} The serial number of this modal (same as "H[s].w[0]._jqm")
-//   t: {DOM Element} The triggering element
-// *AND* ...
-H[s]={c:$.extend(o,p),a:false,w:$(this).addClass('jqmID'+s),s:s};
-
-// ... Attach events to trigger showing of this modal
-o.trigger&&$(this).jqmAddTrigger(o.trigger);
+return this.each(function(){if(this._jqm)return H[this._jqm].c=$.extend({},H[this._jqm].c,o);s++;this._jqm=s;
+H[s]={c:$.extend(p,$.jqm.params,o),a:F,w:$(this).addClass('jqmID'+s),s:s};
+if(p.trigger)$(this).jqmAddTrigger(p.trigger);
 });};
 
-// Adds behavior to triggering elements via the hide-show (HS) function.
-//
-$.fn.jqmAddClose=function(e){return HS(this,e,'jqmHide');};
-$.fn.jqmAddTrigger=function(e){return HS(this,e,'jqmShow');};
-
-// Hide/Show a modal -- first check if it is already shown or hidden via the toggle state (H[{modal serial}].a)
-$.fn.jqmShow=function(t){return this.each(function(){!H[this._jqm].a&&$.jqm.open(this._jqm,t)});};
-$.fn.jqmHide=function(t){return this.each(function(){H[this._jqm].a&&$.jqm.close(this._jqm,t)});};
+$.fn.jqmAddClose=function(e){return hs(this,e,'jqmHide');};
+$.fn.jqmAddTrigger=function(e){return hs(this,e,'jqmShow');};
+$.fn.jqmShow=function(t){return this.each(function(){t=t||window.event;$.jqm.open(this._jqm,t);});};
+$.fn.jqmHide=function(t){return this.each(function(){t=t||window.event;$.jqm.close(this._jqm,t)});};
 
 $.jqm = {
 hash:{},
-
-// Function is executed by $.jqmShow to show a modal
-// s: {INT} serial of modal
-// t: {DOM Element} the triggering element
-
-// set local shortcuts
-//  h: {obj} this Modal's "hash"
-//  c: {obj} (h.c) config/options
-//  cc: {STR} closing class ('.'+h.c.closeClass)
-//  z: {INT} z-Index of Modal. If the Modal (h.w) has the z-index style set it will use this value before defaulting to the one passed in the config (h.c.zIndex)
-//  o: The overlay object
-// mark this modal as active (h.a === true)
-// set the triggering object (h.t) and the modal's z-Index.
-open:function(s,t){var h=H[s],c=h.c,cc='.'+c.closeClass,z=/^\d+$/.test(h.w.css('z-index'))&&h.w.css('z-index')||c.zIndex,o=$('<div></div>').css({height:'100%',width:'100%',position:'fixed',left:0,top:0,'z-index':z-1,opacity:c.overlay/100});h.t=t;h.a=true;h.w.css('z-index',z);
-
- // IF the modal argument was passed as true;
- //    Bind the Keep Focus Function if no other Modals are open (!A[0]),
- //    Add this modal to the opened modals stack (A) for nested modal support,
- //    and Mark overlay to show wait cursor when mouse hovers over it.
- if(c.modal) {!A[0]&&F('bind');A.push(s);o.css('cursor','wait');}
-
- // ELSE IF an overlay was requested (translucency set greater than 0);
- //    Attach a Close event to overlay to hide modal when overlay is clicked.
+open:function(s,t){var h=H[s],c=h.c,cc='.'+c.closeClass,z=(parseInt(h.w.css('z-index'))),z=(z>0)?z:3000,o=$('<div></div>').css({height:'100%',width:'100%',position:'fixed',left:0,top:0,'z-index':z-1,opacity:c.overlay/100});if(h.a)return F;h.t=t;h.a=true;h.w.css('z-index',z);
+ if(c.modal) {if(!A[0])L('bind');A.push(s);}
  else if(c.overlay > 0)h.w.jqmAddClose(o);
+ else o=F;
 
- // ELSE disable the overlay
- else o=false;
+ h.o=(o)?o.addClass(c.overlayClass).prependTo('body'):F;
+ if(ie6){$('html,body').css({height:'100%',width:'100%'});if(o){o=o.css({position:'absolute'})[0];for(var y in {Top:1,Left:1})o.style.setExpression(y.toLowerCase(),"(_=(document.documentElement.scroll"+y+" || document.body.scroll"+y+"))+'px'");}}
 
- // Add the Overlay to BODY if not disabled.
- h.o=(o)?o.addClass(c.overlayClass).prependTo('body'):false;
-
- // IF IE6;
- //  Set the Overlay to 100% height/width, and fix-position it via JS workaround
- if(ie6&&$('html,body').css({height:'100%',width:'100%'})&&o){o=o.css({position:'absolute'})[0];for(var y in {Top:1,Left:1})o.style.setExpression(y.toLowerCase(),"(_=(document.documentElement.scroll"+y+" || document.body.scroll"+y+"))+'px'");}
-
- // IF the modal's content is to be loaded via ajax;
- //  determine the target element {JQ} to recieve content (r),
- //  determine the URL {STR} to load content from (u)
  if(c.ajax) {var r=c.target||h.w,u=c.ajax,r=(typeof r == 'string')?$(r,h.w):$(r),u=(u.substr(0,1) == '@')?$(t).attr(u.substring(1)):u;
+  r.html(c.ajaxText).load(u,function(){if(c.onLoad)c.onLoad.call(this,h);if(cc)h.w.jqmAddClose($(cc,h.w));e(h);});}
+ else if(cc)h.w.jqmAddClose($(cc,h.w));
 
-  // Load the Content (and once loaded);
-   // Fire the onLoad callback (if exists),
-   // Attach closing events to elements inside the modal that match the closingClass,
-   // and Execute the jqModal default Open Callback
-  r.load(u,function(){c.onLoad&&c.onLoad.call(this,h);cc&&h.w.jqmAddClose($(cc,h.w));O(h);});}
-
- // ELSE the modal content is NOT to be loaded via ajax;
- //  Attach closing events to elements inside the modal that match the closingClass
- else cc&&h.w.jqmAddClose($(cc,h.w));
-
- // IF toTop was passed and an overlay exists;
- //  Remember the DOM posistion of the modal by inserting a tagged (matching serial) <SPAN> before the modal
- //  Move the Modal from its current position to a first child of the body tag (after the overlay)
- c.toTop&&h.o&&h.w.before('<span id="jqmP'+h.w[0]._jqm+'"></span>').insertAfter(h.o);
-
- // Execute user defined onShow callback, or else show (make visible) the modal.
- // Execute the jqModal default Open Callback.
- // Return false to prevent trigger click from being followed.
- (c.onShow)?c.onShow(h):h.w.show();O(h);return false;
-
+ if(c.toTop&&h.o)h.w.before('<span id="jqmP'+h.w[0]._jqm+'"></span>').insertAfter(h.o);        
+ (c.onShow)?c.onShow(h):h.w.show();e(h);return F;
 },
-
-// Function is executed by $.jqmHide to hide a modal
-  // mark this modal as inactive (h.a === false)
-close:function(s){var h=H[s];h.a=false;
- // If modal, remove from modal stack.
-   // If no modals in modal stack, unbind the Keep Focus Function
- if(h.c.modal){A.pop();!A[0]&&F('unbind');}
-
- // IF toTop was passed and an overlay exists;
- //  Move modal back to its previous ("remembered") position.
- h.c.toTop&&h.o&&$('#jqmP'+h.w[0]._jqm).after(h.w).remove();
-
- // Execute user defined onHide callback, or else hide (make invisible) the modal and remove the overlay.
- if(h.c.onHide)h.c.onHide(h);else{h.w.hide()&&h.o&&h.o.remove()}return false;
-}};
-
-// set jqModal scope shortcuts;
-//  s: {INT} serials placeholder
-//  H: {HASH} shortcut to jqModal Hash Object
-//  A: {ARRAY} Array of active/visible modals
-//  ie6: {bool} True if client browser is Internet Explorer 6
-//  i: {JQ, DOM Element} iframe placeholder used to prevent active-x bleedthrough in IE6
-//    NOTE: It is important to include the iframe styling (iframe.jqm) in your CSS!
-//     *AND* ...
-var s=0,H=$.jqm.hash,A=[],ie6=$.browser.msie&&($.browser.version == "6.0"),i=$('<iframe src="javascript:false;document.write(\'\');" class="jqm"></iframe>').css({opacity:0}),
-
-//  O: The jqModal default Open Callback;
-//    IF ie6; Add the iframe to the overlay (if overlay exists) OR to the modal (if an iframe doesn't already exist from a previous opening)
-//    Execute the Modal Focus Function
-O=function(h){if(ie6)h.o&&h.o.html('<p style="width:100%;height:100%"/>').prepend(i)||(!$('iframe.jqm',h.w)[0]&&h.w.prepend(i)); f(h);},
-
-//  f: The Modal Focus Function;
-//    Attempt to focus the first visible input within the modal
-f=function(h){try{$(':input:visible',h.w)[0].focus();}catch(e){}},
-
-//  F: The Keep Focus Function;
-//    Binds or Unbinds (t) the Focus Examination Function to keypresses and clicks
-F=function(t){$()[t]("keypress",x)[t]("keydown",x)[t]("mousedown",x);},
-
-//  x: The Focus Examination Function;
-//    Fetch the current modal's Hash as h (supports nested modals)
-//    Determine if the click/press falls within the modal. If not (r===true);
-//      call the Modal Focus Function and prevent click/press follow-through (return false [!true])
-//      ELSE if so (r===false); follow event (return true [!false])
-x=function(e){var h=H[A[A.length-1]],r=(!$(e.target).parents('.jqmID'+h.s)[0]);r&&f(h);return !r;},
-
-// hide-show function; assigns click events to trigger elements that
-//   hide, show, or hide AND show modals.
-
-// Expandos (jqmShow and/or jqmHide) are added to all trigger elements.
-// These Expandos hold an array of modal serials {INT} to show or hide.
-
-//  w: {DOM Element} The modal element (window/dialog/notice/etc. container)
-//  e: {DOM Elemet||jQ Selector String} The triggering element
-//  y: {String} Type (jqmHide||jqmShow)
-
-//  s: {array} the serial number of passed modals, calculated below;
-HS=function(w,e,y){var s=[];w.each(function(){s.push(this._jqm)});
-
-// for each triggering element attach the jqmHide or jqmShow expando (y)
-//  or else expand the expando with the current serial array
- $(e).each(function(){if(this[y])$.extend(this[y],s);
-
- // Assign a click event on the trigger element which examines the element's
- //  jqmHide/Show expandos and attempts to execute $.jqmHide/Show on matching modals
- else{this[y]=s;$(this).click(function(){for(var i in {jqmShow:1,jqmHide:1})for(var s in this[i])if(H[this[i][s]])H[this[i][s]].w[i](this);return false;});}});return w;};
+close:function(s){var h=H[s];if(!h.a)return F;h.a=F;
+ if(A[0]){A.pop();if(!A[0])L('unbind');}
+ if(h.c.toTop&&h.o)$('#jqmP'+h.w[0]._jqm).after(h.w).remove();
+ if(h.c.onHide)h.c.onHide(h);else{h.w.hide();if(h.o)h.o.remove();} return F;
+},
+params:{}};
+var s=0,H=$.jqm.hash,A=[],ie6=$.browser.msie&&($.browser.version == "6.0"),F=false,
+i=$('<iframe src="javascript:false;document.write(\'\');" class="jqm"></iframe>').css({opacity:0}),
+e=function(h){if(ie6)if(h.o)h.o.html('<p style="width:100%;height:100%"/>').prepend(i);else if(!$('iframe.jqm',h.w)[0])h.w.prepend(i); f(h);},
+f=function(h){try{$(':input:visible',h.w)[0].focus();}catch(_){}},
+L=function(t){$()[t]("keypress",m)[t]("keydown",m)[t]("mousedown",m);},
+m=function(e){var h=H[A[A.length-1]],r=(!$(e.target).parents('.jqmID'+h.s)[0]);if(r)f(h);return !r;},
+hs=function(w,t,c){return w.each(function(){var s=this._jqm;$(t).each(function() {
+ if(!this[c]){this[c]=[];$(this).click(function(){for(var i in {jqmShow:1,jqmHide:1})for(var s in this[i])if(H[this[i][s]])H[this[i][s]].w[i](this);return F;});}this[c].push(s);});});};
 })(jQuery);
\ No newline at end of file
diff --git a/wolnelektury/static/js/locale.js b/wolnelektury/static/js/locale.js
new file mode 100755 (executable)
index 0000000..e765548
--- /dev/null
@@ -0,0 +1,29 @@
+var LOCALE_TEXTS = {
+    "pl": {
+        "Loading": "Ładowanie"
+    },
+    "de": {
+        "Loading": "Herunterladen"
+    },
+    "fr": {
+        "Loading": "Chargement"
+    },
+    "en": {
+        "Loading": "Loading"
+    },
+    "ru": {
+        "Loading": "Загрузка"
+    },
+    "es": {
+        "Loading": "Cargando"
+    },
+    "lt":{
+        "Loading": "Krovimas"
+    },
+    "uk":{
+        "Loading": "Завантажується"
+    }
+}
+function gettext(text) {
+    return LOCALE_TEXTS[LANGUAGE_CODE][text];
+}
\ No newline at end of file
diff --git a/wolnelektury/static/js/pdcounter.js b/wolnelektury/static/js/pdcounter.js
new file mode 100755 (executable)
index 0000000..00c0742
--- /dev/null
@@ -0,0 +1,36 @@
+(function($) {
+    $(function() {
+
+
+        $('#countdown').each(function() {
+            var $this = $(this);
+
+            var serverTime = function() {
+                var time = null;
+                $.ajax({url: '/zegar/',
+                    async: false, dataType: 'text',
+                    success: function(text) {
+                        time = new Date(text);
+                    }, error: function(http, message, exc) {
+                        time = new Date();
+                }});
+                return time;
+            }
+
+            if (LANGUAGE_CODE != 'en') {
+                $.countdown.setDefaults($.countdown.regional[LANGUAGE_CODE]);
+            }
+            else {
+                $.countdown.setDefaults($.countdown.regional['']);
+            }
+
+            var d = new Date($this.attr('data-year'), 0, 1);
+            function re() {location.reload()};
+            $this.countdown({until: d, format: 'ydHMS', serverSync: serverTime,
+                onExpiry: re, alwaysExpire: true});
+
+        });
+
+
+    });
+})(jQuery)
\ No newline at end of file
diff --git a/wolnelektury/static/js/player.js b/wolnelektury/static/js/player.js
new file mode 100755 (executable)
index 0000000..fea8450
--- /dev/null
@@ -0,0 +1,33 @@
+(function($) {
+    $(function() {
+
+
+        $("#jplayer").jPlayer({
+            swfPath: "/static/jplayer/",
+            solution: "html,flash",
+            supplied: $(this).attr('data-supplied'),
+    
+            ready: function() {
+                var player = $(this);
+                var setMedia = function(elem) {
+                    var li = $(elem).parent();
+                    $('.jp-playlist-current').removeClass('jp-playlist-current');
+                    $(li).addClass('jp-playlist-current');
+                    var media = {}
+    
+                    $('.mp3', li).each(function() {media['mp3'] = $(this).attr('href')});
+                    $('.ogg', li).each(function() {media['oga'] = $(this).attr('href')});
+    
+                    return player.jPlayer("setMedia", media);
+                };
+                setMedia($('.play').first()).jPlayer("play");
+    
+                $('.play').click(function() {
+                    setMedia(this).jPlayer("play");
+                });
+            }
+        });
+
+
+    });
+})(jQuery)
\ No newline at end of file
diff --git a/wolnelektury/static/js/search.js b/wolnelektury/static/js/search.js
new file mode 100644 (file)
index 0000000..47b6665
--- /dev/null
@@ -0,0 +1,49 @@
+
+var __bind = function (self, fn) {
+    return function() { fn.apply(self, arguments); };
+};
+
+(function($){
+    $.widget("wl.search", {
+       options: {
+          minLength: 0,
+        },
+
+       _create: function() {
+           var opts = { 
+               minLength: this.options.minLength,
+               select: __bind(this, this.enter),
+               focus: function() { return false; },
+               source: this.element.data('source'),
+           };
+           this.element.autocomplete(opts).data("autocomplete")._renderItem = __bind(this, this.render_item);
+       },
+
+       enter: function(event, ui) {
+           if (ui.item.url != undefined) {
+               location.href = ui.item.url;
+           } else {
+               this.element.closest('form').submit();
+           }
+       },
+   
+       render_item: function (ul, item) {
+           return $("<li></li>").data('item.autocomplete', item)
+               .append('<a href="'+item.url+'">'+item.label+ ' ('+item.category+')</a>')
+               .appendTo(ul);
+       },
+
+       destroy: function() {
+
+       },
+       
+
+
+       });
+
+    $(function() {
+        $("#search").search().labelify({labelledClass: "blur"});
+    });
+
+
+})(jQuery);
diff --git a/wolnelektury/static/js/sponsors.js b/wolnelektury/static/js/sponsors.js
new file mode 100755 (executable)
index 0000000..7674379
--- /dev/null
@@ -0,0 +1,8 @@
+(function($) {
+    $(function() {
+
+        $('.sponsor-logos').cycle({timeout: 3000});
+
+    });
+})(jQuery)
+
index 9cc0565..a603966 100644 (file)
@@ -11,7 +11,7 @@
     <Image height="16" width="16" type="image/x-icon">http://www.wolnelektury.pl/static/img/favicon.ico</Image>
     <Image height="64" width="64" type="image/png">http://www.wolnelektury.pl/static/img/wl_icon_64.png</Image>
     <Url type="application/atom+xml;profile=opds-catalog"
-        template="http://www.wolnelektury.pl/opds/search/?q={searchTerms}" />
+        template="http://www.wolnelektury.pl/opds/search/?q={searchTerms}&author={atom:author}&translator={atom:contributor}&title={atom:title}" />
     <Url type="text/html" method="GET" template="http://www.wolnelektury.pl/katalog/szukaj?q={searchTerms}" />
     <Url type="application/x-suggestions+json" method="GET" template="http://www.wolnelektury.pl/katalog/jtags?mozhint=1&amp;q={searchTerms}" />
     <moz:SearchForm>http://www.wolnelektury.pl/katalog/</moz:SearchForm>
index 1277bd2..fe19d30 100644 (file)
@@ -66,3 +66,8 @@
     background-color: #EEE;
     cursor: default;
 }
+
+.sponsors-sponsor img {
+    max-width: 120px;
+    max-height: 120px;
+}
index 50a56ea..8083d28 100644 (file)
@@ -1,27 +1,19 @@
+{% extends "base.html" %}
 {% load i18n %}
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
-        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" lang="pl" xml:lang="pl">
-<head>
-<title>404 - {% trans "Page does not exist" %} - WolneLektury.pl</title>
-<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
-<link rel="stylesheet" type="text/css" href="http://yui.yahooapis.com/2.5.2/build/reset/reset-min.css">
-<link rel="stylesheet" href="{{ STATIC_URL }}css/error.css" type="text/css" />
-</head>
-
-<body>
-
-<a href="/"><img src="{{ STATIC_URL }}img/logo.png" /></a>
-<p class="haj" style="font-weight: bold">{% trans "Page does not exist" %}</p>
+
+
+{% block titleextra %}404 - {% trans "Page does not exist" %}{% endblock %}
+
+
+{% block body %}
+
+<h1>{% trans "Page not found" %}</h1>
+
+
 <p>
 {% trans "We are sorry, but this page does not exist. Please check if you entered correct address or go to "%} <a href="/">{% trans "main page" %}</a>.
 </p>
 
-<script src="http://www.google-analytics.com/urchin.js" type="text/javascript">
-</script>
-<script type="text/javascript">
-    _uacct = "UA-2576694-1";
-    urchinTracker();
-</script>
-</body>
-</html>
\ No newline at end of file
+
+
+{% endblock body %}
diff --git a/wolnelektury/templates/auth/login.html b/wolnelektury/templates/auth/login.html
deleted file mode 100644 (file)
index a168ff7..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-{% extends "base.html" %}
-{% load i18n %}
-
-{% block title %}{% trans "Sign in" %} / {% trans "Register on"%} WolneLektury.pl{% endblock %}
-
-{% block body %}
-    <h1>{% trans "Sign in" %} / {% trans "Register"%}</h1>
-    <form action="." method="get" accept-charset="utf-8" id="search-form">
-        <p><li>{{ search_form.q }} <input type="submit" value="{% trans "Search" %}" /></li> <strong>{% trans "or" %}</strong> <a href="{% url main_page %}">{% trans "return to main page" %}</a></p>
-    </form>
-    <form method="post" action="." id="login-form" class="cuteform">
-        <h2>{% trans "Sign in" %}</h2>
-        <ol>
-            {{ form.as_ul }}
-            <li><input type="submit" value="{% trans "Sign in" %}" /></li>
-        </ol>
-        <p><input type="hidden" name="next" value="{{ next }}" /></p>
-    </form>
-
-    <form action="." method="post" accept-charset="utf-8" id="registration-form">
-        <h2>{% trans "Register" %}</h2>
-
-        <p><input type="submit" value="{% trans "Register" %}"/></p>
-    </form>
-{% endblock %}
index 061197f..23cb46b 100644 (file)
 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
 <html xmlns="http://www.w3.org/1999/xhtml">
-       {% load i18n chunks compressed catalogue_tags sponsor_tags %}
+       {% load i18n compressed catalogue_tags sponsor_tags %}
+    {% load reporting_stats %}
     <head>
         <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
         <meta http-equiv="Content-Style-Type" content="text/css" />
         <meta name="description" 
             content="{% block metadescription %}Darmowe opracowane, pełne teksty lektur, e-booki, audiobooki i pliki DAISY na wolnej licencji.{% endblock %}" />
-        <title>{% block title %}WolneLektury.pl{% endblock %}</title>
+        <title>{% block title %}{% trans "Wolne Lektury" %} :: 
+            {% block titleextra %}{% endblock %}{% endblock %}</title>
         <link rel="icon" href="{{ STATIC_URL }}img/favicon.png" type="image/png" />
         <link rel="search" type="application/opensearchdescription+xml" title="Wolne Lektury" href="{{ STATIC_URL }}opensearch.xml" />
         {% compressed_css "all" %}
-        <script type="text/javascript">var LANGUAGE_CODE = "{{ LANGUAGE_CODE }}";</script>
-        {% compressed_js "jquery" %}
-        {% compressed_js "all" %}
+
         {% block extrahead %}
         {% endblock %}
     </head>
     <body id="{% block bodyid %}base{% endblock %}">
-        <!--[if lt IE 7]><link href={{ STATIC_URL }}infobar/infobar.css rel=stylesheet>
-        <div id=infobar><a href=http://browsehappy.pl/infobar>
-        {% trans "Internet Explorer cannot display this site properly. Click here to read more..." %}
-        </a></div><div id=viewplot><script src={{ STATIC_URL }}infobar/infobar.js></script><![endif]-->
+
         {% block bodycontent %}
-        <div id="top-message">
-            {% chunk "top-message" %}
-        </div>
+
         <div id="header">
+
+        <div id="header-content">
             <div id="logo">
-                <a class="logo" href="/"><img src="{% block logo_url %}{{ STATIC_URL }}img/logo-bez.png{% endblock %}" alt="WolneLektury.pl" />
-                <br/>szkolna biblioteka internetowa</a>
-                {% block tagline %}{% endblock %}
+                <a class="logo" href="/">
+                Wolne Lektury</a>
+            </div>
+
+            <div id="tagline">
+                <span>
+                {% count_books book_count %}
+                {% url book_list as b %}
+                {% url book_list as r %}
+                {% blocktrans count book_count as c %}
+                <a href='{{b}}'>{{c}}</a> free reading you have <a href='{{r}}'>right to</a>
+                {% plural %}
+                <a href='{{b}}'>{{c}}</a> free readings you have <a href='{{r}}'>right to</a>
+                {% endblocktrans %}
+                </span>
             </div>
-            <div id="user-info" style="display:none">
+
+            <p id="user-info" class="mono">
                 {% if user.is_authenticated %}
-                    <p>
-                        {% trans "Welcome" %}, <strong>{{ user.username }}</strong>
-                        | <a href="{% url user_shelves %}" id="user-shelves-link">{% trans "Your shelves" %}</a>
-                        {% if user.is_staff %}
-                        | <a href="/admin/">{% trans "Administration" %}</a>
-                        {% endif %}
-                                               | <a href="{% url suggest %}" id="suggest-link">{% trans "Report a bug" %}</a>
-                        | <a href="{% url logout %}?next={{ request.get_full_path }}">{% trans "Logout" %}</a>
-                    </p>
+                    {% trans "Welcome" %}, <strong>{{ user.username }}</strong>
+                    | <a href="{% url user_shelves %}" id="user-shelves-link">{% trans "Your shelves" %}</a>
+                    {% if user.is_staff %}
+                    | <a href="/admin/">{% trans "Administration" %}</a>
+                    {% endif %}
+                    | <a href="{% url logout %}?next={{ request.get_full_path }}">{% trans "Logout" %}</a>
                 {% else %}
-                    <p><a href="{% url suggest %}" id="suggest-link">{% trans "Report a bug" %}</a>
-                        | <a href="{% url login %}" class="login-register-link">{% trans "Sign in" %} / {% trans "Register" %}</a></p>
+                    <a href="{% url login %}?next={{ request.path }}"
+                        id="login" class="ajaxable">
+                            {% trans "Sign in" %}</a>
+                    /
+                    <a href="{% url register %}?next={{ request.path }}"
+                        id="register" class="ajaxable">
+                            {% trans "Register" %}</a>
                 {% endif %}
+            </p>
+
+
+            <div class="clearboth"></div>
+
+        </div>
+        </div>
+
+        <div id="half-header">
+        <div id="half-header-content">
+
+
+
+            <form id="search-area" action="/fullsearch/">
+                
+                <span id="search-field" class="grid-line">
+                 {{search_form.q}}
+<!--                    <input title="np. Leśmian" name="q" autocomplete="off" data-source="/fullsearch/hint/">-->
+                </span><span id="search-button">
+                    <button type='submit'><span class="mono">{% trans "Search" %}</span></button>
+                </span>
+                
+                <div class="clearboth"></div>
+            </form>
+
+
+
+        </div>
+        </div>
+
+
+
+        <div id="main-content">
+
+            <div id="nav-line">
+            <ul id="catalogue">
+                <li><a href="{% url catalogue %}#autorzy"><span class='mono'>{% trans "Authors" %}</span></a></li>
+                <li><a href="{% url catalogue %}#gatunki"><span class='mono'>{% trans "Genres" %}</span></a></li>
+                <li><a href="{% url catalogue %}#rodzaje"><span class='mono'>{% trans "Kinds" %}</span></a></li>
+                <li><a href="{% url catalogue %}#epoki"><span class='mono'>{% trans "Epochs" %}</span></a></li>
+                <li><a href="{% url catalogue %}#motywy"><span class='mono'>{% trans "Themes" %}</span></a></li>
+                <li><a href="{% url book_list %}"><span class='mono'>{% trans "All books" %}</span></a></li>
+                <li><a href="{% url audiobook_list %}"><span class='mono'>{% trans "Audiobooks" %}</span></a></li>
+                <li><a href="{% url daisy_list %}"><span class='mono'>{% trans "DAISY" %}</span></a></li>
+            </ul>
+
+            <form action="{% url django.views.i18n.set_language %}" method="post">
+            <div id="lang-menu">
+                <span id='lang-button' class='mono'>
+                    {% trans "Language versions" %}</span>
+                <div id="lang-menu-items">
+                {% for lang in LANGUAGES %}
+                    <button type="submit" name="language"
+                        class="{% ifequal lang.0 LANGUAGE_CODE %}active{% endifequal %} mono"
+                        value="{{ lang.0 }}">{{ lang.1 }}</button>
+                {% endfor %}
+                </div>
             </div>
-            <div class="social-links" style="float:right">
-                <a href="http://pl-pl.facebook.com/pages/Wolne-Lektury/203084073268"><img src="{{ STATIC_URL }}img/social/facebook.png" alt="WolneLektury @ Facebook" /></a>
-                <a href="http://nk.pl/profile/30441509"><img src="{{ STATIC_URL }}img/social/naszaklasa.png" alt="WolneLektury @ NK" /></a>
-            </div>
-                               <form action="{% url django.views.i18n.set_language %}" method="post">
-            <div class="lang-menu" style="float:right;">
-                                       {% spaceless %}
-                                       {% for lang in LANGUAGES %}                                          
-                                           <button type="submit" name="language" title="{{ lang.1 }}"
-                                                       class="{% ifequal lang.0 LANGUAGE_CODE %}active{% endifequal %} {% if forloop.last %}last{% endif %}" 
-                                                       value="{{ lang.0 }}">{{ lang.0|upper }}</button>
-                                       {% endfor %}    
-                                       {% endspaceless %}                              
-                       </div>
-                </form>
-            {# publication plan consultations - form link #}
-            <div style="clear:right;float:right" class="big-top-link">
-                <a href="{% url suggest_publishing %}" data-ajax="{% url suggest_publishing %}?ajax=1" id="suggest-publishing-link">
-                    {% trans "Didn't find a book? Make a suggestion." %}
-                </a>
+            </form>
             </div>
+
             <div class="clearboth"></div>
-        </div>
-        <div id="maincontent">
+
+
+
             {% block body %}
             {% endblock %}
-        </div>
+
+
+
+
         <div class="clearboth"></div>
+
+        </div>{# end main-content #}
+
+
         <div id="footer">
+        <div id="footer-content">
             <p>
                {% blocktrans %}
                                Wolne Lektury is a project lead by <a href="http://nowoczesnapolska.org.pl/">Modern Poland Foundation</a>.
 
                        {% sponsor_page "footer" %}
         </div>
-        <div id="login-register-window">
-            <div class="header"><a href="#" class="jqmClose">{% trans "Close" %}</a></div>
-            <div class="target">
-                <form method="post" action="{% url login %}" id="login-form" class="cuteform">
-                    <h2>{% trans "Sign in" %} / <a href="#" id="show-registration-form" style="font-size: 0.85em; font-weight: normal">{% trans "Register" %}</a></h2>
-                    <p><span id="id_login-__all__"></span></p>
-                    <ol>
-                        {% authentication_form %}
-                        <li><input type="submit" value="{% trans "Sign in" %}" /></li>
-                    </ol>
-                </form>
-                <form method="post" action="{% url register %}" id="registration-form" class="cuteform" style="display: none;">
-                    <h2><a href="#" id="show-login-form" style="font-size: 0.85em; font-weight: normal">{% trans "Sign in" %}</a> / {% trans "Register" %}</h2>
-                    <p><span id="id_registration-__all__"></span></p>
-                    <ol>
-                        {% user_creation_form %}
-                        <li><input type="submit" value="{% trans "Register" %}" /></li>
-                    </ol>
-                </form>
-            </div>
-        </div>
-        <div id="user-shelves-window">
-            <div class="header"><a href="#" class="jqmClose">{% trans "Close" %}</a></div>
-            <div class="target">
-                <p><img src="{{ STATIC_URL }}img/indicator.gif" alt="*"/> {% trans "Loading" %}</p>
-            </div>
-        </div>
-        <div id="suggest-window">
-            <div class="header"><a href="#" class="jqmClose">{% trans "Close" %}</a></div>
-            <div class="target">
-                <p><img src="{{ STATIC_URL }}img/indicator.gif" alt="*"/> {% trans "Loading" %}</p>
-            </div>
         </div>
-        <div id="suggest-publishing-window">
-            <div class="header"><a href="#" class="jqmClose">{% trans "Close" %}</a></div>
+
+
+
+        {# template #}
+        <div id="ajaxable-window" class='dialog-window'>
+            <div class="header mono"><a href="#" class="jqmClose">{% trans "Close" %}</a></div>
             <div class="target">
                 <p><img src="{{ STATIC_URL }}img/indicator.gif" alt="*"/> {% trans "Loading" %}</p>
             </div>
         </div>
+
+
         {% endblock bodycontent %}
-        {{ piwik_tag|safe }}
+
+
+        <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
+        <script type="text/javascript">
+            var LANGUAGE_CODE = "{{ LANGUAGE_CODE }}";
+            var STATIC_URL = "{{ STATIC_URL }}";
+        </script>
+        {% compressed_js "base" %}
+
+        <!--{{ piwik_tag|safe }}
         <script type="text/javascript">
         var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
         document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
         <script type="text/javascript">
         var pageTracker = _gat._getTracker("UA-2576694-1");
         pageTracker._trackPageview();
-        </script>
+        </script>-->
     </body>
 </html>
index a9b8ba0..d794cd9 100644 (file)
@@ -3,7 +3,7 @@
 
 {% block bodyid %}book-a-list{% endblock %}
 
-{% block title %}{% trans "Listing of all audiobooks on WolneLektury.pl" %}{% endblock %}
+{% block titleextra %}{% trans "Listing of all audiobooks" %}{% endblock %}
 
 {% block metadescription %}Darmowe audiobooki na wolnej licencji. Lektury czytane przez znanych aktorów.{% endblock %}
 
index ff7b519..5645f68 100644 (file)
 {% load i18n %}
 {% load catalogue_tags pagination_tags %}
 
-{% block title %}{{ book.title }} {% trans "on WolneLektury.pl" %}{% endblock %}
+{% block titleextra %}{{ book.title }}{% endblock %}
 
 {% block metadescription %}{% book_title book %}. {{ block.super }}{% endblock %}
 
 {% block bodyid %}book-detail{% endblock %}
 
 {% block body %}
-    <h1>{% book_title book %}</h1>
-    <form action="{% url search %}" method="get" accept-charset="utf-8" id="search-form">
-        <p>{{ form.q }} <input type="submit" value="{% trans "Search" %}" /> <strong>{% trans "or" %}</strong> <a href="{% url main_page %}">{% trans "return to main page" %}</a></p>
-    </form>
 
-    <div id="books-list">
-        <div id='breadcrumbs'>
-            {% if categories.author %}
-                {% for tag in categories.author %}
-                    <a href="{{ tag.get_absolute_url }}">{{ tag }}</a>{% if not forloop.last %}, {% endif %}
-                {% endfor %}
-                &#187; 
-            {% endif %}
-            {% for parent in parents %}
-                <a href="{{ parent.get_absolute_url }}">{{ parent }}</a> &#187; 
-            {% endfor %}
-        </div>
+{% book_wide book %}
 
-        {% if extra_info.license %}
-        <p>{% trans "Work is licensed under " %} <a href="{{ extra_info.license }}">{{ extra_info.license_description }}</a>.</p>
-        {% endif %}
-        <p>{% trans "Based on" %}: {{ extra_info.source_name }}</p>
-        {% if book.has_description %}
-            <div id="description">
-                <div id='description-long'>{{ book.description|safe }}</div>
-                <div id='description-short'>{{ book.description|safe|truncatewords_html:30 }}</div>
-            </div>
-            <div id="toggle-description"><p></p></div>
-        {% endif %}
-        <div id="formats">
-            <p class="change-sets">{% trans "Put a book" %} <span><a href="{% url catalogue.views.book_sets book.slug %}" class="jqm-trigger">{% trans "on the shelf!" %}</a></span></p>
-            <div class="clearboth"></div>
-            <div class="wrap">
-                {% if book.has_html_file %}
-                    <a class="online" href="{% url book_text book.slug %}">{% trans "Read online" %}</a>
-                {% endif %}
-                <div class="download">
-                    {% if book.pdf_file %}
-                        <a href="{{ book.pdf_file.url }}"><img src="{{ STATIC_URL }}img/pdf.png" title="{% trans "Download PDF" %} &ndash; {% trans "for reading" %} {% trans "and printing using" %} Adobe Reader" %}" alt="{% trans "Download PDF" %}" /></a>
-                    {% endif %}
-                    {% if book.root_ancestor.epub_file %}
-                        <a href="{{ book.root_ancestor.epub_file.url }}"><img src="{{ STATIC_URL }}img/epub.png" title="{% trans "Download EPUB" %} &ndash; {% trans "for reading" %} {% trans "on mobile devices" %}" alt="{% trans "Download EPUB" %}" /></a>
-                    {% endif %}
-                    {% if book.mobi_file %}
-                        <a href="{{ book.mobi_file.url }}"><img src="{{ STATIC_URL }}img/mobi.png" title="{% trans "Download MOBI" %} &ndash; {% trans "for reading" %} {% trans "on mobile devices" %}" alt="{% trans "Download MOBI" %}" /></a>
-                    {% endif %}
-                    {% if book.txt_file %}
-                        <a href="{{ book.txt_file.url }}"><img src="{{ STATIC_URL }}img/txt.png" title="{% trans "Download TXT" %} &ndash; {% trans "for reading" %} {% trans "on small displays, for example mobile phones" %}" alt="{% trans "Download TXT" %}" /></a>
-                    {% endif %}
-                    {% for media in book.get_odt %}
-                        <a href="{{ media.file.url }}"><img src="{{ STATIC_URL }}img/odt.png" title="{% trans "Download ODT" %} &ndash; {% trans "for reading" %} {% trans "and editing using" %} OpenOffice.org: {{ media.name }}" alt="{% trans "Download ODT" %}" /></a>
-                    {% endfor %}
-                </div>
-                {% if book.has_mp3_file or book.has_ogg_file or book.has_daisy_file %}
-                    <p class="header">
-                        <span class="desc">{% trans "Audiobooks" %}:</span>
-                        <span class="audiotabs">
-                            {% if book.has_mp3_file %}<span class="active" data-format="mp3">MP3</span>{% endif %}
-                            {% if book.has_ogg_file %}<span data-format="ogg">Ogg Vorbis</span>{% endif %}
-                            {% if book.has_daisy_file %}<span data-format="daisy">DAISY</span>{% endif %}
-                        </span>
-                    </p>
-                    <div class="audiobooks">
-                        <img src="{{ STATIC_URL }}img/speaker.png" id="speaker" alt="Speaker icon"/>
-                        {% if book.has_mp3_file %}
-                            <ul class="audiobook-list" id="mp3-files">
-                            {% for media in book.get_mp3 %}
-                                <li class="mp3Player">
-                                  <a href="{{ media.file.url }}">{{ media.name }}</a><br/>
-                                  {% trans "Artist" %}: {{ media.get_extra_info_value.artist_name }}<br/>
-                                  {% trans "Director"%}: {{ media.get_extra_info_value.director_name }}<br/>
-                                  <object type="application/x-shockwave-flash" style="margin-top: 0.5em" data="{{ STATIC_URL }}player.swf" width="226" height="20">
-                                        <param name="movie" value="{{ STATIC_URL }}player.swf" />
-                                        <param name="bgcolor" value="#ffffff" />
-                                        <param name="FlashVars" value="mp3={{ media.file.url }}&amp;width=226&amp;showvolume=1&amp;bgcolor1=eeeeee&amp;bgcolor2=eeeeee&amp;buttoncolor=666666" />
-                                    </object>
-                                    
-                                </li>
-                            {% endfor %}
-                            </ul>     
-                        {% endif %}
-
-                        {% if book.has_ogg_file %}
-                            <ul class="audiobook-list" id="ogg-files">
-                            {% for media in book.get_ogg %}
-                                <li><a href="{{ media.file.url }}">{{ media.name }}</a></li>
-                            {% endfor %}
-                            </ul>
-                        {% endif %}
-                        {% if book.has_daisy_file %}
-                            <ul class="audiobook-list" id="daisy-files">
-                            {% for media in book.get_daisy %}
-                                <li><a href="{{ media.file.url }}">{{ media.name }}</a></li>
-                            {% endfor %}
-                            </ul>
-                        {% endif %}
-                    </div> <!-- /audiobooks -->
-                    {% if projects|length > 1 %}
-                        <p>{% trans "Audiobooks were prepared as a part of the projects:" %}</p>
-                        <ul>
-                        {% for cs, fb in projects %}
-                            <li>
-                            {% if fb %}
-                                {% blocktrans %}{{ cs }}, funded by {{ fb }}{% endblocktrans %}
-                            {% else %}
-                                {{ cs }}
-                            {% endif %}
-                            </li>
-                        {% endfor %}
-                        </ul>
-                    {% else %}
-                        <p>
-                        {% with projects.0.0 as cs %}
-                        {% with projects.0.1 as fb %}
-                            {% if fb %}
-                                {% blocktrans %}Audiobooks were prepared as a part of the {{ cs }} project funded by {{ fb }}.{% endblocktrans %}
-                            {% else %}
-                                {% blocktrans %}Audiobooks were prepared as a part of the {{ cs }} project.{% endblocktrans %}
-                            {% endif %}
-                        {% endwith %}
-                        {% endwith %}
-                        </p>
-                    {% endif %}
-                {% endif %}
-            </div>
-        </div>
-
-        {% if book_children %}
-        {% autopaginate book_children 10 %}
-        <div id="book-children">
-            <ol>
-            {% for book in book_children %}
-                <li>{{ book.short_html }}</li>
-            {% endfor %}
-            </ol>
-        </div>
-        {% paginate %}
-        {% endif %}
-
-    </div>
-
-    <div id="tags-list">
-        <div id="book-info">
-            <h2>{% trans "Details" %}</h2>
-            <ul>
-                <li>
-                    {% trans "Author" %}:
-                    {% for tag in categories.author %}
-                    <a href="{{ tag.get_absolute_url }}">{{ tag }}</a>{% if not forloop.last %}, {% endif %}
-                    {% endfor %}
-                </li>
-                <li>
-                    {% trans "Epoch" %}:
-                    {% for tag in categories.epoch %}
-                    <a href="{{ tag.get_absolute_url }}">{{ tag }}</a>{% if not forloop.last %}, {% endif %}
-                    {% endfor %}
-                </li>
-                <li>
-                    {% trans "Kind" %}:
-                    {% for tag in categories.kind %}
-                    <a href="{{ tag.get_absolute_url }}">{{ tag }}</a>{% if not forloop.last %}, {% endif %}
-                    {% endfor %}
-                </li>
-                <li>
-                    {% trans "Genre" %}:
-                    {% for tag in categories.genre %}
-                    <a href="{{ tag.get_absolute_url }}">{{ tag }}</a>{% if not forloop.last %}, {% endif %}
-                    {% endfor %}
-                </li>              
-            </ul>
-            <h2>{% trans "Other resources" %}</h2>
-            <ul>
-                {% if extra_info.source_url %}
-                <li><a href="{{ extra_info.source_url }}">{% trans "Source of the book" %}</a></li>
-                {% endif %}
-                {% if extra_info.about and not hide_about %}
-                <li><a href="{{ extra_info.about }}">{% trans "Book on the Editor's Platform" %}</a></li>
-                {% endif %}
-                {% if book.gazeta_link %}
-                <li><a href="{{ book.gazeta_link }}">{% trans "Book description on Lektury.Gazeta.pl" %}</a></li>
-                {% endif %}
-                {% if book.wiki_link %}
-                <li><a href="{{ book.wiki_link }}">{% trans "Book description on Wikipedia" %}</a></li>
-                {% endif %}
-            </ul>
-            <p><a href="{{ book.xml_file.url }}">{% trans "View XML source" %}</a></p>
-            <p><a href="{% url poem_from_book book.slug %}">Miksuj ten utwór</a></p>
-        </div>
-        <div id="themes-list">
-            <h2>{% trans "Work's themes " %}</h2>
-            <ul>
-            {% for theme in book_themes %}
-                <li><a href="{% url book_fragments book.slug,theme.slug %}">{{ theme }} ({{ theme.count }})</a></li>
-            {% endfor %}
-            </ul>
-        </div>
-        <div class="clearboth"></div>
-    </div>
-    <div id="set-window">
-        <div class="header"><a href="#" class="jqmClose">{% trans "Close" %}</a></div>
-        <div class="target">
-            <p><img src="{{ STATIC_URL }}img/indicator.gif" alt="*"/> {% trans "Loading" %}</p>
-        </div>
-    </div>
 {% endblock %}
index e71a190..3ec3ad2 100644 (file)
@@ -2,15 +2,12 @@
 {% load i18n %}
 {% load catalogue_tags pagination_tags %}
 
-{% block title %}{% trans "Theme" %} {{ theme }} {% trans "in work " %} {{ book }} {% trans "on" %} WolneLektury.pl{% endblock %}
+{% block titleextra %}{% trans "Theme" %} {{ theme }} {% trans "in work " %} {{ book }}{% endblock %}
 
 {% block bodyid %}tagged-object-list{% endblock %}
 
 {% block body %}
     <h1>{% trans "Theme" %} {{ theme }} {% trans "in work " %} {{ book }} </h1>
-    <form action="{% url search %}" method="get" accept-charset="utf-8" id="search-form">
-        <p>{{ form.q }} <input type="submit" value="{% trans "Search" %}" /> <strong>{% trans "or" %}</strong> <a href="{{ book.get_absolute_url }}">{% trans "return to book's page" %}</a></p>
-    </form>
 
     {% autopaginate fragments 10 %}
     <div id="books-list">
         </div>
         <div class="clearboth"></div>
     </div>
-    <div id="set-window">
-        <div class="header"><a href="#" class="jqmClose">{% trans "Close" %}</a></div>
-        <div class="target">
-            <p><img src="{{ STATIC_URL }}img/indicator.gif" alt="*"/> {% trans "Loading" %}</p>
-        </div>
-    </div>
 {% endblock %}
\ No newline at end of file
index dd01066..318f1b4 100644 (file)
@@ -4,22 +4,20 @@
 
 {% block bodyid %}book-a-list{% endblock %}
 
-{% block title %}{% trans "Listing of all works on WolneLektury.pl" %}{% endblock %}
+{% block titleextra %}{% trans "Listing of all works" %}{% endblock %}
 
 {% block body %}
     <h1>{% block book_list_header %}{% trans "Listing of all works" %}{% endblock %}</h1>
-    <form action="{% url search %}" method="get" accept-charset="utf-8" id="search-form">
-        <p>{{ form.q }} {{ form.tags }} <input type="submit" value="{% trans "Search" %}" />
-            <strong>{% trans "or" %}</strong> {% trans "see" %}: 
-            <span class='collections'>
-                <a href="{% url catalogue.views.book_list %}">{% trans "all books" %}</a>
-                <a href="{% url catalogue.views.audiobook_list %}">{% trans "audiobooks" %}</a>
-                <a href="{% url catalogue.views.daisy_list %}">{% trans "DAISY" %}</a>
-            </span></p>
-    </form>
-    <div class="column-left">{% block book_list_info %}{% endblock %}</div><div style='clear:both;'></div>
+
+    <div class="left-column"><div class="normal-text">
+        {% block book_list_info %}{% endblock %}
+    </div></div>
+
+    <div style='clear:both;'></div>
+
     <a name="top">
-    <div id="book-list-nav">
+
+    <div id="book-list-nav" class="normal-text">
         {% trans "Table of Content" %}
         {% for index, authors in books_nav.items %}
                 <ul>
@@ -32,7 +30,8 @@
                 </ul>
         {% endfor %}    
     </div>
-    <div id="book-list">
+    <div id="book-list" class="normal-text">
+      {% block book_list %}
         {% book_tree orphans books_by_parent %}
         {% for author, group in books_by_author.items %}
             {% if group %}
@@ -43,6 +42,7 @@
                 </div>
             {% endif %}
         {% endfor %}
+      {% endblock %}
     </div>
     <div id="book-list-up">
         <p><a href="#top">{% trans "↑ top ↑" %}</a></p>
diff --git a/wolnelektury/templates/catalogue/book_mini_box.html b/wolnelektury/templates/catalogue/book_mini_box.html
new file mode 100755 (executable)
index 0000000..58abaa6
--- /dev/null
@@ -0,0 +1,22 @@
+{% load thumbnail %}
+<div class="book-mini-box">
+    <a href="{{ book.get_absolute_url }}">
+        {% if book.cover %}
+            <img src="
+                {% thumbnail book.cover "216x288" as thumb %}
+                    {{ thumb.url }}
+                {% empty %}
+                    {{ book.cover.url }}
+                {% endthumbnail %}
+            " alt="Cover" />
+        {% endif %}
+        {% for author in authors %}
+            <div class="desc">
+                <span class="mono author">{{ author }}</span>
+                <span class="title">{{ book.title }}</span>
+            </div>
+        {% endfor %}
+    </a>
+</div>
+
+
index 8b36718..7f26237 100644 (file)
@@ -1,18 +1,83 @@
 {% load i18n %}
-<div class="book">
-    <div class="change-sets">
-        <a href="{% url catalogue.views.book_sets book.slug %}" class="jqm-trigger">{% trans "Put on the shelf!" %}</a>
-    </div>
-    {% if book.children.all|length %}
-        <div class="book-parent-thumbnail"></div>
-    {% else %}
-        <div class="book-thumbnail"></div>
-    {% endif %}
-    <div class="book-description">
-        <h2><a href="{{ book.get_absolute_url }}">{{ book.title }}</a></h2>
-        {% if formats %}
-            <p style="margin: 0">{% trans "Jump to" %}: {{ formats|join:", " }}</p>
+{% load thumbnail %}
+<div class="{% block box-class %}book-box{% endblock %}">
+<div class="book-box-inner">
+    <a href="{{ book.get_absolute_url }}">
+        {% if book.cover %}
+            <img src="
+                {% thumbnail book.cover "216x288" as thumb %}
+                    {{ thumb.url }}
+                {% empty %}
+                    {{ book.cover.url }}
+                {% endthumbnail %}
+            " alt="Cover" />
         {% endif %}
-        <p style="margin: 0">{% trans "Categories" %}: {{ tags|join:", " }}</p>
+    </a>
+    {% block right-column %}
+    {% endblock %}
+    <div class="book-box-body">
+        <div class="book-box-head">
+            <div class="mono author">
+            {% for author in tags.author %}
+                {{ author }}
+            {% endfor %}
+            </div>
+            <div class="title">{{ book.title }}</div>
+        </div>
+        <div class="tags">
+            {% spaceless %}
+
+            <span class="mono">{% trans "Epoch" %}:</span>&nbsp;<span class="book-box-tag">
+                {% for tag in tags.epoch %}
+                    <a href="{{ tag.get_absolute_url }}">{{ tag.name }} </a>
+                {% endfor %}
+            </span>
+
+            <span class="mono">{% trans "Kind" %}:</span>&nbsp;<span class="book-box-tag">
+                {% for tag in tags.kind %}
+                    <a href="{{ tag.get_absolute_url }}">{{ tag.name }} </a>
+                {% endfor %}
+            </span>
+
+            <span class="mono">{% trans "Genre" %}:</span>&nbsp;<span class="book-box-tag">
+                {% for tag in tags.genre %}
+                    <a href="{{ tag.get_absolute_url }}">{{ tag.name }} </a>
+                {% endfor %}
+            </span>
+
+            {% endspaceless %}
+        </div>
     </div>
-</div>
\ No newline at end of file
+    <ul class="book-box-tools">
+        <li class="book-box-read">
+        {% if book.html_file %}
+            <a href="{% url book_text book.slug %}" class="mono downarrow">{% trans "Read online" %}</a>
+        {% endif %}
+        </li>
+        <li class="book-box-download">
+            <a class="mono downarrow">{% trans "Download" %}</a>
+            <div class="book-box-formats mono">
+             {% if formats.pdf %}
+             <span><a href="{{formats.pdf.url}}">PDF</a> do wydruku</span>
+             {% endif %}
+             {% if formats.epub %}
+             <span><a href="{{formats.epub.url}}">EPUB</a> na czytnik</span>
+             {% endif %}
+             {% if formats.mobi %}
+             <span><a href="{{formats.mobi.url}}">MOBI</a> na Kindle</span>
+             {% endif %}
+             {% if formats.txt %}
+             <span><a href="{{formats.txt.url}}">TXT</a> do zadań specjalnych</span>
+             {% endif %}
+            </div>
+        </li>
+        <li class="book-box-audiobook">
+        {% if book.has_mp3_file %}
+            <a href="{% url book_player book.slug %}" class="open-player mono downarrow">{% trans "Listen" %}</a>
+        {% endif %}
+        </li>
+    </ul>
+    {% block box-append %}
+    {% endblock %}
+</div>
+</div>
index 935cdf3..c133165 100644 (file)
@@ -8,7 +8,7 @@
         <title>{% block title %}WolneLektury.pl{% endblock %}</title>
         <link rel="icon" href="{{ STATIC_URL }}img/favicon.png" type="image/x-icon" />
         {% compressed_css "book" %}
-        {% compressed_js "jquery" %}
+        <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
         {% compressed_js "book" %}
         <!--[if IE]>
             {% compressed_js "book_ie" %}
diff --git a/wolnelektury/templates/catalogue/book_wide.html b/wolnelektury/templates/catalogue/book_wide.html
new file mode 100644 (file)
index 0000000..0506c8e
--- /dev/null
@@ -0,0 +1,46 @@
+{% extends "catalogue/book_short.html" %}
+{% load i18n %}
+
+{% block box-class %}book-wide-box{% endblock %}
+
+{% block right-column %}
+<div class="right-column">
+  <blockquote id="quote" class="cite-body">
+    <div>Ten, który walczy z potworami powinien zadbać, by sam nie stał się potworem. 
+    Gdy długo spoglądamy w otchłań, otchłań spogląda również w nas.</div>
+  </blockquote>
+
+  <div id="other-tools">
+    <h2 class="mono">{% trans "See" %}</h2>
+    <ul class="inline-items">
+      {% if extra_info.source_url %}
+      <li><a href="{{ extra_info.source_url }}">{% trans "Source" %}</a> {% trans "of the book" %}</li>
+      {% endif %}
+      {% if extra_info.about and not hide_about %}
+      <li>{% trans "Book on" %} <a href="{{ extra_info.about }}">{% trans "Editor's Platform" %}</a></li>
+      {% endif %}
+      {% if book.gazeta_link %}
+      <li><a href="{{ book.gazeta_link }}">{% trans "Book description on Lektury.Gazeta.pl" %}</a></li>
+      {% endif %}
+      {% if book.wiki_link %}
+      <li><a href="{{ book.wiki_link }}">{% trans "Book description on Wikipedia" %}</a></li>
+      {% endif %}
+    </ul>
+  </div>
+  <div id="other-download">
+    <h2 class="mono">{% trans "Download" %}</h2>
+    <ul class="inline-items">
+      <li>
+       {% if has_media.mp3 or has_media.ogg %}
+       {% trans "Download all audiobooks for this book" %}: 
+       {% if has_media.mp3 %}<a href="{% url download_zip_mp3 book.slug %}">MP3</a>{% endif %}{% if has_media.mp4 and has_media.ogg %},{% endif %}
+       {% if has_media.ogg %}<a href="{% url download_zip_ogg book.slug %}">OGG</a>{% endif %}.
+       {% endif %}
+      </li>
+      <li>
+       <a href="{% url custom_pdf_form %}?slug={{book.slug}}" id="custom-pdf" class="ajaxable">{% trans "Download a custom PDF" %}</a>
+      </li>
+    </ul>
+  </div>
+</div>
+{% endblock %}
diff --git a/wolnelektury/templates/catalogue/breadcrumbs.html b/wolnelektury/templates/catalogue/breadcrumbs.html
deleted file mode 100644 (file)
index 5aafeb9..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-{% load i18n %}
-{% load catalogue_tags %}
-<div id="searchContainer">
-    <form action="{% url search %}" method="get" accept-charset="utf-8" id="search-form">
-    <ul class="facelist-selections" style="float:left;">
-        {% for tag in tag_list %}
-        <li class="facelist-selection-item">
-               <span style="margin-left: 5px;">
-               <a href="{% catalogue_url tag %}">{{ tag }}</a>
-               <a class="facelist-close" href="{% catalogue_url tag_list -tag %}">x</a>
-               </span>
-           </li>
-        {% empty %}
-            <li class="facelist-selection-item">
-            </li>
-        {% endfor %}
-        {% if search_form %}
-            <li>{{ search_form.q }} {{ search_form.tags }}</li>
-        {% endif %}
-    </ul>
-    <input type="submit" value="{% trans "Search" %}" id="searchSubmit"/>
-    </form>
-</div>    
-<div class="clearboth"></div>
diff --git a/wolnelektury/templates/catalogue/catalogue.html b/wolnelektury/templates/catalogue/catalogue.html
new file mode 100644 (file)
index 0000000..c84ebbb
--- /dev/null
@@ -0,0 +1,34 @@
+{% extends "base.html" %}
+{% load i18n %}
+{% load catalogue_tags %}
+
+
+{% block titleextra %}{% trans "Catalogue" %}{% endblock %}
+
+{% block bodyid %}catalogue-catalogue{% endblock %}
+
+{% block body %}
+    <h1>{% trans "Catalogue" %}</h1>
+
+    <div class="normal-text">
+
+    <p><a href="{% url reporting_catalogue_pdf %}">
+        {% trans "Download the catalogue in PDF format." %}
+    </a></p>
+
+    <h2></a>{% trans "Authors" %}<a name="autorzy"></a></h2>
+    {% tag_list categories.author %}
+
+    <h2>{% trans "Kinds" %}<a name="rodzaje"></a></h2>
+    {% tag_list categories.kind %}
+
+    <h2>{% trans "Genres" %}<a name="gatunki"></a></h2>
+    {% tag_list categories.genre %}
+
+    <h2>{% trans "Epochs" %}<a name="epoki"></a></h2>
+    {% tag_list categories.epoch %}
+
+    <h2>{% trans "Themes and topics" %}<a name="motywy"></a></h2>
+    {% tag_list fragment_tags %}
+    </div>
+{% endblock %}
index 113f7b7..4bb12c9 100755 (executable)
@@ -1,7 +1,7 @@
 {% extends "catalogue/book_list.html" %}
 {% load i18n %}
 
-{% block title %}{{ context.collection.title }} {% trans "in WolneLektury.pl" %}{% endblock %}
+{% block titleextra %}{{ context.collection.title }}{% endblock %}
 
 {% block book_list_header %}{{ context.collection.title }}{% endblock %}
 
index b637bed..88e95a4 100644 (file)
@@ -3,7 +3,7 @@
 
 {% block bodyid %}book-a-list{% endblock %}
 
-{% block title %}{% trans "Listing of all DAISY files on WolneLektury.pl" %}{% endblock %}
+{% block titleextra %}{% trans "Listing of all DAISY files" %}{% endblock %}
 
 {% block extrahead %}
     <link rel="alternate" type="application/atom+xml" title="{% trans "Latest DAISY audiobooks" %}" href="{% url audiobook_feed 'daisy' %}" />
index 1e7a1a7..f3bddd3 100644 (file)
@@ -2,13 +2,12 @@
 {% load i18n %}
 {% load catalogue_tags %}
 
-{% block title %}{% title_from_tags tags %} w WolneLektury.pl{% endblock %}
+{% block titleextra %}{% title_from_tags tags %}{% endblock %}
 
 {% block bodyid %}differentiate_tags{% endblock %}
 
 {% block body %}
     <h1>{% title_from_tags tags %}</h1>
-    {% breadcrumbs tags %}
 
        <p>{% trans "The criteria are ambiguous. Please select one of the following options:" %}</p>
     <div id="books-list">
                </div>
         {% endfor %}
     </div>
-
-    <div id="set-window">
-        <div class="header"><a href="#" class="jqmClose">{% trans "Close" %}</a></div>
-        <div class="target">
-            <p><img src="{{ STATIC_URL }}img/indicator.gif" alt="*"/> {% trans "Loading" %}</p>
-        </div>
-    </div>
 {% endblock %}
\ No newline at end of file
diff --git a/wolnelektury/templates/catalogue/folded_tag_list.html b/wolnelektury/templates/catalogue/folded_tag_list.html
deleted file mode 100644 (file)
index a6ff53a..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-{% load i18n %}
-{% load catalogue_tags %}
-{% if one_tag %}
-    <p>{% trans "Show full category" %} <a href="{% catalogue_url one_tag %}">{{ one_tag }}</a>.</p>
-{% else %}
-    <div class="shown-tags">
-        <ul class="shown-tags">
-            {% for tag in shown_tags %}
-                <li><a href="{% catalogue_url choices tag %}">{{ tag }}&nbsp;({{ tag.count }})</a></li>
-            {% endfor %}
-        </ul>
-        {% if some_tags_hidden %}
-            <p><a href="#" class="show-all-tags">{% trans "See more" %}</a></p>
-        {% endif %}
-    </div>
-    <div class="all-tags">
-        <ul>
-            {% for tag in tags %}
-                <li><a href="{% catalogue_url choices tag %}">{{ tag }}&nbsp;({{ tag.count }})</a></li>
-            {% endfor %}
-        </ul>
-        <p><a href="#" class="hide-all-tags">{% trans "Hide" %}</a></p>
-    </div>
-{% endif %}
diff --git a/wolnelektury/templates/catalogue/inline_tag_list.html b/wolnelektury/templates/catalogue/inline_tag_list.html
new file mode 100755 (executable)
index 0000000..d42f3f9
--- /dev/null
@@ -0,0 +1,9 @@
+{% load i18n %}
+{% load catalogue_tags %}
+{% if one_tag %}
+    {% trans "See full category" %} <a href="{% catalogue_url one_tag %}">{{ one_tag }}</a>
+{% else %}
+    {% for tag in tags %}
+        <a href="{% catalogue_url choices tag %}">{{ tag }}&nbsp;({{ tag.book_count }})</a>
+    {% endfor %}
+{% endif %}
diff --git a/wolnelektury/templates/catalogue/main_page.html b/wolnelektury/templates/catalogue/main_page.html
deleted file mode 100644 (file)
index b9cb5a3..0000000
+++ /dev/null
@@ -1,351 +0,0 @@
-{% extends "base.html" %}
-{% load i18n %}
-{% load catalogue_tags chunks cache reporting_stats %}
-
-
-{% block tagline %}
-<div id='tagline'>
-
-{% count_books_nonempty count_books %}
-{% blocktrans count count_books as c %}
-{{c}} book from <a href='http://domenapubliczna.org'>public domain</a> or under
-a <a href='http://creativecommons.org/licenses/by-sa/3.0/deed.pl'>free license</a>.
-{% plural %}
-{{c}} books from <a href='http://domenapubliczna.org'>public domain</a> or under
-a <a href='http://creativecommons.org/licenses/by-sa/3.0/deed.pl'>free license</a>.
-{% endblocktrans %}
-
-</div>
-{% endblock %}
-
-
-{% block bodyid %}main-page{% endblock %}
-
-{% block body %}
-    <div id="site-description">
-        {% chunk "site-description" %}
-    </div>
-    <div class="clearboth"></div>
-    <form action="{% url search %}" method="get" accept-charset="utf-8" id="search-form">
-        <p>{{ form.q }} {{ form.tags }} <input type="submit" value="{% trans "Search" %}" />
-            <strong>{% trans "or" %}</strong> {% trans "see" %}: 
-            <span class='collections'>
-                <a href="{% url catalogue.views.book_list %}">{% trans "all books" %}</a>
-                <a href="{% url catalogue.views.audiobook_list %}">{% trans "audiobooks" %}</a>
-                <a href="{% url catalogue.views.daisy_list %}">{% trans "DAISY" %}</a>
-            </span></p>
-    </form>
-
-    <div id="intro">
-        <p id="tags-description">↓ {% trans "Browse books by categories" %} ↓</p>
-        <div id="propaganda">
-
-            {% chunk "promo" %}
-
-            {% comment %}
-            <h2>Spot promocyjny</h2>
-            <p><iframe width="260" height="162"
-                src="http://www.youtube.com/embed/IteV_EoTrZE"
-                frameborder="0" allowfullscreen></iframe></p>
-            {% endcomment %}
-
-            {% comment %}    <h2>{% trans "Books for every school level" %}</h2>
-            <ul>
-                <li><a href="">{% trans "primary school" %}</a></li>
-                <li><a href="">{% trans "gymnasium" %}</a></li>
-                <li><a href="">{% trans "high school" %}</a></li>
-            </ul>    {% endcomment %}
-
-            <br/>
-            <h2>{% trans "Your shelves with books" %}</h2>
-            {% if user.is_authenticated %}
-                {% if shelves %}
-                <ul class="shelf-list">
-                {% for shelf in shelves %}
-                    <li><a href="{% url delete_shelf shelf.slug %}" class="delete-shelf">{% trans "delete" %}</a> <a href="{{ shelf.get_absolute_url }}" class="visit-shelf">{{ shelf.name }} ({{ shelf.get_count }})</a></li>
-                {% endfor %}
-                </ul>
-                {% else %}
-                <p>{% trans "You do not own any shelves. You can create one below, if you want to." %}</p>
-                {% endif %}
-                <hr />
-                <form action="{% url catalogue.views.new_set %}" method="post" accept-charset="utf-8" class="cuteform">
-                <ol>
-                    <li>{{ new_set_form.name }} <input type="submit" value="{% trans "Create shelf" %}"/></li>
-                </ol>
-                </form>
-                <br/>
-            {% else %}
-                <p>{% trans "Create your own book set. You can share it with friends by sending them link to your shelf." %}</p>
-                <p>{% trans "You need to " %} <a class="login-register-link" href="#">{% trans "sign in" %}</a> {% trans "to manage your shelves." %}</p>
-            {% endif %}
-
-            <h2>Leśmianator</h2>
-            <p>Wybierz, co wpadnie do miksera, i pokaż efekt znajomym!
-                </p>
-            <p class="see-more" style="clear:both"><a href="{% url lesmianator %}">{% trans "Twórzże się!" %} ⇒</a></p>
-
-            <h2>{% trans "Wolne Lektury Widget" %}</h2>
-            <p>{% trans "Place our widget - search engine for Wolne Lektury which gives access to free books and audiobooks - on your homepage! Just copy the HTML code below onto your page:" %}</p>
-            <textarea rows="1" cols="35" class='widget-code' title='widget'><!-- START {% trans "Insert this element in place where you want display the widget" %} -->
-&lt;div id="wl" /&gt;
-&lt;!-- END --&gt;
-&lt;!-- START {% trans "Place this element just before closing body tag: &lt;/body&gt;" %} --&gt;
-&lt;script type="text/javascript" src="http://www.wolnelektury.pl/static/js/widget.js"&gt;&lt;/script&gt;
-&lt;!-- END --&gt;</textarea>
-                <p class="see-more"><a href="{% url widget %}" title="{% trans "Wolne Lektury Widget" %}">{% trans "See more" %} ⇒</a></p>
-
-            <div id="lessons">
-                <h2><a href="{% url lessons_document_list %}">{% trans "Hand-outs for teachers" %}</a></h2>
-                <p>{% trans "Lessons' prospects and other ideas for using Wolnelektury.pl for teaching." %}</p>
-                <p class="see-more"><a href="{% url lessons_document_list %}" title="{% trans "Hand-outs for teachers" %}">{% trans "See more" %} ⇒</a></p>
-            </div>
-        </div>
-        <div id="tags-list">
-            <div id="categories-list">
-                {% if categories.author %}
-                    <h2>{% trans "Authors" %}</h2>
-                    {% folded_tag_list categories.author 'Authors' %}
-                {% endif %}
-                {% if categories.kind %}
-                    <h2>{% trans "Kinds" %}</h2>
-                    {% folded_tag_list categories.kind 'Kinds' %}
-                {% endif %}
-                {% if categories.genre %}
-                    <h2>{% trans "Genres" %}</h2>
-                    {% folded_tag_list categories.genre 'Genres' %}
-                {% endif %}
-                {% if categories.epoch %}
-                    <h2>{% trans "Epochs" %}</h2>
-                    {% folded_tag_list categories.epoch 'Epochs' %}
-                {% endif %}
-            </div>
-            <div id="themes-list">
-                {% if fragment_tags %}
-                    <h2>{% trans "Themes and topics" %}</h2>
-                    {% folded_tag_list fragment_tags 'Themes' %}
-                {% endif %}
-                <h2>{% trans "Themes groups" %}</h2>
-                <div class="shown-tags">
-                    <ol>
-                        <li>środowisko miejskie i wiejskie
-                        <span class="subcategories"><a href="/katalog/motyw/miasto/">Miasto</a>, <a href="/katalog/motyw/warszawa/">Warszawa</a>, <a href="/katalog/motyw/mieszczanin/">Mieszczanin</a>, <a href="/katalog/motyw/handel/">Handel</a>, <a href="/katalog/motyw/robotnik/">Robotnik</a>, <a href="/katalog/motyw/zyd/">Żyd</a>, <a href="/katalog/motyw/wies/">Wieś</a>, <a href="/katalog/motyw/sielanka/">Sielanka</a>, <a href="/katalog/motyw/chlop/">Chłop</a>, <a href="/katalog/motyw/ogrod/">Ogród</a>, <a href="/katalog/motyw/natura/">Natura</a>, <a href="/katalog/motyw/przestrzen/">Przestrzeń</a></span></li>
-
-                        <li>polityczny obraz świata
-                        <span class="subcategories"><a href="/katalog/motyw/panstwo/">Państwo</a>, <a href="/katalog/motyw/obowiazek/">Obowiązek</a>, <a href="/katalog/motyw/cnota/">Cnota</a>, <a href="/katalog/motyw/obywatel/">Obywatel</a>, <a href="/katalog/motyw/patriota/">Patriota</a>, <a href="/katalog/motyw/ojczyzna/">Ojczyzna</a>, <a href="/katalog/motyw/narod/">Naród</a>, <a href="/katalog/motyw/przywodca/">Przywódca</a>, <a href="/katalog/motyw/wladza/">Władza</a>, <a href="/katalog/motyw/urzednik/">Urzędnik</a>, <a href="/katalog/motyw/krol/">Król</a>, <a href="/katalog/motyw/rycerz/">Rycerz</a>, <a href="/katalog/motyw/zolnierz/">Żołnierz</a>, <a href="/katalog/motyw/wojna/">Wojna</a>, <a href="/katalog/motyw/wrog/">Wróg</a>, <a href="/katalog/motyw/zwyciestwo/">Zwycięstwo</a>, <a href="/katalog/motyw/walka/">Walka</a>, <a href="/katalog/motyw/sila/">Siła</a>, <a href="/katalog/motyw/historia/">Historia</a>, <a href="/katalog/motyw/powstanie/">Powstanie</a>, <a href="/katalog/motyw/smierc-bohaterska/">Śmierć bohaterska</a>, <a href="/katalog/motyw/slawa/">Sława</a>, <a href="/katalog/motyw/rewolucja/">Rewolucja</a>, <a href="/katalog/motyw/sad/">Sąd</a>, <a href="/katalog/motyw/zdrada/">Zdrada</a></span></li>
-
-                        <li>przyroda
-                        <span class="subcategories"><a href="/katalog/motyw/natura/">Natura</a>, <a href="/katalog/motyw/zywioly/">Żywioły</a>, <a href="/katalog/motyw/ogien/">Ogień</a>, <a href="/katalog/motyw/ziemia/">Ziemia</a>, <a href="/katalog/motyw/wiatr/">Wiatr</a>, <a href="/katalog/motyw/woda/">Woda</a>, <a href="/katalog/motyw/wiosna/">Wiosna</a>, <a href="/katalog/motyw/lato/">Lato</a>, <a href="/katalog/motyw/jesien/">Jesień</a>, <a href="/katalog/motyw/zima/">Zima</a>, <a href="/katalog/motyw/przemijanie/">Przemijanie</a>, <a href="/katalog/motyw/slonce/">Słońce</a>, <a href="/katalog/motyw/ksiezyc/">Księżyc</a>, <a href="/katalog/motyw/gwiazda/">Gwiazda</a>, <a href="/katalog/motyw/oblok/">Obłok</a>, <a href="/katalog/motyw/noc/">Noc</a>, <a href="/katalog/motyw/swiatlo/">Światło</a>, <a href="/katalog/motyw/gora/">Góra</a>, <a href="/katalog/motyw/rzeka/">Rzeka</a>, <a href="/katalog/motyw/morze/">Morze</a>, <a href="/katalog/motyw/burza/">Burza</a>, <a href="/katalog/motyw/deszcz/">Deszcz</a>, <a href="/katalog/motyw/bloto/">Błoto</a>, <a href="/katalog/motyw/przyroda-nieozywiona/">Przyroda nieożywiona</a>, <a href="/katalog/motyw/rosliny/">Rośliny</a>, <a href="/katalog/motyw/kwiaty/">Kwiaty</a>, <a href="/katalog/motyw/ogrod/">Ogród</a>, <a href="/katalog/motyw/sielanka/">Sielanka</a>, <a href="/katalog/motyw/raj/">Raj</a>, <a href="/katalog/motyw/jablko/">Jabłko</a>, <a href="/katalog/motyw/drzewo/">Drzewo</a>, <a href="/katalog/motyw/zwierzeta/">Zwierzęta</a>, <a href="/katalog/motyw/ptak/">Ptak</a>, <a href="/katalog/motyw/motyl/">Motyl</a>, <a href="/katalog/motyw/kot/">Kot</a>, <a href="/katalog/motyw/kon/">Koń</a>, <a href="/katalog/motyw/pies/">Pies</a>, <a href="/katalog/motyw/waz/">Wąż</a>, <a href="/katalog/motyw/potwor/">Potwór</a></span></li>
-                    </ol>
-                    <p><a href="#" class="show-all-tags" title="{% trans "Themes and topics" %}">{% trans "See more" %}</a></p>
-                </div>
-                <div class="all-tags">
-                    <ol>
-                    <li>cielesność
-                    <span class="subcategories"><a href="/katalog/motyw/cialo/">Ciało</a>, <a href="/katalog/motyw/krew/">Krew</a>, <a href="/katalog/motyw/zdrowie/">Zdrowie</a>, <a href="/katalog/motyw/choroba/">Choroba</a>, <a href="/katalog/motyw/kaleka/">Kaleka</a></span></li>
-
-                    <li>dom
-                    <span class="subcategories"><a href="/katalog/motyw/dom/">Dom</a>, <a href="/katalog/motyw/ogrod/">Ogród</a>, <a href="/katalog/motyw/przestrzen/">Przestrzeń</a>, <a href="/katalog/motyw/gospodarz/">Gospodarz</a>, <a href="/katalog/motyw/gospodyni/">Gospodyni</a>, <a href="/katalog/motyw/sasiad/">Sąsiad</a>, <a href="/katalog/motyw/gosc/">Gość</a>, <a href="/katalog/motyw/bezdomnosc/">Bezdomność</a>, <a href="/katalog/motyw/bezpieczenstwo/">Bezpieczeństwo</a>, <a href="/katalog/motyw/niebezpieczenstwo/">Niebezpieczeństwo</a></span></li>
-
-                    <li>działania nieczyste
-                    <span class="subcategories"><a href="/katalog/motyw/szantaz/">Szantaż</a>, <a href="/katalog/motyw/zazdrosc/">Zazdrość</a>, <a href="/katalog/motyw/zdrada/">Zdrada</a>, <a href="/katalog/motyw/zemsta/">Zemsta</a>, <a href="/katalog/motyw/klamstwo/">Kłamstwo</a>, <a href="/katalog/motyw/falsz/">Fałsz</a>, <a href="/katalog/motyw/pozory/">Pozory</a>, <a href="/katalog/motyw/tajemnica/">Tajemnica</a></span></li>
-
-                    <li>dziedzictwo
-                    <span class="subcategories"><a href="/katalog/motyw/dziedzictwo/">Dziedzictwo</a>, <a href="/katalog/motyw/obyczaje/">Obyczaje</a>, <a href="/katalog/motyw/pamiec/">Pamięć</a>, <a href="/katalog/motyw/historia/">Historia</a>, <a href="/katalog/motyw/narod/">Naród</a>, <a href="/katalog/motyw/krew/">Krew</a>, <a href="/katalog/motyw/panstwo/">Państwo</a>, <a href="/katalog/motyw/obowiazek/">Obowiązek</a>, <a href="/katalog/motyw/patriota/">Patriota</a>, <a href="/katalog/motyw/ruiny/">Ruiny</a>, <a href="/katalog/motyw/dom/">Dom</a>, <a href="/katalog/motyw/przywodca/">Przywódca</a></span></li>
-
-                    <li>dźwięk
-                    <span class="subcategories"><a href="/katalog/motyw/cisza/">Cisza</a>, <a href="/katalog/motyw/muzyka/">Muzyka</a>, <a href="/katalog/motyw/spiew/">Śpiew</a>, <a href="/katalog/motyw/poezja/">Poezja</a></span></li>
-
-                    <li>edukacja
-                    <span class="subcategories"><a href="/katalog/motyw/uczen/">Uczeń</a>, <a href="/katalog/motyw/szkola/">Szkoła</a>, <a href="/katalog/motyw/nauczyciel/">Nauczyciel</a>, <a href="/katalog/motyw/nauczycielka/">Nauczycielka</a>, <a href="/katalog/motyw/nauka/">Nauka</a>, <a href="/katalog/motyw/wiedza/">Wiedza</a>, <a href="/katalog/motyw/dziecinstwo/">Dzieciństwo</a>, <a href="/katalog/motyw/mlodosc/">Młodość</a>, <a href="/katalog/motyw/doroslosc/">Dorosłość</a></span></li>
-
-                    <li>egzystencja ludzka
-                    <span class="subcategories"><a href="/katalog/motyw/kondycja-ludzka/">Kondycja ludzka</a>, <a href="/katalog/motyw/los/">Los</a>, <a href="/katalog/motyw/bladzenie/">Błądzenie</a>, <a href="/katalog/motyw/bunt/">Bunt</a>, <a href="/katalog/motyw/buntownik/">Buntownik</a>, <a href="/katalog/motyw/pielgrzym/">Pielgrzym</a>, <a href="/katalog/motyw/theatrum-mundi/">Theatrum mundi</a>, <a href="/katalog/motyw/zycie-jako-wedrowka/">Życie jako wędrówka</a>, <a href="/katalog/motyw/zycie-snem/">Życie snem</a></span></li>
-
-                    <li>etapy życia
-                    <span class="subcategories"><a href="/katalog/motyw/dziecinstwo/">Dzieciństwo</a>, <a href="/katalog/motyw/mlodosc/">Młodość</a>, <a href="/katalog/motyw/doroslosc/">Dorosłość</a>, <a href="/katalog/motyw/panna-mloda/">Panna młoda</a>, <a href="/katalog/motyw/zona/">Żona</a>, <a href="/katalog/motyw/maz/">Mąż</a>, <a href="/katalog/motyw/wdowa/">Wdowa</a>, <a href="/katalog/motyw/wdowiec/">Wdowiec</a>, <a href="/katalog/motyw/starosc/">Starość</a>, <a href="/katalog/motyw/czas/">Czas</a>, <a href="/katalog/motyw/przemijanie/">Przemijanie</a>, <a href="/katalog/motyw/przemiana/">Przemiana</a>, <a href="/katalog/motyw/kondycja-ludzka/">Kondycja ludzka</a></span></li>
-
-                    <li>fauna
-                    <span class="subcategories"><a href="/katalog/motyw/zwierzeta/">Zwierzęta</a>, <a href="/katalog/motyw/kot/">Kot</a>, <a href="/katalog/motyw/kon/">Koń</a>, <a href="/katalog/motyw/motyl/">Motyl</a>, <a href="/katalog/motyw/pies/">Pies</a>, <a href="/katalog/motyw/ptak/">Ptak</a>, <a href="/katalog/motyw/waz/">Wąż</a></span></li>
-
-                    <li>flora
-                    <span class="subcategories"><a href="/katalog/motyw/rosliny/">Rośliny</a>, <a href="/katalog/motyw/kwiaty/">Kwiaty</a>, <a href="/katalog/motyw/ogrod/">Ogród</a>, <a href="/katalog/motyw/drzewo/">Drzewo</a></span></li>
-
-                    <li>historie miłosne
-                    <span class="subcategories"><a href="/katalog/motyw/milosc/">Miłość</a>, <a href="/katalog/motyw/milosc-platoniczna/">Miłość platoniczna</a>, <a href="/katalog/motyw/milosc-romantyczna/">Miłość romantyczna</a>, <a href="/katalog/motyw/milosc-silniejsza-niz-smierc/">Miłość silniejsza niż śmierć</a>, <a href="/katalog/motyw/milosc-spelniona/">Miłość spełniona</a>, <a href="/katalog/motyw/milosc-tragiczna/">Miłość tragiczna</a>, <a href="/katalog/motyw/kochanek/">Kochanek</a>, <a href="/katalog/motyw/kochanek-romantyczny/">Kochanek romantyczny</a>, <a href="/katalog/motyw/flirt/">Flirt</a>, <a href="/katalog/motyw/pocalunek/">Pocałunek</a>, <a href="/katalog/motyw/pozadanie/">Pożądanie</a>, <a href="/katalog/motyw/list/">List</a>, <a href="/katalog/motyw/serce/">Serce</a>, <a href="/katalog/motyw/lzy/">Łzy</a>, <a href="/katalog/motyw/przysiega/">Przysięga</a>, <a href="/katalog/motyw/tesknota/">Tęsknota</a>, <a href="/katalog/motyw/wspomnienia/">Wspomnienia</a>, <a href="/katalog/motyw/zdrada/">Zdrada</a>, <a href="/katalog/motyw/rozczarowanie/">Rozczarowanie</a>, <a href="/katalog/motyw/rozpacz/">Rozpacz</a>, <a href="/katalog/motyw/malzenstwo/">Małżeństwo</a>, <a href="/katalog/motyw/slub/">Ślub</a>, <a href="/katalog/motyw/panna-mloda/">Panna młoda</a>, <a href="/katalog/motyw/przyjazn/">Przyjaźń</a></span></li>
-
-                    <li>jedzenie i picie
-                    <span class="subcategories"><a href="/katalog/motyw/glod/">Głód</a>, <a href="/katalog/motyw/bieda/">Bieda</a>, <a href="/katalog/motyw/chleb/">Chleb</a>, <a href="/katalog/motyw/jedzenie/">Jedzenie</a>, <a href="/katalog/motyw/uczta/">Uczta</a>, <a href="/katalog/motyw/wino/">Wino</a>, <a href="/katalog/motyw/alkohol/">Alkohol</a>, <a href="/katalog/motyw/pijanstwo/">Pijaństwo</a></span></li>
-
-                    <li>konflikty
-                    <span class="subcategories"><a href="/katalog/motyw/klotnia/">Kłótnia</a>, <a href="/katalog/motyw/bijatyka/">Bijatyka</a>, <a href="/katalog/motyw/sila/">Siła</a>, <a href="/katalog/motyw/przemoc/">Przemoc</a>, <a href="/katalog/motyw/krew/">Krew</a>, <a href="/katalog/motyw/konflikt/">Konflikt</a>, <a href="/katalog/motyw/walka/">Walka</a>, <a href="/katalog/motyw/wojna/">Wojna</a>, <a href="/katalog/motyw/powstanie/">Powstanie</a>, <a href="/katalog/motyw/bunt/">Bunt</a>, <a href="/katalog/motyw/rewolucja/">Rewolucja</a></span></li>
-
-                    <li>momenty graniczne
-                    <span class="subcategories"><a href="/katalog/motyw/narodziny/">Narodziny</a>, <a href="/katalog/motyw/smierc/">Śmierć</a>, <a href="/katalog/motyw/przemiana/">Przemiana</a>, <a href="/katalog/motyw/zmartwychwstanie/">Zmartwychwstanie</a></span></li>
-
-                    <li>nadużycie władzy
-                    <span class="subcategories"><a href="/katalog/motyw/wladza/">Władza</a>, <a href="/katalog/motyw/przemoc/">Przemoc</a>, <a href="/katalog/motyw/strach/">Strach</a>, <a href="/katalog/motyw/kara/">Kara</a></span></li>
-
-                    <li>nacjonalizm
-                    <span class="subcategories"><a href="/katalog/motyw/polak/">Polak</a>, <a href="/katalog/motyw/niemiec/">Niemiec</a>, <a href="/katalog/motyw/rosjanin/">Rosjanin</a>, <a href="/katalog/motyw/car/">Car</a>, <a href="/katalog/motyw/zyd/">Żyd</a>, <a href="/katalog/motyw/narod/">Naród</a>, <a href="/katalog/motyw/obcy/">Obcy</a>, <a href="/katalog/motyw/wrog/">Wróg</a>, <a href="/katalog/motyw/niebezpieczenstwo/">Niebezpieczeństwo</a></span></li>
-
-                    <li>nastroje melancholijne
-                    <span class="subcategories"><a href="/katalog/motyw/nuda/">Nuda</a>, <a href="/katalog/motyw/melancholia/">Melancholia</a>, <a href="/katalog/motyw/ruiny/">Ruiny</a>, <a href="/katalog/motyw/wspomnienia/">Wspomnienia</a>, <a href="/katalog/motyw/marzenie/">Marzenie</a>, <a href="/katalog/motyw/przemijanie/">Przemijanie</a>, <a href="/katalog/motyw/tesknota/">Tęsknota</a>, <a href="/katalog/motyw/rozpacz/">Rozpacz</a>, <a href="/katalog/motyw/smierc/">Śmierć</a>, <a href="/katalog/motyw/los/">Los</a>, <a href="/katalog/motyw/kondycja-ludzka/">Kondycja ludzka</a></span></li>
-
-                    <li>nastroje rewolucyjne
-                    <span class="subcategories"><a href="/katalog/motyw/rewolucja/">Rewolucja</a>, <a href="/katalog/motyw/walka-klas/">Walka klas</a>, <a href="/katalog/motyw/robotnik/">Robotnik</a>, <a href="/katalog/motyw/chlop/">Chłop</a>, <a href="/katalog/motyw/pozycja-spoleczna/">Pozycja społeczna</a>, <a href="/katalog/motyw/wladza/">Władza</a>, <a href="/katalog/motyw/przemoc/">Przemoc</a>, <a href="/katalog/motyw/sprawiedliwosc/">Sprawiedliwość</a></span></li>
-
-                    <li>podporządkowanie
-                    <span class="subcategories"><a href="/katalog/motyw/sluga/">Sługa</a>, <a href="/katalog/motyw/pan/">Pan</a>, <a href="/katalog/motyw/praca/">Praca</a>, <a href="/katalog/motyw/wladza/">Władza</a>, <a href="/katalog/motyw/obyczaje/">Obyczaje</a></span></li>
-
-                    <li>pokrewieństwo
-                    <span class="subcategories"><a href="/katalog/motyw/rodzina/">Rodzina</a>, <a href="/katalog/motyw/ojciec/">Ojciec</a>, <a href="/katalog/motyw/matka/">Matka</a>, <a href="/katalog/motyw/dziecko/">Dziecko</a>, <a href="/katalog/motyw/syn/">Syn</a>, <a href="/katalog/motyw/corka/">Córka</a>, <a href="/katalog/motyw/brat/">Brat</a>, <a href="/katalog/motyw/siostra/">Siostra</a>, <a href="/katalog/motyw/sierota/">Sierota</a>, <a href="/katalog/motyw/dziedzictwo/">Dziedzictwo</a>, <a href="/katalog/motyw/obowiazek/">Obowiązek</a>, <a href="/katalog/motyw/zdrada/">Zdrada</a></span></li>
-
-                    <li>polityczny obraz świata
-                    <span class="subcategories"><a href="/katalog/motyw/panstwo/">Państwo</a>, <a href="/katalog/motyw/obowiazek/">Obowiązek</a>, <a href="/katalog/motyw/cnota/">Cnota</a>, <a href="/katalog/motyw/obywatel/">Obywatel</a>, <a href="/katalog/motyw/patriota/">Patriota</a>, <a href="/katalog/motyw/ojczyzna/">Ojczyzna</a>, <a href="/katalog/motyw/narod/">Naród</a>, <a href="/katalog/motyw/przywodca/">Przywódca</a>, <a href="/katalog/motyw/wladza/">Władza</a>, <a href="/katalog/motyw/urzednik/">Urzędnik</a>, <a href="/katalog/motyw/krol/">Król</a>, <a href="/katalog/motyw/rycerz/">Rycerz</a>, <a href="/katalog/motyw/zolnierz/">Żołnierz</a>, <a href="/katalog/motyw/wojna/">Wojna</a>, <a href="/katalog/motyw/wrog/">Wróg</a>, <a href="/katalog/motyw/zwyciestwo/">Zwycięstwo</a>, <a href="/katalog/motyw/walka/">Walka</a>, <a href="/katalog/motyw/sila/">Siła</a>, <a href="/katalog/motyw/historia/">Historia</a>, <a href="/katalog/motyw/powstanie/">Powstanie</a>, <a href="/katalog/motyw/smierc-bohaterska/">Śmierć bohaterska</a>, <a href="/katalog/motyw/slawa/">Sława</a>, <a href="/katalog/motyw/rewolucja/">Rewolucja</a>, <a href="/katalog/motyw/sad/">Sąd</a>, <a href="/katalog/motyw/zdrada/">Zdrada</a></span></li>
-
-                    <li>pory roku
-                    <span class="subcategories"><a href="/katalog/motyw/wiosna/">Wiosna</a>, <a href="/katalog/motyw/lato/">Lato</a>, <a href="/katalog/motyw/jesien/">Jesień</a>, <a href="/katalog/motyw/zima/">Zima</a>, <a href="/katalog/motyw/czas/">Czas</a>, <a href="/katalog/motyw/przemijanie/">Przemijanie</a></span></li>
-
-                    <li>posiadanie
-                    <span class="subcategories"><a href="/katalog/motyw/pieniadz/">Pieniądz</a>, <a href="/katalog/motyw/handel/">Handel</a>, <a href="/katalog/motyw/korzysc/">Korzyść</a>, <a href="/katalog/motyw/chciwosc/">Chciwość</a>, <a href="/katalog/motyw/bieda/">Bieda</a>, <a href="/katalog/motyw/bogactwo/">Bogactwo</a>, <a href="/katalog/motyw/skapiec/">Skąpiec</a>, <a href="/katalog/motyw/wlasnosc/">Własność</a>, <a href="/katalog/motyw/zlodziej/">Złodziej</a>, <a href="/katalog/motyw/zebrak/">Żebrak</a></span></li>
-
-                    <li>poświęcenie
-                    <span class="subcategories"><a href="/katalog/motyw/poswiecenie/">Poświęcenie</a>, <a href="/katalog/motyw/ofiara/">Ofiara</a>, <a href="/katalog/motyw/prometeusz/">Prometeusz</a>, <a href="/katalog/motyw/milosierdzie/">Miłosierdzie</a>, <a href="/katalog/motyw/chrystus/">Chrystus</a>, <a href="/katalog/motyw/zbawienie/">Zbawienie</a></span></li>
-
-                    <li>poznanie
-                    <span class="subcategories"><a href="/katalog/motyw/filozof/">Filozof</a>, <a href="/katalog/motyw/madrosc/">Mądrość</a>, <a href="/katalog/motyw/medrzec/">Mędrzec</a>, <a href="/katalog/motyw/glupiec/">Głupiec</a>, <a href="/katalog/motyw/glupota/">Głupota</a>, <a href="/katalog/motyw/rozum/">Rozum</a>, <a href="/katalog/motyw/wiedza/">Wiedza</a>, <a href="/katalog/motyw/prawda/">Prawda</a>, <a href="/katalog/motyw/falsz/">Fałsz</a></span></li>
-
-                    <li>poznanie alternatywne
-                    <span class="subcategories"><a href="/katalog/motyw/szaleniec/">Szaleniec</a>, <a href="/katalog/motyw/szalenstwo/">Szaleństwo</a>, <a href="/katalog/motyw/prawda/">Prawda</a>, <a href="/katalog/motyw/pozory/">Pozory</a>, <a href="/katalog/motyw/obraz-swiata/">Obraz świata</a>, <a href="/katalog/motyw/serce/">Serce</a>, <a href="/katalog/motyw/wiedza/">Wiedza</a>, <a href="/katalog/motyw/madrosc/">Mądrość</a>, <a href="/katalog/motyw/dusza/">Dusza</a>, <a href="/katalog/motyw/duch/">Duch</a>, <a href="/katalog/motyw/cialo/">Ciało</a></span></li>
-
-                    <li>praca
-                    <span class="subcategories"><a href="/katalog/motyw/praca/">Praca</a>, <a href="/katalog/motyw/sluga/">Sługa</a></span></li>
-
-                    <li>przyroda
-                    <span class="subcategories"><a href="/katalog/motyw/natura/">Natura</a>, <a href="/katalog/motyw/zywioly/">Żywioły</a>, <a href="/katalog/motyw/ogien/">Ogień</a>, <a href="/katalog/motyw/ziemia/">Ziemia</a>, <a href="/katalog/motyw/wiatr/">Wiatr</a>, <a href="/katalog/motyw/woda/">Woda</a>, <a href="/katalog/motyw/wiosna/">Wiosna</a>, <a href="/katalog/motyw/lato/">Lato</a>, <a href="/katalog/motyw/jesien/">Jesień</a>, <a href="/katalog/motyw/zima/">Zima</a>, <a href="/katalog/motyw/przemijanie/">Przemijanie</a>, <a href="/katalog/motyw/slonce/">Słońce</a>, <a href="/katalog/motyw/ksiezyc/">Księżyc</a>, <a href="/katalog/motyw/gwiazda/">Gwiazda</a>, <a href="/katalog/motyw/oblok/">Obłok</a>, <a href="/katalog/motyw/noc/">Noc</a>, <a href="/katalog/motyw/swiatlo/">Światło</a>, <a href="/katalog/motyw/gora/">Góra</a>, <a href="/katalog/motyw/rzeka/">Rzeka</a>, <a href="/katalog/motyw/morze/">Morze</a>, <a href="/katalog/motyw/burza/">Burza</a>, <a href="/katalog/motyw/deszcz/">Deszcz</a>, <a href="/katalog/motyw/bloto/">Błoto</a>, <a href="/katalog/motyw/przyroda-nieozywiona/">Przyroda nieożywiona</a>, <a href="/katalog/motyw/rosliny/">Rośliny</a>, <a href="/katalog/motyw/kwiaty/">Kwiaty</a>, <a href="/katalog/motyw/ogrod/">Ogród</a>, <a href="/katalog/motyw/sielanka/">Sielanka</a>, <a href="/katalog/motyw/raj/">Raj</a>, <a href="/katalog/motyw/jablko/">Jabłko</a>, <a href="/katalog/motyw/drzewo/">Drzewo</a>, <a href="/katalog/motyw/zwierzeta/">Zwierzęta</a>, <a href="/katalog/motyw/ptak/">Ptak</a>, <a href="/katalog/motyw/motyl/">Motyl</a>, <a href="/katalog/motyw/kot/">Kot</a>, <a href="/katalog/motyw/kon/">Koń</a>, <a href="/katalog/motyw/pies/">Pies</a>, <a href="/katalog/motyw/waz/">Wąż</a>, <a href="/katalog/motyw/potwor/">Potwór</a></span></li>
-
-                    <li>regulacja postępowania
-                    <span class="subcategories"><a href="/katalog/motyw/cnota/">Cnota</a>, <a href="/katalog/motyw/sprawiedliwosc/">Sprawiedliwość</a>, <a href="/katalog/motyw/obowiazek/">Obowiązek</a></span></li>
-
-                    <li>role społeczne
-                    <span class="subcategories"><a href="/katalog/motyw/kobieta/">Kobieta</a>, <a href="/katalog/motyw/mezczyzna/">Mężczyzna</a>, <a href="/katalog/motyw/maz/">Mąż</a>, <a href="/katalog/motyw/zona/">Żona</a>, <a href="/katalog/motyw/matka/">Matka</a>, <a href="/katalog/motyw/ojciec/">Ojciec</a>, <a href="/katalog/motyw/dziecko/">Dziecko</a>, <a href="/katalog/motyw/syn/">Syn</a>, <a href="/katalog/motyw/corka/">Córka</a>, <a href="/katalog/motyw/brat/">Brat</a>, <a href="/katalog/motyw/siostra/">Siostra</a>, <a href="/katalog/motyw/wdowa/">Wdowa</a>, <a href="/katalog/motyw/wdowiec/">Wdowiec</a>, <a href="/katalog/motyw/nauczyciel/">Nauczyciel</a>, <a href="/katalog/motyw/nauczycielka/">Nauczycielka</a>, <a href="/katalog/motyw/uczen/">Uczeń</a>, <a href="/katalog/motyw/poeta/">Poeta</a>, <a href="/katalog/motyw/literat/">Literat</a>, <a href="/katalog/motyw/lekarz/">Lekarz</a>, <a href="/katalog/motyw/sedzia/">Sędzia</a>, <a href="/katalog/motyw/krol/">Król</a>, <a href="/katalog/motyw/przywodca/">Przywódca</a>, <a href="/katalog/motyw/zolnierz/">Żołnierz</a>, <a href="/katalog/motyw/urzednik/">Urzędnik</a>, <a href="/katalog/motyw/filozof/">Filozof</a>, <a href="/katalog/motyw/ksiadz/">Ksiądz</a></span></li>
-
-                    <li>rycerskie czasy
-                    <span class="subcategories"><a href="/katalog/motyw/zamek/">Zamek</a>, <a href="/katalog/motyw/ruiny/">Ruiny</a>, <a href="/katalog/motyw/rycerz/">Rycerz</a>, <a href="/katalog/motyw/honor/">Honor</a>, <a href="/katalog/motyw/wiernosc/">Wierność</a>, <a href="/katalog/motyw/obowiazek/">Obowiązek</a>, <a href="/katalog/motyw/walka/">Walka</a>, <a href="/katalog/motyw/krol/">Król</a>, <a href="/katalog/motyw/dama/">Dama</a></span></li>
-
-                    <li>rzeczywistość nadprzyrodzona
-                    <span class="subcategories"><a href="/katalog/motyw/bog/">Bóg</a>, <a href="/katalog/motyw/chrystus/">Chrystus</a>, <a href="/katalog/motyw/matka-boska/">Matka Boska</a>, <a href="/katalog/motyw/aniol/">Anioł</a>, <a href="/katalog/motyw/szatan/">Szatan</a>, <a href="/katalog/motyw/diabel/">Diabeł</a>, <a href="/katalog/motyw/duch/">Duch</a>, <a href="/katalog/motyw/dusza/">Dusza</a>, <a href="/katalog/motyw/wampir/">Wampir</a>, <a href="/katalog/motyw/upior/">Upiór</a>, <a href="/katalog/motyw/czary/">Czary</a>, <a href="/katalog/motyw/czarownica/">Czarownica</a></span></li>
-
-                    <li>struktura społeczna
-                    <span class="subcategories"><a href="/katalog/motyw/chlop/">Chłop</a>, <a href="/katalog/motyw/mieszczanin/">Mieszczanin</a>, <a href="/katalog/motyw/zyd/">Żyd</a>, <a href="/katalog/motyw/szlachcic/">Szlachcic</a>, <a href="/katalog/motyw/ksiadz/">Ksiądz</a>, <a href="/katalog/motyw/robotnik/">Robotnik</a>, <a href="/katalog/motyw/krol/">Król</a>, <a href="/katalog/motyw/przywodca/">Przywódca</a>, <a href="/katalog/motyw/pozycja-spoleczna/">Pozycja społeczna</a>, <a href="/katalog/motyw/dworek/">Dworek</a>, <a href="/katalog/motyw/obyczaje/">Obyczaje</a></span></li>
-
-                    <li>sarmatyzm
-                    <span class="subcategories"><a href="/katalog/motyw/polak/">Polak</a>, <a href="/katalog/motyw/sarmata/">Sarmata</a>, <a href="/katalog/motyw/szlachcic/">Szlachcic</a>, <a href="/katalog/motyw/przedmurze-chrzescijanstwa/">Przedmurze chrześcijaństwa</a>, <a href="/katalog/motyw/matka-boska/">Matka Boska</a>, <a href="/katalog/motyw/religia/">Religia</a></span></li>
-
-                    <li>sprawowanie władzy
-                    <span class="subcategories"><a href="/katalog/motyw/urzednik/">Urzędnik</a>, <a href="/katalog/motyw/panstwo/">Państwo</a>, <a href="/katalog/motyw/wladza/">Władza</a>, <a href="/katalog/motyw/przemoc/">Przemoc</a>, <a href="/katalog/motyw/sad/">Sąd</a>, <a href="/katalog/motyw/kara/">Kara</a>, <a href="/katalog/motyw/wiezienie/">Więzienie</a></span></li>
-
-                    <li>śmierć
-                    <span class="subcategories"><a href="/katalog/motyw/smierc/">Śmierć</a>, <a href="/katalog/motyw/danse-macabre/">Danse macabre</a>, <a href="/katalog/motyw/gotycyzm/">Gotycyzm</a>, <a href="/katalog/motyw/grob/">Grób</a>, <a href="/katalog/motyw/otchlan/">Otchłań</a>, <a href="/katalog/motyw/pogrzeb/">Pogrzeb</a>, <a href="/katalog/motyw/samobojstwo/">Samobójstwo</a>, <a href="/katalog/motyw/krew/">Krew</a>, <a href="/katalog/motyw/trup/">Trup</a>, <a href="/katalog/motyw/morderstwo/">Morderstwo</a>, <a href="/katalog/motyw/zaloba/">Żałoba</a>, <a href="/katalog/motyw/zmartwychwstanie/">Zmartwychwstanie</a>, <a href="/katalog/motyw/melancholia/">Melancholia</a>, <a href="/katalog/motyw/vanitas/">Vanitas</a>, <a href="/katalog/motyw/los/">Los</a>, <a href="/katalog/motyw/kondycja-ludzka/">Kondycja ludzka</a></span></li>
-
-                    <li>środowisko miejskie i wiejskie
-                    <span class="subcategories"><a href="/katalog/motyw/miasto/">Miasto</a>, <a href="/katalog/motyw/warszawa/">Warszawa</a>, <a href="/katalog/motyw/mieszczanin/">Mieszczanin</a>, <a href="/katalog/motyw/handel/">Handel</a>, <a href="/katalog/motyw/robotnik/">Robotnik</a>, <a href="/katalog/motyw/zyd/">Żyd</a>, <a href="/katalog/motyw/wies/">Wieś</a>, <a href="/katalog/motyw/sielanka/">Sielanka</a>, <a href="/katalog/motyw/chlop/">Chłop</a>, <a href="/katalog/motyw/ogrod/">Ogród</a>, <a href="/katalog/motyw/natura/">Natura</a>, <a href="/katalog/motyw/przestrzen/">Przestrzeń</a></span></li>
-
-                    <li>świat w perspektywie etycznej
-                    <span class="subcategories"><a href="/katalog/motyw/kuszenie/">Kuszenie</a>, <a href="/katalog/motyw/zwatpienie/">Zwątpienie</a>, <a href="/katalog/motyw/wyrzuty-sumienia/">Wyrzuty sumienia</a>, <a href="/katalog/motyw/wina/">Wina</a>, <a href="/katalog/motyw/grzech/">Grzech</a>, <a href="/katalog/motyw/kara/">Kara</a>, <a href="/katalog/motyw/los/">Los</a>, <a href="/katalog/motyw/koniec-swiata/">Koniec świata</a>, <a href="/katalog/motyw/wieza-babel/">Wieża Babel</a>, <a href="/katalog/motyw/zbawienie/">Zbawienie</a>, <a href="/katalog/motyw/zaswiaty/">Zaświaty</a>, <a href="/katalog/motyw/czysciec/">Czyściec</a>, <a href="/katalog/motyw/raj/">Raj</a>, <a href="/katalog/motyw/niesmiertelnosc/">Nieśmiertelność</a>, <a href="/katalog/motyw/przysiega/">Przysięga</a>, <a href="/katalog/motyw/przeklenstwo/">Przekleństwo</a>, <a href="/katalog/motyw/religia/">Religia</a>, <a href="/katalog/motyw/obrzedy/">Obrzędy</a>, <a href="/katalog/motyw/modlitwa/">Modlitwa</a>, <a href="/katalog/motyw/niedziela/">Niedziela</a>, <a href="/katalog/motyw/przedmurze-chrzescijanstwa/">Przedmurze chrześcijaństwa</a>, <a href="/katalog/motyw/ksiadz/">Ksiądz</a>, <a href="/katalog/motyw/poboznosc/">Pobożność</a>, <a href="/katalog/motyw/swietoszek/">Świętoszek</a>, <a href="/katalog/motyw/swiety/">Święty</a>, <a href="/katalog/motyw/wierzenia/">Wierzenia</a>, <a href="/katalog/motyw/zabobony/">Zabobony</a></span></li>
-
-                    <li>świętowanie
-                    <span class="subcategories"><a href="/katalog/motyw/wesele/">Wesele</a>, <a href="/katalog/motyw/uczta/">Uczta</a>, <a href="/katalog/motyw/jedzenie/">Jedzenie</a>, <a href="/katalog/motyw/pijanstwo/">Pijaństwo</a>, <a href="/katalog/motyw/zabawa/">Zabawa</a>, <a href="/katalog/motyw/taniec/">Taniec</a>, <a href="/katalog/motyw/muzyka/">Muzyka</a>, <a href="/katalog/motyw/smiech/">Śmiech</a>, <a href="/katalog/motyw/spiew/">Śpiew</a>, <a href="/katalog/motyw/bijatyka/">Bijatyka</a>, <a href="/katalog/motyw/obyczaje/">Obyczaje</a>, <a href="/katalog/motyw/wierzenia/">Wierzenia</a>, <a href="/katalog/motyw/zabobony/">Zabobony</a></span></li>
-
-                    <li>tożsamość pozorna i podwójna
-                    <span class="subcategories"><a href="/katalog/motyw/portret/">Portret</a>, <a href="/katalog/motyw/lustro/">Lustro</a>, <a href="/katalog/motyw/cien/">Cień</a>, <a href="/katalog/motyw/sobowtor/">Sobowtór</a>, <a href="/katalog/motyw/maska/">Maska</a>, <a href="/katalog/motyw/przebranie/">Przebranie</a>, <a href="/katalog/motyw/stroj/">Strój</a>, <a href="/katalog/motyw/przemiana/">Przemiana</a></span></li>
-
-                    <li>trunki
-                    <span class="subcategories"><a href="/katalog/motyw/alkohol/">Alkohol</a>, <a href="/katalog/motyw/wino/">Wino</a>, <a href="/katalog/motyw/carpe-diem/">Carpe diem</a>, <a href="/katalog/motyw/pijanstwo/">Pijaństwo</a>, <a href="/katalog/motyw/karczma/">Karczma</a>, <a href="/katalog/motyw/obyczaje/">Obyczaje</a></span></li>
-
-                    <li>typy bohaterów
-                    <span class="subcategories"><a href="/katalog/motyw/samotnik/">Samotnik</a>, <a href="/katalog/motyw/buntownik/">Buntownik</a>, <a href="/katalog/motyw/pielgrzym/">Pielgrzym</a>, <a href="/katalog/motyw/szaleniec/">Szaleniec</a>, <a href="/katalog/motyw/filozof/">Filozof</a>, <a href="/katalog/motyw/medrzec/">Mędrzec</a>, <a href="/katalog/motyw/obcy/">Obcy</a>, <a href="/katalog/motyw/przywodca/">Przywódca</a>, <a href="/katalog/motyw/realista/">Realista</a>, <a href="/katalog/motyw/idealista/">Idealista</a>, <a href="/katalog/motyw/spolecznik/">Społecznik</a>, <a href="/katalog/motyw/syzyf/">Syzyf</a>, <a href="/katalog/motyw/prometeusz/">Prometeusz</a>, <a href="/katalog/motyw/sluga/">Sługa</a>, <a href="/katalog/motyw/uczen/">Uczeń</a></span></li>
-
-                    <li>ukrywanie/ujawnianie
-                    <span class="subcategories"><a href="/katalog/motyw/tajemnica/">Tajemnica</a>, <a href="/katalog/motyw/przysiega/">Przysięga</a>, <a href="/katalog/motyw/zdrada/">Zdrada</a>, <a href="/katalog/motyw/klamstwo/">Kłamstwo</a>, <a href="/katalog/motyw/falsz/">Fałsz</a></span></li>
-
-                    <li>upływ czasu
-                    <span class="subcategories"><a href="/katalog/motyw/wspomnienia/">Wspomnienia</a>, <a href="/katalog/motyw/marzenie/">Marzenie</a>, <a href="/katalog/motyw/pamiec/">Pamięć</a>, <a href="/katalog/motyw/przemijanie/">Przemijanie</a>, <a href="/katalog/motyw/czas/">Czas</a>, <a href="/katalog/motyw/smierc/">Śmierć</a></span></li>
-
-                    <li>widzenie
-                    <span class="subcategories"><a href="/katalog/motyw/oko/">Oko</a>, <a href="/katalog/motyw/wzrok/">Wzrok</a>, <a href="/katalog/motyw/sen/">Sen</a>, <a href="/katalog/motyw/marzenie/">Marzenie</a>, <a href="/katalog/motyw/wizja/">Wizja</a>, <a href="/katalog/motyw/przeczucie/">Przeczucie</a>, <a href="/katalog/motyw/duch/">Duch</a>, <a href="/katalog/motyw/dusza/">Dusza</a>, <a href="/katalog/motyw/proroctwo/">Proroctwo</a></span></li>
-
-                    <li>wina i przebaczenie
-                    <span class="subcategories"><a href="/katalog/motyw/grzech/">Grzech</a>, <a href="/katalog/motyw/wina/">Wina</a>, <a href="/katalog/motyw/wyrzuty-sumienia/">Wyrzuty sumienia</a>, <a href="/katalog/motyw/syn-marnotrawny/">Syn marnotrawny</a>, <a href="/katalog/motyw/pokora/">Pokora</a>, <a href="/katalog/motyw/przemiana/">Przemiana</a>, <a href="/katalog/motyw/milosierdzie/">Miłosierdzie</a>, <a href="/katalog/motyw/zbawienie/">Zbawienie</a></span></li>
-
-                    <li>wizerunki kobiety
-                    <span class="subcategories"><a href="/katalog/motyw/kobieta/">Kobieta</a>, <a href="/katalog/motyw/kobieta-demoniczna/">Kobieta demoniczna</a>, <a href="/katalog/motyw/kobieta-upadla/">Kobieta „upadła”</a>, <a href="/katalog/motyw/czarownica/">Czarownica</a>, <a href="/katalog/motyw/dama/">Dama</a>, <a href="/katalog/motyw/proznosc/">Próżność</a>, <a href="/katalog/motyw/cialo/">Ciało</a>, <a href="/katalog/motyw/corka/">Córka</a>, <a href="/katalog/motyw/siostra/">Siostra</a>, <a href="/katalog/motyw/zona/">Żona</a>, <a href="/katalog/motyw/matka/">Matka</a></span></li>
-
-                    <li>w kręgu sztuki
-                    <span class="subcategories"><a href="/katalog/motyw/artysta/">Artysta</a>, <a href="/katalog/motyw/sztuka/">Sztuka</a>, <a href="/katalog/motyw/literat/">Literat</a>, <a href="/katalog/motyw/poeta/">Poeta</a>, <a href="/katalog/motyw/poetka/">Poetka</a>, <a href="/katalog/motyw/poezja/">Poezja</a>, <a href="/katalog/motyw/muzyka/">Muzyka</a>, <a href="/katalog/motyw/taniec/">Taniec</a>, <a href="/katalog/motyw/spiew/">Śpiew</a>, <a href="/katalog/motyw/teatr/">Teatr</a>, <a href="/katalog/motyw/ksiazka/">Książka</a>, <a href="/katalog/motyw/slowo/">Słowo</a>, <a href="/katalog/motyw/slawa/">Sława</a>, <a href="/katalog/motyw/niesmiertelnosc/">Nieśmiertelność</a></span></li>
-
-                    <li>wychodźstwo i uwięzienie
-                    <span class="subcategories"><a href="/katalog/motyw/emigrant/">Emigrant</a>, <a href="/katalog/motyw/tesknota/">Tęsknota</a>, <a href="/katalog/motyw/obcy/">Obcy</a>, <a href="/katalog/motyw/wiezienie/">Więzienie</a>, <a href="/katalog/motyw/przemiana/">Przemiana</a>, <a href="/katalog/motyw/wiezien/">Więzień</a>, <a href="/katalog/motyw/wolnosc/">Wolność</a>, <a href="/katalog/motyw/niewola/">Niewola</a>, <a href="/katalog/motyw/wygnanie/">Wygnanie</a>, <a href="/katalog/motyw/zeslaniec/">Zesłaniec</a>, <a href="/katalog/motyw/zbrodnia/">Zbrodnia</a>, <a href="/katalog/motyw/zbrodniarz/">Zbrodniarz</a></span></li>
-
-                    <li>zagrożenie
-                    <span class="subcategories"><a href="/katalog/motyw/niebezpieczenstwo/">Niebezpieczeństwo</a>, <a href="/katalog/motyw/trucizna/">Trucizna</a>, <a href="/katalog/motyw/falsz/">Fałsz</a>, <a href="/katalog/motyw/zdrada/">Zdrada</a>, <a href="/katalog/motyw/choroba/">Choroba</a>, <a href="/katalog/motyw/smierc/">Śmierć</a></span></li>
-
-                    <li>zajęcia i zawody
-                    <span class="subcategories"><a href="/katalog/motyw/lekarz/">Lekarz</a>, <a href="/katalog/motyw/prawnik/">Prawnik</a>, <a href="/katalog/motyw/sedzia/">Sędzia</a>, <a href="/katalog/motyw/nauczyciel/">Nauczyciel</a>, <a href="/katalog/motyw/nauczycielka/">Nauczycielka</a>, <a href="/katalog/motyw/literat/">Literat</a>, <a href="/katalog/motyw/poeta/">Poeta</a>, <a href="/katalog/motyw/poetka/">Poetka</a>, <a href="/katalog/motyw/artysta/">Artysta</a>, <a href="/katalog/motyw/zolnierz/">Żołnierz</a>, <a href="/katalog/motyw/urzednik/">Urzędnik</a>, <a href="/katalog/motyw/sluga/">Sługa</a>, <a href="/katalog/motyw/rycerz/">Rycerz</a></span></li>
-
-                    <li>życie dworskie
-                    <span class="subcategories"><a href="/katalog/motyw/dworzanin/">Dworzanin</a>, <a href="/katalog/motyw/dwor/">Dwór</a>, <a href="/katalog/motyw/dama/">Dama</a>, <a href="/katalog/motyw/fircyk/">Fircyk</a>, <a href="/katalog/motyw/blazen/">Błazen</a>, <a href="/katalog/motyw/krol/">Król</a>, <a href="/katalog/motyw/urzednik/">Urzędnik</a>, <a href="/katalog/motyw/sluga/">Sługa</a>, <a href="/katalog/motyw/grzecznosc/">Grzeczność</a>, <a href="/katalog/motyw/obyczaje/">Obyczaje</a>, <a href="/katalog/motyw/pochlebstwo/">Pochlebstwo</a></span></li>
-
-                    <li>żywioły
-                    <span class="subcategories"><a href="/katalog/motyw/zywioly/">Żywioły</a>, <a href="/katalog/motyw/ogien/">Ogień</a>, <a href="/katalog/motyw/ziemia/">Ziemia</a>, <a href="/katalog/motyw/wiatr/">Wiatr</a>, <a href="/katalog/motyw/woda/">Woda</a>, <a href="/katalog/motyw/przestrzen/">Przestrzeń</a></span></li>
-                    </ol>
-                    <p><a href="#" class="hide-all-tags">{% trans "Hide" %}</a></p>
-                </div>
-            </div>
-            <div class="clearboth"></div>
-        </div>
-    </div>
-    <div class="clearboth"></div>
-
-    <div id="site-info">
-        <div id="latest-blog-posts">
-            <h2>{% trans "News" %}</h2>
-            {% cache 1800 latest-blog-posts %}
-            {% latest_blog_posts "http://nowoczesnapolska.org.pl/category/wolne-lektury/feed/" %}
-            {% endcache %}
-            <p class="see-more"><a href="http://www.nowoczesnapolska.org.pl/">{% trans "See our blog" %} ⇒</a></p>
-        </div>
-        <div id="you-can-help">
-            <h2>{% trans "You can help us!" %}</h2>
-            <ul>
-                <li>{% trans "Become a volunteer &ndash; an editor, developer or translator." %}</li>
-                <li>{% trans "Gain new skills and experience." %}</li>
-                <li>{% trans "Join an open project of creating an innovative online library." %}</li>
-            </ul>
-            <p class="see-more"><a href="{% url help_us %}" title="{% trans "You can help us!" %}">{% trans "You can help us!" %} ⇒</a></p>
-        </div>
-        <div id="about-us">
-            <h2>{% trans "About us" %}</h2>
-            <p>
-               {% blocktrans %}
-                       Internet library with school readings “Wolne Lektury” (<a href="http://wolnelektury.pl">www.wolnelektury.pl</a>) is a project made by Modern Poland Foundation. It started in 2007 and shares school readings, which are recommended by Ministry of National Education and are in public domain.
-                       {% endblocktrans %}
-            </p>
-            <p class="see-more"><a href="{% url about_us %}" title="{% trans "About us" %}">{% trans "See more" %} ⇒</a></p>
-        </div>
-    </div>
-{% endblock %}
-
-
-{% block add_footer %}
-
-<p>
-{% blocktrans %}
-Portions of this page are modifications based on work created and <a href="http://code.google.com/policies.html">shared by Google</a> and used
-according to terms described in the <a href="http://creativecommons.org/licenses/by/3.0/">Creative Commons
-3.0 Attribution License</a>.
-{% endblocktrans %}
-</p>
-
-{% endblock %}
diff --git a/wolnelektury/templates/catalogue/picture_detail.html b/wolnelektury/templates/catalogue/picture_detail.html
new file mode 100644 (file)
index 0000000..b8b70c6
--- /dev/null
@@ -0,0 +1,94 @@
+{% extends "base.html" %}
+{% load i18n %}
+{% load catalogue_tags pagination_tags %}
+{% load thumbnail %}
+
+
+{% block titleextra %}{{ picture.title }}{% endblock %}
+
+{% block bodyid %}picture-detail{% endblock %}
+
+{% block body %}
+    <h1>{{picture.title}}</h1>
+
+    <div id="books-list">
+        <div id='breadcrumbs'>
+            {% if categories.author %}
+                {% for tag in categories.author %}
+                    <a href="{{ tag.get_absolute_url }}">{{ tag }}</a>{% if not forloop.last %}, {% endif %}
+                {% endfor %}
+                &#187; 
+            {% endif %}
+        </div>
+
+       {% thumbnail picture.image_file "400x500" upscale="false" as im %}
+       <img style="margin:{{ im|margin:"500x500" }}" src="{{ im.url }}" width="{{ im.x }}" height="{{ im.y }}" />
+       {% endthumbnail %}
+
+        {% if picture.info.license %}
+        <p>{% trans "Work is licensed under " %} <a href="{{ picture.info.license }}">{{ picture.info.license_description }}</a>.</p>
+        {% endif %}
+        <p>{% trans "Based on" %}: {{ picture.info.source_name }}</p>
+        {% if picture.info.description %}
+            <div id="description">
+                <div id='description-long'>{{ picture.info.description|safe }}</div>
+{%comment%}                <div id='description-short'>{{ picture.info.description|safe|truncatewords_html:30 }}</div>{%endcomment%}
+            </div>
+            <div id="toggle-description"><p></p></div>
+        {% endif %}
+
+    </div>
+
+    <div id="tags-list">
+        <div id="book-info">
+            <h2>{% trans "Details" %}</h2>
+            <ul>
+                <li>
+                    {% trans "Author" %}:
+                    {% for tag in categories.author %}
+                    <a href="{{ tag.get_absolute_url }}">{{ tag }}</a>{% if not forloop.last %}, {% endif %}
+                    {% endfor %}
+                </li>
+                <li>
+                    {% trans "Epoch" %}:
+                    {% for tag in categories.epoch %}
+                    <a href="{{ tag.get_absolute_url }}">{{ tag }}</a>{% if not forloop.last %}, {% endif %}
+                    {% endfor %}
+                </li>
+                <li>
+                    {% trans "Kind" %}:
+                    {% for tag in categories.kind %}
+                    <a href="{{ tag.get_absolute_url }}">{{ tag }}</a>{% if not forloop.last %}, {% endif %}
+                    {% endfor %}
+                </li>
+            </ul>
+            <h2>{% trans "Other resources" %}</h2>
+            <ul>
+                {% if picture.info.source_url %}
+                <li><a href="{{ picture.info.source_url }}">{% trans "Source of the image" %}</a></li>
+                {% endif %}
+                {% if picture.info.about and not hide_about %}
+                <li><a href="{{ picture.info.about }}">{% trans "Image on the Editor's Platform" %}</a></li>
+                {% endif %}
+{% comment %}
+                {% if book.gazeta_link %}
+                <li><a href="{{ book.gazeta_link }}">{% trans "Picture description on Lektury.Gazeta.pl" %}</a></li>
+                {% endif %}
+                {% if book.wiki_link %}
+                <li><a href="{{ book.wiki_link }}">{% trans "Book description on Wikipedia" %}</a></li>
+                {% endif %}
+{% endcomment %}
+            </ul>
+            <p><a href="{{ picture.xml_file.url }}">{% trans "View XML source" %}</a></p>
+        </div>
+        <div id="themes-list">
+            <h2>{% trans "Work's themes " %}</h2>
+            <ul>
+            {% for theme in picture_themes %}
+                <li><a href="{{ theme.get_absolute_url }}">{{ theme }} ({{ theme.count }})</a></li>
+            {% endfor %}
+            </ul>
+        </div>
+        <div class="clearboth"></div>
+    </div>
+{% endblock %}
diff --git a/wolnelektury/templates/catalogue/picture_list.html b/wolnelektury/templates/catalogue/picture_list.html
new file mode 100644 (file)
index 0000000..a17d987
--- /dev/null
@@ -0,0 +1,32 @@
+{% extends "catalogue/book_list.html" %}
+{% load i18n %}
+{% load catalogue_tags chunks %}
+{% load thumbnail %}
+
+{% block bodyid %}picture-list{% endblock %}
+
+{% block titleextra %}{% trans "Listing of all pictures" %}{% endblock %}
+
+{% block picture_list_header %}{% trans "Listing of all pictures" %}{% endblock %}
+
+
+{% block book_list %}
+{% for author, group in pictures_by_author.items %}
+<a name="{{ author.slug }}"/>
+<div class="group">
+  <h2><a href="{{ author.get_absolute_url }}">{{ author }}</a></h2>
+  {% for picture in group %}
+  <div class="picture">
+    {% thumbnail picture.image_file "300x300" as im %}
+    <img style="float: left; margin:{{ im|margin:"300x300" }}" src="{{ im.url }}" width="{{ im.x }}" height="{{ im.y }}" />
+    {% endthumbnail %}
+    <span class="title"><a href="{{picture.get_absolute_url}}">{{picture.title}}</a></span>
+    <br class="clearboth"/>
+  </div>
+  {% endfor %}
+</div>
+  
+{% endfor %}
+
+{% endblock %}
+
diff --git a/wolnelektury/templates/catalogue/player.html b/wolnelektury/templates/catalogue/player.html
new file mode 100755 (executable)
index 0000000..414bf0e
--- /dev/null
@@ -0,0 +1,149 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+    {% load i18n compressed %}
+    <head>
+        <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
+        <meta http-equiv="Content-Style-Type" content="text/css" />
+        <meta name="description" 
+            content="{{ book.title }} - darmowy audiobook na wolnej licencji" />
+        <title>{% trans "Wolne Lektury" %} ::
+            {{ book.title }} - {{ audiobook }}</title>
+        <link rel="icon" href="{{ STATIC_URL }}img/favicon.png" type="image/png" />
+        {% compressed_css "all" %}
+        {% compressed_css "player" %}
+
+    </head>
+    <body id="{% block bodyid %}player{% endblock %}">
+
+
+
+
+<div class="jp-type-playlist">
+  <div id="jplayer" class="jp-jplayer"
+    data-supplied="{% if have_oggs %}ogg,{% endif %}mp3"></div>
+  <div id="jp_container_1" class="jp-audio">
+    <div class="jp-type-single">
+      <div class="jp-gui jp-interface">
+        <ul class="jp-controls">
+          <li><a href="javascript:;" class="jp-play" tabindex="1">play</a></li>
+          <li><a href="javascript:;" class="jp-pause" tabindex="1">pause</a></li>
+          <li><a href="javascript:;" class="jp-stop" tabindex="1">stop</a></li>
+          <li><a href="javascript:;" class="jp-mute" tabindex="1" title="mute">mute</a></li>
+          <li><a href="javascript:;" class="jp-unmute" tabindex="1" title="unmute">unmute</a></li>
+          <li><a href="javascript:;" class="jp-volume-max" tabindex="1" title="max volume">max volume</a></li>
+        </ul>
+        <div class="jp-progress">
+          <div class="jp-seek-bar">
+            <div class="jp-play-bar"></div>
+          </div>
+        </div>
+        <div class="jp-volume-bar">
+          <div class="jp-volume-bar-value"></div>
+        </div>
+        <div class="jp-time-holder">
+          <div class="jp-current-time"></div>
+          <div class="jp-duration"></div>
+        </div>
+      </div>
+
+      <div class="jp-playlist">
+        <ul>
+
+        {% for i in audiobooks %}
+          <li>
+            <span class='jp-free-media'>
+              (<a class='mp3' href='{{ i.mp3.file.url }}'>mp3</a>{% if i.ogg %}
+              | <a class='ogg' href='{{ i.ogg.file.url }}'>ogg</a>{% endif %})
+            </span>
+            <span class='play'>{{ i.mp3.name }}</span>
+            <div class='extra-info'>
+              {% trans "Artist" %}: <span class='artist'>{{ i.mp3.get_extra_info_value.artist_name }}</span>,
+              {% trans "Director" %}: <span class='director'>{{ i.mp3.get_extra_info_value.director_name }}</span>
+            </div>
+          </li>
+        {% endfor %}
+
+        </ul>
+      </div>
+
+      <div class="jp-no-solution">
+        <span>Update Required</span>
+        To play the media you will need to either update your browser to a recent version or update your <a href="http://get.adobe.com/flashplayer/" target="_blank">Flash plugin</a>.
+      </div>
+    </div>
+  </div>
+</div>
+
+
+    <p>{% trans "Download as" %}:
+        <a href="{% url download_zip_mp3 book.slug %}">MP3</a>{% if have_oggs %},
+            <a href="{% url download_zip_ogg book.slug %}">Ogg Vorbis</a>{% endif %}.
+    </p>
+
+
+        {% if book.has_daisy_file %}
+            <p>DAISY:</p>
+            <ul class="audiobook-list" id="daisy-files">
+            {% for media in book.get_daisy %}
+                <li><a href="{{ media.file.url }}">{{ media.name }}</a></li>
+            {% endfor %}
+            </ul>
+        {% endif %}
+
+
+        {% if projects|length > 1 %}
+            <p>{% trans "Audiobooks were prepared as a part of the projects:" %}</p>
+            <ul>
+            {% for cs, fb in projects %}
+                <li>
+                {% if fb %}
+                    {% blocktrans %}{{ cs }}, funded by {{ fb }}{% endblocktrans %}
+                {% else %}
+                    {{ cs }}
+                {% endif %}
+                </li>
+            {% endfor %}
+            </ul>
+        {% else %}
+            <p>
+            {% with projects.0.0 as cs %}
+            {% with projects.0.1 as fb %}
+                {% if fb %}
+                    {% blocktrans %}Audiobooks were prepared as a part of the {{ cs }} project funded by {{ fb }}.{% endblocktrans %}
+                {% else %}
+                    {% blocktrans %}Audiobooks were prepared as a part of the {{ cs }} project.{% endblocktrans %}
+                {% endif %}
+            {% endwith %}
+            {% endwith %}
+            </p>
+        {% endif %}
+
+
+
+
+
+
+
+
+
+        <div class="clearboth"></div>
+
+
+
+
+
+        <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
+        {% compressed_js "player" %}
+
+        <!--{{ piwik_tag|safe }}
+        <script type="text/javascript">
+        var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
+        document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
+        </script>
+        <script type="text/javascript">
+        var pageTracker = _gat._getTracker("UA-2576694-1");
+        pageTracker._trackPageview();
+        </script>-->
+    </body>
+</html>
diff --git a/wolnelektury/templates/catalogue/search_form.html b/wolnelektury/templates/catalogue/search_form.html
deleted file mode 100755 (executable)
index 4535226..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-{% load i18n %}
-<form action="{% url search %}" method="get" accept-charset="utf-8" id="search-form">
-    <p>{{ form.q }} <input type="submit" value="{% trans "Search" %}" /> <strong>{%trans "or" %}</strong> <a href="{% url main_page %}">{% trans "return to the main page" %}</a></p>
-</form>
index b569e7d..7a3e580 100644 (file)
@@ -2,14 +2,60 @@
 {% load i18n %}
 {% load catalogue_tags pagination_tags %}
 
-{% block title %}{% trans "Searching in" %} WolneLektury.pl{% endblock %}
+{% block titleextra %}{% trans "Search" %}{% endblock %}
 
 {% block bodyid %}tagged-object-list{% endblock %}
 
 {% block body %}
-    <h1>{% title_from_tags tags %}</h1>
-    {% breadcrumbs tags %}
+    <h1>{% trans "Search" %}</h1>
 
+    {% if did_you_mean %}
+      <span class="did_you_mean">{% trans "Dod you mean" %} <a href="{% url search %}?q={{did_you_mean|urlencode}}">{{did_you_mean|lower}}</a></b>?</span>
+    {% endif %}
+    <div id="results">
+      <ol>
+      {% for result in results %}
+      <li>
+       <p><a href="{{result.book.get_absolute_url}}">{{result.book.pretty_title}}</a> (id: {{result.book_id}}, score: {{result.score}})</p>
+       <ul>
+         {% for hit in result.hits %}
+         <li>
+           {% if hit.fragment %}
+           <a href="{{hit.fragment.get_absolute_url}}">Idź do fragmentu</a>
+           <div style="">Tagi/Motywy: {% for tag in hit.themes %}{{tag.name}} {% endfor %}</div>
+           {# snippets or short html? #}
+           {% if hit.snippets %}
+            {% for snip in hit.snippets %}
+              {{snip|safe}}<br/>
+            {% endfor %}
+           {% else %}
+            {{hit.fragment.short_text|safe}}
+           {% endif %}
+           
+           {% else %}
+           {# it's a section #}
+           <a href="{% url book_text result.book.slug %}#f{{hit.section_number}}">{{hit.header_index}}</a>
+            {% if hit.snippets %}
+             {% for snip in hit.snippets %}
+               {{snip|safe}}<br/>
+             {% endfor %}
+            {% else %}
+              [section matched but no snippets :-(]
+            {% endif %}
+           {% endif %}
+         </li>
+         {% endfor %}
+
+       </ul>
+      </li>
+      {% empty %}
+      <p>No results.</p>
+      {% endfor %}
+      </ol>
+    </div>
+
+
+{% comment %}
     <div id="books-list">
         <p>{% trans "More than one result matching the criteria found." %}</p>
         <ul class='matches'>
         {% endfor %}
         </ul>
     </div>
+{% endcomment %}
 
-    <div id="set-window">
-        <div class="header"><a href="#" class="jqmClose">{% trans "Close" %}</a></div>
-        <div class="target">
-            <p><img src="{{ STATIC_URL }}img/indicator.gif" alt="*"/> {% trans "Loading" %}</p>
-        </div>
-    </div>
-{% endblock %}
\ No newline at end of file
+{% endblock %}
index bffefe3..50ad2d3 100644 (file)
@@ -2,23 +2,24 @@
 {% load i18n %}
 {% load catalogue_tags pagination_tags %}
 
-{% block title %}{% trans "Search in WolneLektury.pl" %}{% endblock %}
+{% block titleextra %}{% trans "Search" %}{% endblock %}
 
 {% block bodyid %}tagged-object-list{% endblock %}
 
 {% block body %}
-    <h1>{% title_from_tags tags %}</h1>
-    {% breadcrumbs tags %}
+    <h1>{% trans "Search" %}</h1>
 
-    <div id="books-list">
+    <div class="left-column">
+    <div class="normal-text">
         <p>{% trans "Sorry! Search cirteria did not match any resources." %}</p>
 
                <p>{% blocktrans %}Search engine supports following criteria: title, author, theme/topic, epoch, kind and genre.
                As for now we do not support full text search.{% endblocktrans %}</p>
         {% include "info/join_us.html" %}
     </div>
+    </div>
 
-    <div class="column-right block-form">
+    <div class="right-column">
         {% include "publishing_suggest.html" %}
     </div>
 {% endblock %}
index e10ddc2..62d0ad0 100644 (file)
@@ -2,23 +2,16 @@
 {% load i18n %}
 {% load catalogue_tags pagination_tags %}
 
-{% block title %}{% trans "Searching in" %} WolneLektury.pl{% endblock %}
+{% block titleextra %}{% trans "Search" %}{% endblock %}
 
 {% block bodyid %}tagged-object-list{% endblock %}
 
 {% block body %}
-    <h1>{% title_from_tags tags %}</h1>
-    {% breadcrumbs tags %}
+    <h1>{% trans "Search" %}</h1>
 
     <div id="books-list">
         <p>{% trans "Sorry! Search query must have at least two characters." %}</p>
         {% include "info/join_us.html" %}
     </div>
 
-    <div id="set-window">
-        <div class="header"><a href="#" class="jqmClose">{% trans "Close" %}</a></div>
-        <div class="target">
-            <p><img src="{{ STATIC_URL }}img/indicator.gif" alt="*"/> {% trans "Loading" %}</p>
-        </div>
-    </div>
 {% endblock %}
\ No newline at end of file
index e96320e..3f842ac 100644 (file)
@@ -5,7 +5,7 @@
 {% else %}
     <ul>
         {% for tag in tags %}
-            <li><a href="{% catalogue_url choices tag %}">{{ tag }}&nbsp;({{ tag.count }})</a></li>
+            <li><a href="{% catalogue_url choices tag %}">{{ tag }}&nbsp;({{ tag.book_count }})</a></li>
         {% endfor %}
     </ul>
 {% endif %}
index fec9105..7d3642f 100644 (file)
@@ -2,59 +2,99 @@
 {% load i18n %}
 {% load catalogue_tags pagination_tags switch_tag %}
 
-{% block title %}{% title_from_tags tags %} w WolneLektury.pl{% endblock %}
+{% block titleextra %}{% title_from_tags tags %}{% endblock %}
 
 {% block bodyid %}tagged-object-list{% endblock %}
 
 {% block body %}
+    <div class="left-column">
+    <div class="page-desc">
     <h1>{% title_from_tags tags %}</h1>
-    {% breadcrumbs tags %}
 
-    {% if only_shelf and not object_list %}
-    <div id="books-list">
-        <h2>{% trans "Your shelf is empty" %}</h2>
-        <p>{% trans "You can put a book on a shelf by entering page of the reading and clicking 'Put on the shelf'." %}</p>
+    {% with tags|last as last_tag %}
+    {% if last_tag.has_description %}
+        <div id="description">
+            <div id='description-long'>{{ last_tag.description|safe }}</div>
+            <div id='description-short'>{{ last_tag.description|safe|truncatewords_html:30 }}</div>
+        </div>
+        <div class="clearboth"></div>
+        <div id="toggle-description"><p></p></div>
+    {% endif %}
+
+
+    <div class="inline-tag-lists">
+    {% if categories.author %}
+        <p><span class="mono">{% trans "Authors" %}:</span>
+        {% inline_tag_list categories.author tags %}
+        </p>
+    {% endif %}
+    {% if categories.kind %}
+        <p><span class="mono">{% trans "Kinds" %}:</span>
+        {% inline_tag_list categories.kind tags %}
+        </p>
+    {% endif %}
+    {% if categories.genre %}
+        <p><span class="mono">{% trans "Genres" %}:</span>
+        {% inline_tag_list categories.genre tags %}
+        </p>
+    {% endif %}
+    {% if categories.epoch %}
+        <p><span class="mono">{% trans "Epochs" %}:</span>
+        {% inline_tag_list categories.epoch tags %}
+        </p>
+    {% endif %}
     </div>
-    {% else %}
-    {% autopaginate object_list 10 %}
-    <div id="books-list">
-        {% with tags|last as last_tag %}
-        {% if last_tag.has_description %}
-            <div id="description">
-                <div id='description-long'>{{ last_tag.description|safe }}</div>
-                <div id='description-short'>{{ last_tag.description|safe|truncatewords_html:30 }}</div>
-            </div>
-            <div class="clearboth"></div>
-            <div id="toggle-description"><p></p></div>
-        {% endif %}
-        {% if only_shelf %}
-            <a id="download-shelf" href="{% url download_shelf last_tag.slug %}">
-                {% trans "Download all books from this shelf" %}
-            </a>
-            <div id="download-shelf-menu" style="display:none;">
-                <form action="{% url download_shelf last_tag.slug %}" method="get" accept-charset="utf-8" id="download-formats-form" data-formats-feed="{% url shelf_book_formats last_tag.slug %}">
-                    <p>{% trans "Choose books' formats which you want to download:" %}</p>
-                    <li data-format="pdf"><label for="id_formats_2"><input type="checkbox" name="formats" value="pdf" id="id_formats_2" /> PDF</label> <em><strong>{% trans "for reading" %}</strong> {% trans "and printing using" %} <a href="http://get.adobe.com/reader/">Adobe Reader</a></em></li>
-                    <li data-format="epub"><label for="id_formats_5"><input type="checkbox" name="formats" value="epub" id="id_formats_5" /> EPUB</label> <em><strong>{% trans "for reading" %}</strong> {% trans "on mobile devices" %}</em></li>
-                    <li data-format="mobi"><label for="id_formats_7"><input type="checkbox" name="formats" value="mobi" id="id_formats_7" /> MOBI</label> <em><strong>{% trans "for reading" %}</strong> {% trans "on mobile devices" %}</em></li>
-                    <li data-format="odt"><label for="id_formats_3"><input type="checkbox" name="formats" value="odt" id="id_formats_3" /> ODT</label> <em><strong>{% trans "for reading" %}</strong> {% trans "and editing using" %} <a href="http://pl.openoffice.org/">OpenOffice.org</a></em></li>
-                    <li data-format="txt"><label for="id_formats_4"><input type="checkbox" name="formats" value="txt" id="id_formats_4" /> TXT</label> <em><strong>{% trans "for reading" %}</strong> {% trans "on small displays, for example mobile phones" %}</em></li>
-                    <li data-format="mp3"><label for="id_formats_0"><input type="checkbox" name="formats" value="mp3" id="id_formats_0" /> MP3</label> <em><strong>{% trans "for listening" %}</strong> {% trans "on favourite MP3 player" %}</em></li>
-                    <li data-format="ogg"><label for="id_formats_1"><input type="checkbox" name="formats" value="ogg" id="id_formats_1" /> Ogg Vorbis</label> <em><strong>{% trans "for listening" %}</strong> &mdash; {% trans "open format" %} <a href="http://www.vorbis.com/">{% trans "Xiph.org Foundation" %}</a></em></li>
-                    <li data-format="daisy"><label for="id_formats_6"><input type="checkbox" name="formats" value="daisy" id="id_formats_6" /> DAISY</label> </li>
-                    <li id="download-formats-form-submit-li"><label><input type="submit" name="submit" value="{% trans "Download" %}" id="download-formats-form-submit" disabled="disabled" />&nbsp;<img src="{{ STATIC_URL }}img/indicator.gif" /></label> <span id="updating-formats">{% trans "Updating list of books' formats on the shelf" %}</span><span id="formats-updated" style="display:none;">{% trans "or" %} <a href="#" id="download-formats-form-cancel">{% trans "cancel" %}</a></span></li>
-                    <div class="clearboth"></div>
-                </form>
+
+    {% if categories.theme %}
+        <div id="themes-list-wrapper">
+            <p><a href="#" id="themes-list-toggle" class="mono">{% trans "Motifs and themes" %}</a></p>
+            <div id="themes-list">
+                {% tag_list categories.theme tags %}
             </div>
-            {% if only_my_shelf %}
-            <div id="toggle-share-shelf"><p>{% trans "Share this shelf" %}</p></div>
-            <div id="share-shelf">
-                <p>{% trans "Copy this link and share it with other people to let them see your shelf." %}
-                <input id="share-shelf-url" value='http://{{ request.META.HTTP_HOST }}{{ request.path }}' />
-                </p>
+        </div>
+    {% endif %}
+
+    </div>
+    </div>
+
+
+    <div class="right-column">
+        <a href="" class="cite">
+            <div class="cite-body">
+            Dobranoc, obróć jeszcze raz na mnie oczęta,<br/>
+            (…) Daj mi pierś ucałować<br/>
+            Dobranoc, zapięta.
             </div>
-                       {% endif %}
-        {% endif %}
+            <p class="mono">Adam Mickiewicz, Dziady część III</p>
+        </a>
+
+        <div class="see-also">
+            <h2 class='mono'>{% trans "See also" %}:</h2>
+            <ul>
+                <li><a href="">Wiki</a></li>
+                <li><a href="">Gazeta</a></li>
+            </ul>
+        </div>
+
+        <div class="download">
+            <h2 class='mono'>{% trans "Download" %}:</h2>
+            <ul>
+                <li><a href="">wszystko</a></li>
+                <li><a href="">część</a></li>
+            </ul>
+        </div>
+
+    </div>
+
+    <div class="clearboth"></div>
+
+
+
+
+    {% autopaginate object_list 10 %}
+    <div id="books-list">
+
+
         {% if last_tag.gazeta_link %}
         <p><a href="{{ last_tag.gazeta_link }}">
             {% switch last_tag.category %}
         {% endif %}
 
         {% if object_list %}
-            <ol>
+            {% spaceless %}
+            <ol class='work-list'>
             {% for book in object_list %}
-                <li>
+                <li class='work-item'>
                     {% if user_is_owner %}
                         <a href="{% url remove_from_shelf last_tag.slug book.slug %}" class="remove-from-shelf">{% trans "Delete" %}</a>
                     {% endif %}
                     {{ book.short_html }}</li>
             {% endfor %}
             </ol>
+            {% endspaceless %}
             {% paginate %}
         {% else %}
             {% trans "Sorry! Search cirteria did not match any resources." %}
     </div>
        {% if object_list %}
        {% comment %} If we didn't find anything there will be nothing on the right side as well {% endcomment %}
-    <div id="tags-list">
-        <div id="categories-list">
-            {% if categories.author %}
-                <h2>{% trans "Authors" %}</h2>
-                {% tag_list categories.author tags %}
-            {% endif %}
-            {% if categories.kind %}
-                <h2>{% trans "Kinds" %}</h2>
-                {% tag_list categories.kind tags %}
-            {% endif %}
-            {% if categories.genre %}
-                <h2>{% trans "Genres" %}</h2>
-                {% tag_list categories.genre tags %}
-            {% endif %}
-            {% if categories.epoch %}
-                <h2>{% trans "Epochs" %}</h2>
-                {% tag_list categories.epoch tags %}
-            {% endif %}
-            {% if only_shelf %}
-                <p><a href="{% url poem_from_set tags.0.slug %}">Miksuj utwory z tej półki</a>
-            {% endif %}
-        </div>
-        <div id="themes-list">
-            {% if categories.theme %}
-                <h2>{% trans "Themes" %}</h2>
-                {% tag_list categories.theme tags %}
-            {% endif %}
-        </div>
-        <div class="clearboth"></div>
-    </div>
        {% endif %}
-    {% endif %}
-    <div id="set-window">
-        <div class="header"><a href="#" class="jqmClose">{% trans "Close" %}</a></div>
-        <div class="target">
-            <p><img src="{{ STATIC_URL }}img/indicator.gif" alt="*"/> {% trans "Loading" %}</p>
-        </div>
-    </div>
 {% endblock %}
index d047dbe..28c1222 100644 (file)
@@ -3,7 +3,7 @@
 {% if shelves %}
 <ul class="shelf-list">
 {% for shelf in shelves %}
-    <li><a href="{% url delete_shelf shelf.slug %}" class="delete-shelf">{% trans "remove" %}</a> <a href="{{ shelf.get_absolute_url }}" class="visit-shelf">{{ shelf.name }} ({{ shelf.get_count }})</a></li>
+    <li><a href="{% url delete_shelf shelf.slug %}" class="delete-shelf">{% trans "remove" %}</a> <a href="{{ shelf.get_absolute_url }}" class="visit-shelf">{{ shelf.name }} ({{ shelf.book_count }})</a></li>
 {% endfor %}
 </ul>
 {% else %}
diff --git a/wolnelektury/templates/info/base.html b/wolnelektury/templates/info/base.html
deleted file mode 100644 (file)
index ea15136..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-{% extends "base.html" %}
-{% load i18n %}
-{% load chunks %}
-
-{% block title %}{{ object.page_title }}{% endblock %}
-
-{% block metadescription %}{{ object.left_column|striptags|truncatewords:10 }}{% endblock %}
-
-{% block body %}
-    <h1>{{ object.title }}</h1>
-    <form action="{% url search %}" method="get" accept-charset="utf-8" id="search-form">
-        <p>{{ form.q }} <input type="submit" value="{% trans "Search" %}" /> <strong>{%trans "or" %}</strong> <a href="{% url main_page %}">{% trans "return to the main page" %}</a></p>
-    </form>
-
-    {% autoescape off %}
-    <div class="column-left">
-       {{ object.left_column }}
-    </div>
-    <div class="column-right">
-       {{ object.right_column }}
-    </div>
-       {% endautoescape %}
-{% endblock %}
index 92c1fd0..1b98051 100644 (file)
@@ -1,7 +1,7 @@
 {% load i18n %}
 {% load reporting_stats %}
 
-{% count_books_nonempty book_count %}
+{% count_books book_count %}
 <p>
 {% blocktrans count book_count as c %}
 We have {{c}} work published in Wolne Lektury!
@@ -19,4 +19,4 @@ or transferring 1&#37; of your income tax</a>.
 <p>{% blocktrans %}Become an editor of Wolne Lektury! Find out if
 we're currently working on a reading you're looking for and prepare
 a publication by yourself by logging into the Editorial Platform.{% endblocktrans %}
-<a href='{% url help_us %}'>{% trans "More..." %}</a></p>
+<a href='{% url infopage 'mozesz-nam-pomoc' %}'>{% trans "More..." %}</a></p>
index 5c8490c..2f6a6e5 100644 (file)
@@ -2,7 +2,7 @@
 {% load i18n %}
 {% load catalogue_tags %}
 
-{% block title %}Leśmianator w WolneLektury.pl{% endblock %}
+{% block titleextra %}Leśmianator{% endblock %}
 
 {% block metadescription %}Stwórz własny wierszmiks z utworów znajdujących się na Wolnych Lekturach.{% endblock %}
 
 
 {% block body %}
     <h1>Leśmianator</h1>
-    <form action="{% url search %}" method="get" accept-charset="utf-8" id="search-form">
-        <p>{{ form.q }} <input type="submit" value="{% trans "Search" %}" /> <strong>{% trans "or" %}</strong> <a href="{% url main_page %}">{% trans "return to main page" %}</a></p>
-    </form>
 
-    <div id="books-list">
+    <div class="left-column">
+    <div class="normal-text">
         <p>Leśmianator tworzy wierszmiksy – dzięki niemu
             <a href="{% url new_poem %}">napiszesz wiersz jednym kliknięciem</a>.
             W nowej odsłonie nowe możliwości zabawy – teraz możesz zdecydować, co wrzucasz do miksera,
             {% endfor %}
     
     </div>
+    </div>
 
-    <div id="tags-list">
+    <div class="right-column">
+    <div class="normal-text">
         <h3>Miksuj utwory</h3>
         <p>Możesz <a href="{% url new_poem %}">zmiksować całą lirykę</a> w naszej bibliotece
         albo tylko jeden konkretny utwór. Jak? Wejdź na
@@ -56,4 +56,5 @@
         <p>Miłej zabawy!</p>
 
     </div>
+    </div>
 {% endblock %}
\ No newline at end of file
index 511bbfc..b4e44e2 100644 (file)
@@ -4,7 +4,7 @@
 
 {% block bodyid %}document-list-body{% endblock %}
 
-{% block title %}{% trans "Hand-outs for teachers on " %}WolneLektury.pl{% endblock %}
+{% block titleextra %}{% trans "Hand-outs for teachers" %}{% endblock %}
 
 {% block metadescription %}Scenariusze lekcji. Materiały dla nauczycieli na wolnej licencji.{% endblock %}
 
@@ -30,9 +30,7 @@
 {% endblock extrahead %}
 {% block body %}
     <h1>{% trans "Hand-outs for teachers" %}</h1>
-    <form action="{% url search %}" method="GET" accept-charset="utf-8" id="search-form">
-        <p>{{ form.q }} <input type="submit" value="{% trans "Search" %}" /> <strong>{% trans "or" %}</strong> <a href="{% url main_page %}">{% trans "return to main page" %}</a></p>
-    </form>
+
     <div id="document-list">
         {% chunk "document-list" %}
     </div>
diff --git a/wolnelektury/templates/main_page.html b/wolnelektury/templates/main_page.html
new file mode 100755 (executable)
index 0000000..b0de3cf
--- /dev/null
@@ -0,0 +1,81 @@
+{% extends "base.html" %}
+{% load cache i18n catalogue_tags infopages_tags %}
+
+
+{% block title %}{% trans "Wolne Lektury internet library" %}{% endblock %}
+
+{% block body %}
+
+    <blockquote id="big-cite">
+        <a href='http://google.com'>
+        <h2 class="mono">Jarosław Lipszyc poleca:</h2>
+        <p id="big-cite-text">Dobranoc, obróć jeszcze raz na mnie oczęta,<br/>
+        (…) Daj mi pierś ucałować<br/>
+        Dobranoc, zapięta.</p>
+        <p class="mono" id="big-cite-source">Adam Mickiewicz, Dziady część III</p>
+        </a>
+    </blockquote>
+
+
+    {% spaceless %}
+
+
+    <div id="promo-box">
+        <div class="grid-line" id="promo-box-header">
+            <h2 class="mono">Trwa konkurs</h2>
+        </div>
+        <div id="promo-box-body" class="accent4">
+            <p id="promo-box-title" class="mono"><span>Konkurs poezji automatycznej</span></p>
+            <div id="promo-box-content">
+            <p>Znacie Leśmianatora? To niewielki skrypt miskujący na życzenie
+            wiersze z Wolnych Lektur.</p>
+            </div>
+        </div>
+    </div>
+
+
+    <h2 class="main-last"><span class="mono">Ostatnie publikacje</span></h2>
+        {% for book in last_published %}
+            {{ book.mini_box }}
+        {% endfor %}
+
+    <div class="clearboth"></div>
+
+    <div class="infopages-box">
+        <h2><span class='mono'>Aktualności</span></h2>
+        {% cache 1800 latest-blog-posts %}
+            {% latest_blog_posts "http://nowoczesnapolska.org.pl/category/wolne-lektury/feed/" %}
+        {% endcache %}
+    </div>
+
+
+    <div class="infopages-box">
+        <h2><span class='mono'>Narzędzia</span></h2>
+
+        <ul>
+            <li><a href="{% url suggest %}" id="suggest" class="ajaxable">{% trans "Report a bug or suggestion" %}</a></li>
+            <li><a href="http://turniej.wolnelektury.pl">Turniej Elektrybałtów</a></li>
+            <li><a href="{% url lesmianator %}">Leśmianator</a></li>
+            <li><a href="{% url infopage "mobilna" %}">{% trans "Mobile app" %}</a></li>
+            <li><a href="{% url infopage "widget" %}">{% trans "Widget" %}</a></li>
+            <li><a href="{% url suggest_publishing %}" id="suggest-publishing" class="ajaxable">{% trans "Missing a book?" %}</a></li>
+            <li><a href="{% url publish_plan %}">{% trans "Publishing plan" %}</a></li>
+        </ul>
+    </div>
+
+
+    <div class="infopages-box">
+        <h2><span class='mono'>Informacje</span></h2>
+
+        {% infopages_on_main %}
+
+        <div class="social-links">
+            <a href="http://pl-pl.facebook.com/pages/Wolne-Lektury/203084073268"><img src="{{ STATIC_URL }}img/social/facebook.png" alt="WolneLektury @ Facebook" /></a>
+            <a href="http://nk.pl/profile/30441509"><img src="{{ STATIC_URL }}img/social/naszaklasa.png" alt="WolneLektury @ NK" /></a>
+        </div>
+    </div>
+
+
+    {% endspaceless %}
+
+{% endblock %}
diff --git a/wolnelektury/templates/newsearch/search.html b/wolnelektury/templates/newsearch/search.html
new file mode 100644 (file)
index 0000000..c494ca6
--- /dev/null
@@ -0,0 +1,60 @@
+{% extends "base.html" %}
+{% load i18n %}
+{% load catalogue_tags %}
+
+{% block title %}Search{% endblock %}
+
+{% block metadescription %}{% endblock %}
+
+{% block bodyid %}newsearch{% endblock %}
+
+{% block body %}
+    <h1>Search</h1>
+    <form action="{% url search %}" method="get" accept-charset="utf-8" id="search-form-x">
+        <p>
+         <input type="text" name="q" value="{{request.GET.q}}" style="width:250px; font-size: 1.2em;">
+         <input type="submit" value="{% trans "Search" %}" /> 
+         <br />
+         <input type="checkbox" value="true" name="fuzzy" {% if fuzzy %}checked{% endif %}/> fuzzy.
+       </p>
+    </form>
+    {% if did_you_mean %}
+    Czy miałeś na mysli <a href="?q={{did_you_mean|urlencode}}">{{did_you_mean}}</a>?
+    {% endif %}
+
+
+    <div id="results">
+      <ol>
+      {% for result in results %}
+      <li>
+       <p><a href="{{result.book.get_absolute_url}}">{{result.book.pretty_title}}</a> (id: {{result.book_id}}, score: {{result.score}})</p>
+       <ul>
+         {% for hit in result.hits %}
+         <li>
+           {% for snip in hit.3.snippets %}
+             {{snip|safe}}<br/>
+           {% endfor %}
+         </li>
+         {% endfor %}
+
+         {% for part in result.parts %}
+         {% if part.header %}
+         <li>W {{part.header}} nr {{part.position}}</li>
+         {% else %} 
+         {% if part.fragment %}
+         <li>
+           <div style="">Tagi/Motywy: {% for tag in part.fragment.tags %}{{tag.name}} {% endfor %}</div>
+           {{part.fragment.short_html|safe}}
+         </li>
+         {% endif %}
+         {% endif %}
+         {% endfor %}
+       </ul>
+      </li>
+      {% empty %}
+      <p>No results.</p>
+      {% endfor %}
+      </ol>
+    </div>
+
+{% endblock %}
index 1fd0985..7cdcf2a 100644 (file)
@@ -1,7 +1,7 @@
 {% extends "base.html" %}
 {% load i18n %}
 
-{% block title %}{{ author.name }} w WolneLektury.pl{% endblock %}
+{% block titleextra %}{{ author.name }}{% endblock %}
 
 {% block metadescription %}Licznik domeny publicznej: {{author.name}}.{% endblock %}
 
@@ -9,9 +9,6 @@
 
 {% block body %}
     <h1>{{ author.name }}</h1>
-    <form action="{% url search %}" method="get" accept-charset="utf-8" id="search-form">
-        <p>{{ form.q }} <input type="submit" value="{% trans "Search" %}" /> <strong>{% trans "or" %}</strong> <a href="{% url main_page %}">{% trans "return to main page" %}</a></p>
-    </form>
 
     <div id="books-list">
         {% if author.has_description %}
@@ -42,7 +39,7 @@
             {% else %}
                 <div>
                     <p>{% trans "This author's works will become part of public domain and will be allowed to be published without restrictions in" %}</p>
-                    {% include "pdcounter/pd_counter.html" %}
+                    <div id='countdown' data-year='{{ pd_counter }}'></div>
                     <p>{% trans "<a href='http://domenapubliczna.org/co-to-jest-domena-publiczna/'>Find out</a> why Internet libraries can't publish this author's works." %}</p>
                 </div>
             {% endif %}
index 78b04d6..6e77a78 100644 (file)
@@ -1,7 +1,7 @@
 {% extends "base.html" %}
 {% load i18n %}
 
-{% block title %}{{ book.title }} w WolneLektury.pl{% endblock %}
+{% block titleextra %}{{ book.title }}{% endblock %}
 
 {% block metadescription %}Licznik domeny publicznej: {{ book.title }}.{% endblock %}
 
@@ -9,9 +9,6 @@
 
 {% block body %}
     <h1>{{ book.author }}, {{ book.title }}</h1>
-    <form action="{% url search %}" method="get" accept-charset="utf-8" id="search-form">
-        <p>{{ form.q }} <input type="submit" value="{% trans "Search" %}" /> <strong>{% trans "or" %}</strong> <a href="{% url main_page %}">{% trans "return to main page" %}</a></p>
-    </form>
 
     <div id="books-list">
     {% if book.in_pd %}
@@ -19,7 +16,7 @@
        {% else %}
            {% if book.pd %}
                        <p>{% trans "This work will become part of public domain and will be allowed to be published without restrictions in" %}</p>
-                   {% include "pdcounter/pd_counter.html" %}
+            <div id='countdown' data-year='{{ pd_counter }}'></div>
                        <p>{% trans "<a href='http://domenapubliczna.org/co-to-jest-domena-publiczna/'>Find out</a> why Internet libraries can't publish this work." %}</p>
                {% else %}
                    <p>{% trans "This work is copyrighted." %}
diff --git a/wolnelektury/templates/pdcounter/pd_counter.html b/wolnelektury/templates/pdcounter/pd_counter.html
deleted file mode 100644 (file)
index fd157ad..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-<div id='countdown'></div>
-<script>
-{% if LANGUAGE_CODE != 'en' %}
-    $.countdown.setDefaults($.countdown.regional['{{ LANGUAGE_CODE }}']);
-{% else %}
-    $.countdown.setDefaults($.countdown.regional['']);
-{% endif %}
-d = new Date({{ pd_counter }}, 0, 1);
-function re() {location.reload()};
-$('#countdown').countdown({until: d, format: 'ydHMS', serverSync: serverTime,
-onExpiry: re, alwaysExpire: true});
-</script>
diff --git a/wolnelektury/templates/picture/picture_short.html b/wolnelektury/templates/picture/picture_short.html
new file mode 100644 (file)
index 0000000..1f6a4c0
--- /dev/null
@@ -0,0 +1,42 @@
+{% load i18n %}
+{% load thumbnail %}
+<div class="picture-box">
+<div class="picture-box-inner">
+    <a href="{{ picture.get_absolute_url }}">
+      {% thumbnail picture.image_file "216x288" as thumb %}
+      <img src="{{thumb.url}}"/>
+      {% endthumbnail %}
+    </a>
+    <div class="picture-box-body">
+        <div class="picture-box-head">
+            <div class="mono author">
+            {% for author in tags.author %}
+                {{ author }}
+            {% endfor %}
+            </div>
+            <div class="title">{{ picture.title }}</div>
+        </div>
+        <div class="tags">
+            {% spaceless %}
+
+            <span class="mono">{% trans "Epoch" %}:&nbsp;</span>
+            <span class="picture-box-tag">
+                {% for tag in tags.epoch %}
+                    <a href="{{ tag.get_absolute_url }}">{{ tag.name }} </a>
+                {% endfor %}
+            </span>
+
+            <span class="mono">{% trans "Kind" %}:&nbsp;</span>
+            <span class="picture-box-tag">
+                {% for tag in tags.kind %}
+                    <a href="{{ tag.get_absolute_url }}">{{ tag.name }} </a>
+                {% endfor %}
+            </span>
+
+            {% endspaceless %}
+        </div>
+    </div>
+    <ul class="picture-box-tools">
+    </ul>
+</div>
+</div>
diff --git a/wolnelektury/templates/publish_plan.html b/wolnelektury/templates/publish_plan.html
new file mode 100755 (executable)
index 0000000..c4c3d6e
--- /dev/null
@@ -0,0 +1,16 @@
+{% extends "base.html" %}
+{% load i18n %}
+
+{% block titleextra %}{% trans "Publishing plan" %}{% endblock titleextra %}
+
+
+{% block body %}
+<h1>{% trans "Publishing plan" %}</h1>
+
+<ul class="normal-text">
+{% for elem in plan %}
+    <li><a href="{{ elem.link }}">{{ elem.title }}</a></li>
+{% endfor %}
+</ul>
+
+{% endblock %}
index f9f7ab9..cda9ebb 100644 (file)
@@ -7,7 +7,7 @@ from modeltranslation.translator import translator, TranslationOptions
 from infopages.models import InfoPage
 
 class InfoPageTranslationOptions(TranslationOptions):
-    fields = ('page_title', 'title', 'left_column', 'right_column')
+    fields = ('title', 'left_column', 'right_column')
 
 translator.register(InfoPage, InfoPageTranslationOptions)
 
index ed0228a..90895a5 100644 (file)
@@ -4,15 +4,24 @@ import os
 from django.conf.urls.defaults import *
 from django.conf import settings
 from django.contrib import admin
+import views
 
-from catalogue.forms import SearchForm
 
-from infopages.models import InfoPage
+admin.autodiscover()
 
+urlpatterns = patterns('wolnelektury.views',
+    url(r'^$', 'main_page', name='main_page'),
+    url(r'^planowane/$', 'publish_plan', name='publish_plan'),
 
-admin.autodiscover()
+    url(r'^zegar/$', 'clock', name='clock'),
+
+    # Authentication
+    url(r'^uzytkownicy/zaloguj/$', views.LoginFormView(), name='login'),
+    url(r'^uzytkownicy/utworz/$', views.RegisterFormView(), name='register'),
+    url(r'^uzytkownicy/wyloguj/$', 'logout_then_redirect', name='logout'),
+)
 
-urlpatterns = patterns('',
+urlpatterns += patterns('',
     url(r'^katalog/', include('catalogue.urls')),
     url(r'^materialy/', include('lessons.urls')),
     url(r'^opds/', include('opds.urls')),
@@ -20,41 +29,41 @@ urlpatterns = patterns('',
     url(r'^lesmianator/', include('lesmianator.urls')),
     url(r'^przypisy/', include('dictionary.urls')),
     url(r'^raporty/', include('reporting.urls')),
-
-    # Static pages
-    url(r'^mozesz-nam-pomoc/$', 'infopages.views.infopage', {'slug': 'help_us'}, name='help_us'),
-    url(r'^o-projekcie/$', 'infopages.views.infopage', {'slug': 'about_us'}, name='about_us'),
-    url(r'^widget/$', 'infopages.views.infopage', {'slug': 'widget'}, name='widget'),
+    url(r'^info/', include('infopages.urls')),
 
     # Admin panel
     url(r'^admin/catalogue/book/import$', 'catalogue.views.import_book', name='import_book'),
+    url(r'^admin/catalogue/picture/import$', 'picture.views.import_picture', name='import_picture'),
     url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
     url(r'^admin/', include(admin.site.urls)),
 
-    # Authentication
-    url(r'^uzytkownicy/zaloguj/$', 'catalogue.views.login', name='login'),
-    url(r'^uzytkownicy/wyloguj/$', 'catalogue.views.logout_then_redirect', name='logout'),
-    url(r'^uzytkownicy/utworz/$', 'catalogue.views.register', name='register'),
-    url(r'^uzytkownicy/login/$', 'django.contrib.auth.views.login', name='simple_login'),
-
     # API
     (r'^api/', include('api.urls')),
 
+    url(r'^fullsearch/', include('search.urls')),
+
     # Static files
     url(r'^%s(?P<path>.*)$' % settings.MEDIA_URL[1:], 'django.views.static.serve',
         {'document_root': settings.MEDIA_ROOT, 'show_indexes': True}),
     url(r'^%s(?P<path>.*)$' % settings.STATIC_URL[1:], 'django.views.static.serve',
         {'document_root': settings.STATIC_ROOT, 'show_indexes': True}),
-    url(r'^$', 'django.views.generic.simple.redirect_to', {'url': 'katalog/',
-        'permanent': False}),
     url(r'^i18n/', include('django.conf.urls.i18n')),
 )
 
 urlpatterns += patterns('django.views.generic.simple',
     # old static pages - redirected
-    (r'^1procent/$', 'redirect_to', {'url': 'http://nowoczesnapolska.org.pl/wesprzyj_nas/'}),
-    (r'^wolontariat/$', 'redirect_to', {'url': '/mozesz-nam-pomoc/'}),
-    (r'^epub/$', 'redirect_to', {'url': '/katalog/lektury/'}),
+    url(r'^1procent/$', 'redirect_to',
+        {'url': 'http://nowoczesnapolska.org.pl/wesprzyj_nas/'}),
+    url(r'^epub/$', 'redirect_to',
+        {'url': '/katalog/lektury/'}),
+    url(r'^mozesz-nam-pomoc/$', 'redirect_to',
+        {'url': '/info/mozesz-nam-pomoc'}),
+    url(r'^o-projekcie/$', 'redirect_to',
+        {'url': '/info/o-projekcie'}),
+    url(r'^widget/$', 'redirect_to',
+        {'url': '/info/widget'}),
+    url(r'^wolontariat/$', 'redirect_to',
+        {'url': '/info/mozesz-nam-pomoc/'}),
 )
     
 
diff --git a/wolnelektury/views.py b/wolnelektury/views.py
new file mode 100755 (executable)
index 0000000..c594732
--- /dev/null
@@ -0,0 +1,94 @@
+from datetime import datetime
+import feedparser
+
+from django.contrib import auth
+from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
+from django.core.cache import cache
+from django.http import HttpResponse, HttpResponseRedirect
+from django.shortcuts import render_to_response
+from django.template import RequestContext
+from django.utils.http import urlquote_plus
+from django.utils.translation import ugettext_lazy as _
+from django.views.decorators.cache import never_cache
+
+from django.conf import settings
+from ajaxable.utils import AjaxableFormView
+from catalogue.models import Book
+
+
+def main_page(request):
+    last_published = Book.objects.exclude(html_file='').order_by('-created_at')[:4]
+
+    return render_to_response("main_page.html", locals(),
+        context_instance=RequestContext(request))
+
+
+class LoginFormView(AjaxableFormView):
+    form_class = AuthenticationForm
+    title = _('Sign in')
+    submit = _('Sign in')
+    ajax_redirect = True
+
+    def __call__(self, request):
+        if request.user.is_authenticated():
+            return HttpResponseRedirect('/')
+        return super(LoginFormView, self).__call__(request)
+
+    def success(self, form, request):
+        auth.login(request, form.get_user())
+
+
+class RegisterFormView(AjaxableFormView):
+    form_class = UserCreationForm
+    title = _('Register')
+    submit = _('Register')
+    ajax_redirect = True
+
+    def __call__(self, request):
+        if request.user.is_authenticated():
+            return HttpResponseRedirect('/')
+        return super(RegisterFormView, self).__call__(request)
+
+    def success(self, form, request):
+        user = form.save()
+        user = auth.authenticate(
+            username=form.cleaned_data['username'],
+            password=form.cleaned_data['password1']
+        )
+        auth.login(request, user)
+
+
+@never_cache
+def logout_then_redirect(request):
+    auth.logout(request)
+    return HttpResponseRedirect(urlquote_plus(request.GET.get('next', '/'), safe='/?='))
+
+
+def clock(request):
+    """ Provides server time for jquery.countdown,
+    in a format suitable for Date.parse()
+    """
+    return HttpResponse(datetime.now().strftime('%Y/%m/%d %H:%M:%S'))
+
+
+def publish_plan(request):
+    cache_key = "publish_plan"
+    plan = cache.get(cache_key)
+
+    if plan is None:
+        plan = []
+        try:
+            feed = feedparser.parse(settings.PUBLISH_PLAN_FEED)
+        except:
+            pass
+        else:
+            for i in range(len(feed['entries'])):
+                print i
+                plan.append({
+                    'title': feed['entries'][i].title,
+                    'link': feed['entries'][i].link,
+                    })
+        cache.set(cache_key, plan, 1800)
+
+    return render_to_response("publish_plan.html", {'plan': plan},
+        context_instance=RequestContext(request))