Merge with master.
authorRadek Czajka <radekczajka@nowoczesnapolska.org.pl>
Wed, 26 Mar 2014 15:32:58 +0000 (16:32 +0100)
committerRadek Czajka <radekczajka@nowoczesnapolska.org.pl>
Wed, 26 Mar 2014 15:32:58 +0000 (16:32 +0100)
95 files changed:
apps/apiclient/__init__.py
apps/catalogue/admin.py
apps/catalogue/forms.py
apps/catalogue/locale/pl/LC_MESSAGES/django.mo
apps/catalogue/locale/pl/LC_MESSAGES/django.po
apps/catalogue/migrations/0012_auto__add_imagepublishrecord__add_imagechange__add_unique_imagechange_.py [new file with mode: 0644]
apps/catalogue/models/__init__.py
apps/catalogue/models/image.py [new file with mode: 0755]
apps/catalogue/models/listeners.py
apps/catalogue/models/publish_log.py
apps/catalogue/templates/catalogue/activity.html
apps/catalogue/templates/catalogue/base.html
apps/catalogue/templates/catalogue/book_append_to.html
apps/catalogue/templates/catalogue/book_detail.html
apps/catalogue/templates/catalogue/book_edit.html
apps/catalogue/templates/catalogue/book_html.html [new file with mode: 0755]
apps/catalogue/templates/catalogue/book_list/book_list.html
apps/catalogue/templates/catalogue/chunk_add.html
apps/catalogue/templates/catalogue/chunk_edit.html
apps/catalogue/templates/catalogue/document_create_missing.html
apps/catalogue/templates/catalogue/document_list.html
apps/catalogue/templates/catalogue/document_upload.html
apps/catalogue/templates/catalogue/image_detail.html [new file with mode: 0755]
apps/catalogue/templates/catalogue/image_list.html [new file with mode: 0755]
apps/catalogue/templates/catalogue/image_short.html [new file with mode: 0755]
apps/catalogue/templates/catalogue/image_table.html [new file with mode: 0755]
apps/catalogue/templates/catalogue/my_page.html
apps/catalogue/templates/catalogue/upload_pdf.html [new file with mode: 0755]
apps/catalogue/templates/catalogue/user_list.html
apps/catalogue/templates/catalogue/user_page.html
apps/catalogue/templates/catalogue/wall.html
apps/catalogue/templatetags/book_list.py
apps/catalogue/templatetags/catalogue.py
apps/catalogue/urls.py
apps/catalogue/views.py
apps/dvcs/locale/pl/LC_MESSAGES/django.mo
apps/dvcs/locale/pl/LC_MESSAGES/django.po
apps/dvcs/models.py
apps/wiki/templates/wiki/document_details_base.html
apps/wiki/templates/wiki/revert_dialog.html
apps/wiki/templates/wiki/save_dialog.html
apps/wiki/templates/wiki/tabs/annotations_view.html
apps/wiki/templates/wiki/tabs/annotations_view_item.html
apps/wiki/templates/wiki/tabs/gallery_view.html
apps/wiki/templates/wiki/tabs/gallery_view_item.html
apps/wiki/templates/wiki/tabs/history_view_item.html
apps/wiki/templates/wiki/tabs/source_editor_item.html
apps/wiki/templates/wiki/tabs/summary_view.html
apps/wiki/templates/wiki/tabs/summary_view_item.html
apps/wiki/templates/wiki/tabs/wysiwyg_editor_item.html
apps/wiki_img/__init__.py [new file with mode: 0644]
apps/wiki_img/forms.py [new file with mode: 0644]
apps/wiki_img/locale/pl/LC_MESSAGES/django.mo [new file with mode: 0644]
apps/wiki_img/locale/pl/LC_MESSAGES/django.po [new file with mode: 0644]
apps/wiki_img/models.py [new file with mode: 0644]
apps/wiki_img/templates/wiki_img/diff_table.html [new file with mode: 0644]
apps/wiki_img/templates/wiki_img/document_details.html [new file with mode: 0644]
apps/wiki_img/templates/wiki_img/document_details_base.html [new file with mode: 0644]
apps/wiki_img/templates/wiki_img/document_details_readonly.html [new file with mode: 0644]
apps/wiki_img/templates/wiki_img/tabs/history_view.html [new file with mode: 0755]
apps/wiki_img/templates/wiki_img/tabs/motifs_editor.html [new file with mode: 0644]
apps/wiki_img/templates/wiki_img/tabs/motifs_editor_item.html [new file with mode: 0644]
apps/wiki_img/templates/wiki_img/tabs/objects_editor.html [new file with mode: 0644]
apps/wiki_img/templates/wiki_img/tabs/objects_editor_item.html [new file with mode: 0644]
apps/wiki_img/templates/wiki_img/tabs/source_editor.html [new file with mode: 0644]
apps/wiki_img/templates/wiki_img/tabs/source_editor_item.html [new file with mode: 0644]
apps/wiki_img/templates/wiki_img/tabs/summary_view.html [new file with mode: 0644]
apps/wiki_img/templates/wiki_img/tabs/summary_view_item.html [new file with mode: 0644]
apps/wiki_img/tests.py [new file with mode: 0644]
apps/wiki_img/urls.py [new file with mode: 0644]
apps/wiki_img/views.py [new file with mode: 0644]
lib/librarian
redakcja/settings/common.py
redakcja/settings/compress.py
redakcja/static/css/filelist.css
redakcja/static/css/imgareaselect-default.css [new file with mode: 0644]
redakcja/static/css/master.css
redakcja/static/img/jquery.imgareaselect/border-anim-h.gif [new file with mode: 0644]
redakcja/static/img/jquery.imgareaselect/border-anim-v.gif [new file with mode: 0644]
redakcja/static/img/jquery.imgareaselect/border-h.gif [new file with mode: 0644]
redakcja/static/img/jquery.imgareaselect/border-v.gif [new file with mode: 0644]
redakcja/static/js/lib/jquery/jquery.imgareaselect.js [new file with mode: 0644]
redakcja/static/js/wiki/loader.js
redakcja/static/js/wiki/loader_readonly.js
redakcja/static/js/wiki/view_editor_source.js
redakcja/static/js/wiki_img/base.js [new file with mode: 0644]
redakcja/static/js/wiki_img/loader.js [new file with mode: 0644]
redakcja/static/js/wiki_img/loader_readonly.js [new file with mode: 0755]
redakcja/static/js/wiki_img/view_editor_motifs.js [new file with mode: 0644]
redakcja/static/js/wiki_img/view_editor_objects.js [new file with mode: 0644]
redakcja/static/js/wiki_img/wikiapi.js [new file with mode: 0644]
redakcja/templates/base.html
redakcja/urls.py
scripts/image.xml [new file with mode: 0644]
scripts/import_image.py [new file with mode: 0755]

index 376b66e..33d2008 100644 (file)
@@ -46,5 +46,5 @@ def api_call(user, path, data=None):
     elif status == '401':
         raise ApiError('User not authorized for publishing.')
     else:
-        raise ApiError("WL API call error [code %s]" % status)
+        raise ApiError("WL API call error %s, path: %s" % (status, path))
 
index 7fbacad..53e8a25 100644 (file)
@@ -12,5 +12,7 @@ class BookAdmin(admin.ModelAdmin):
 admin.site.register(models.Project)
 admin.site.register(models.Book, BookAdmin)
 admin.site.register(models.Chunk)
-
 admin.site.register(models.Chunk.tag_model)
+
+admin.site.register(models.Image)
+admin.site.register(models.Image.tag_model)
index 7ae7ff4..83b1652 100644 (file)
@@ -9,7 +9,7 @@ from django import forms
 from django.utils.translation import ugettext_lazy as _
 
 from catalogue.constants import MASTERS
-from catalogue.models import Book, Chunk
+from catalogue.models import Book, Chunk, Image
 
 class DocumentCreateForm(forms.ModelForm):
     """
@@ -145,7 +145,7 @@ class ReadonlyBookForm(BookForm):
     def __init__(self, *args, **kwargs):
         ret = super(ReadonlyBookForm, self).__init__(*args, **kwargs)
         for field in self.fields.values():
-            field.widget.attrs.update({"readonly": True})
+            field.widget.attrs.update({"disabled": "disabled"})
         return ret
 
 
@@ -155,3 +155,30 @@ class ChooseMasterForm(forms.Form):
     """
 
     master = forms.ChoiceField(choices=((m, m) for m in MASTERS))
+
+
+class ImageForm(forms.ModelForm):
+    """Form used for editing an Image."""
+    user = forms.ModelChoiceField(queryset=
+        User.objects.annotate(count=Count('chunk')).
+        order_by('-count', 'last_name', 'first_name'), required=False,
+        label=_('Assigned to')) 
+
+    class Meta:
+        model = Image
+        fields = ['title', 'slug', 'user', 'stage']
+
+    def __init__(self, *args, **kwargs):
+        super(ImageForm, self).__init__(*args, **kwargs)
+        self.fields['slug'].widget.attrs={'class': 'autoslug'}
+        self.fields['title'].widget.attrs={'class': 'autoslug-source'}
+
+
+class ReadonlyImageForm(ImageForm):
+    """Form used for not editing a Book."""
+
+    def __init__(self, *args, **kwargs):
+        ret = super(ReadonlyImageForm, self).__init__(*args, **kwargs)
+        for field in self.fields.values():
+            field.widget.attrs.update({"disabled": "disabled"})
+        return ret
index b6a12e3..64f78b4 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 6790400..d98440f 100644 (file)
@@ -7,8 +7,8 @@ msgid ""
 msgstr ""
 "Project-Id-Version: Platforma Redakcyjna\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2013-07-16 13:22+0200\n"
-"PO-Revision-Date: 2013-07-16 13:22+0100\n"
+"POT-Creation-Date: 2014-03-26 16:13+0100\n"
+"PO-Revision-Date: 2014-03-26 16:14+0100\n"
 "Last-Translator: Radek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>\n"
 "Language-Team: Fundacja Nowoczesna Polska <fundacja@nowoczesnapolska.org."
 "pl>\n"
@@ -36,7 +36,7 @@ msgstr "Plik ZIP"
 msgid "Directories are documents in chunks"
 msgstr "Katalogi zawierają dokumenty w częściach"
 
-#: forms.py:76
+#: forms.py:76 forms.py:165
 msgid "Assigned to"
 msgstr "Przypisane do"
 
@@ -48,36 +48,36 @@ msgstr "Część z tym slugiem już istnieje"
 msgid "Append to"
 msgstr "Dołącz do"
 
-#: views.py:160
+#: views.py:166
 #, python-format
 msgid "Slug already used for %s"
 msgstr "Slug taki sam jak dla pliku %s"
 
-#: views.py:162
+#: views.py:168
 msgid "Slug already used in repository."
 msgstr "Dokument o tym slugu już istnieje w repozytorium."
 
-#: views.py:168
+#: views.py:174
 msgid "File should be UTF-8 encoded."
 msgstr "Plik powinien mieć kodowanie UTF-8."
 
-#: views.py:498 models/book.py:56
+#: views.py:552 models/book.py:56
 msgid "books"
 msgstr "książki"
 
-#: views.py:500
+#: views.py:554
 msgid "scan gallery"
 msgstr "galeria skanów"
 
-#: models/book.py:28 models/chunk.py:23
+#: models/book.py:28 models/chunk.py:23 models/image.py:21
 msgid "title"
 msgstr "tytuł"
 
-#: models/book.py:29 models/chunk.py:24
+#: models/book.py:29 models/chunk.py:24 models/image.py:22
 msgid "slug"
 msgstr "slug"
 
-#: models/book.py:30
+#: models/book.py:30 models/image.py:23
 msgid "public"
 msgstr "publiczna"
 
@@ -105,19 +105,19 @@ msgstr "Książka nie ma części."
 msgid "Not all chunks have publishable revisions."
 msgstr "Niektóre części nie są gotowe do publikacji."
 
-#: models/book.py:266
+#: models/book.py:266 models/image.py:80
 msgid "Invalid XML"
 msgstr "Nieprawidłowy XML"
 
-#: models/book.py:268
+#: models/book.py:268 models/image.py:82
 msgid "No Dublin Core found."
 msgstr "Brak sekcji Dublin Core."
 
-#: models/book.py:270
+#: models/book.py:270 models/image.py:84
 msgid "Invalid Dublin Core"
 msgstr "Nieprawidłowy Dublin Core"
 
-#: models/book.py:273
+#: models/book.py:273 models/image.py:88
 msgid "rdf:about is not"
 msgstr "rdf:about jest różny od"
 
@@ -137,6 +137,18 @@ msgstr "część"
 msgid "chunks"
 msgstr "części"
 
+#: models/image.py:20 models/image.py:34 models/publish_log.py:45
+msgid "image"
+msgstr "obraz"
+
+#: models/image.py:35
+msgid "images"
+msgstr "obrazy"
+
+#: models/image.py:73
+msgid "There is no publishable revision"
+msgstr "Żadna wersja nie została oznaczona do publikacji."
+
 #: models/project.py:13
 msgid "name"
 msgstr "nazwa"
@@ -145,7 +157,7 @@ msgstr "nazwa"
 msgid "notes"
 msgstr "notatki"
 
-#: models/project.py:19 templates/catalogue/book_list/book_list.html:62
+#: models/project.py:19 templates/catalogue/book_list/book_list.html:64
 msgid "project"
 msgstr "projekt"
 
@@ -153,11 +165,12 @@ msgstr "projekt"
 msgid "projects"
 msgstr "projekty"
 
-#: models/publish_log.py:18
+#: models/publish_log.py:18 models/publish_log.py:46
 msgid "time"
 msgstr "czas"
 
-#: models/publish_log.py:19 templates/catalogue/wall.html:18
+#: models/publish_log.py:19 models/publish_log.py:47
+#: templates/catalogue/wall.html:19
 msgid "user"
 msgstr "użytkownik"
 
@@ -169,7 +182,7 @@ msgstr "zapis publikacji książki"
 msgid "book publish records"
 msgstr "zapisy publikacji książek"
 
-#: models/publish_log.py:34
+#: models/publish_log.py:34 models/publish_log.py:48
 msgid "change"
 msgstr "zmiana"
 
@@ -181,7 +194,16 @@ msgstr "zapis publikacji części"
 msgid "chunk publish records"
 msgstr "zapisy publikacji części"
 
-#: templates/catalogue/activity.html:9 templatetags/catalogue.py:29
+#: models/publish_log.py:53
+msgid "image publish record"
+msgstr "zapis publikacji obrazu"
+
+#: models/publish_log.py:54
+msgid "image publish records"
+msgstr "zapisy publikacji obrazów"
+
+#: templates/catalogue/activity.html:6 templates/catalogue/activity.html:12
+#: templatetags/catalogue.py:29
 msgid "Activity"
 msgstr "Aktywność"
 
@@ -189,118 +211,145 @@ msgstr "Aktywność"
 msgid "Platforma Redakcyjna"
 msgstr "Platforma Redakcyjna"
 
-#: templates/catalogue/book_append_to.html:9
+#: templates/catalogue/book_append_to.html:4
+#: templates/catalogue/book_append_to.html:11
 msgid "Append book"
 msgstr "Dołącz książkę"
 
-#: templates/catalogue/book_detail.html:14
-#: templates/catalogue/book_edit.html:9 templates/catalogue/chunk_edit.html:12
+#: templates/catalogue/book_detail.html:18
+#: templates/catalogue/book_edit.html:13
+#: templates/catalogue/chunk_edit.html:16
+#: templates/catalogue/image_detail.html:18
 msgid "Save"
 msgstr "Zapisz"
 
-#: templates/catalogue/book_detail.html:21
+#: templates/catalogue/book_detail.html:25
 msgid "Edit gallery"
 msgstr "Edytuj galerię"
 
-#: templates/catalogue/book_detail.html:24
+#: templates/catalogue/book_detail.html:28
 msgid "Append to other book"
 msgstr "Dołącz do innej książki"
 
-#: templates/catalogue/book_detail.html:30
+#: templates/catalogue/book_detail.html:34
 msgid "Chunks"
 msgstr "Części"
 
-#: templates/catalogue/book_detail.html:45 templatetags/wall.py:78
+#: templates/catalogue/book_detail.html:49
+#: templates/catalogue/image_detail.html:36 templatetags/wall.py:78
 msgid "Publication"
 msgstr "Publikacja"
 
-#: templates/catalogue/book_detail.html:54
+#: templates/catalogue/book_detail.html:58
+#: templates/catalogue/image_detail.html:38
 msgid "Last published"
 msgstr "Ostatnio opublikowano"
 
-#: templates/catalogue/book_detail.html:64
+#: templates/catalogue/book_detail.html:68
 msgid "Full XML"
 msgstr "Pełny XML"
 
-#: templates/catalogue/book_detail.html:65
+#: templates/catalogue/book_detail.html:69
 msgid "HTML version"
 msgstr "Wersja HTML"
 
-#: templates/catalogue/book_detail.html:66
+#: templates/catalogue/book_detail.html:70
 msgid "TXT version"
 msgstr "Wersja TXT"
 
-#: templates/catalogue/book_detail.html:67
+#: templates/catalogue/book_detail.html:71
 msgid "PDF version"
 msgstr "Wersja PDF"
 
-#: templates/catalogue/book_detail.html:68
+#: templates/catalogue/book_detail.html:72
 msgid "EPUB version"
 msgstr "Wersja EPUB"
 
-#: templates/catalogue/book_detail.html:81
+#: templates/catalogue/book_detail.html:85
+#: templates/catalogue/image_detail.html:57
 msgid "Publish"
 msgstr "Opublikuj"
 
-#: templates/catalogue/book_detail.html:85
+#: templates/catalogue/book_detail.html:89
+#: templates/catalogue/image_detail.html:61
 msgid "Log in to publish."
 msgstr "Zaloguj się, aby opublikować."
 
-#: templates/catalogue/book_detail.html:88
+#: templates/catalogue/book_detail.html:92
+#: templates/catalogue/image_detail.html:64
 msgid "This book can't be published yet, because:"
 msgstr "Ta książka nie może jeszcze zostać opublikowana. Powód:"
 
-#: templates/catalogue/book_detail.html:98
+#: templates/catalogue/book_detail.html:102
+#: templates/catalogue/image_detail.html:72
 msgid "Comments"
 msgstr "Komentarze"
 
-#: templates/catalogue/book_text.html:7
-msgid "Redakcja"
-msgstr ""
+#: templates/catalogue/book_edit.html:5
+msgid "Edit book"
+msgstr "Edytuj książkę"
 
-#: templates/catalogue/book_text.html:15
+#: templates/catalogue/book_html.html:13 templates/catalogue/book_text.html:15
 msgid "Table of contents"
 msgstr "Spis treści"
 
-#: templates/catalogue/book_text.html:17
+#: templates/catalogue/book_html.html:14 templates/catalogue/book_text.html:17
 msgid "Edit. note"
 msgstr "Nota red."
 
-#: templates/catalogue/chunk_add.html:5 templates/catalogue/chunk_edit.html:18
+#: templates/catalogue/book_html.html:15
+msgid "Infobox"
+msgstr "Informacje"
+
+#: templates/catalogue/book_text.html:7
+msgid "Redakcja"
+msgstr ""
+
+#: templates/catalogue/chunk_add.html:5 templates/catalogue/chunk_add.html:9
+#: templates/catalogue/chunk_edit.html:22
 msgid "Split chunk"
 msgstr "Podziel część"
 
-#: templates/catalogue/chunk_add.html:10
+#: templates/catalogue/chunk_add.html:14
 msgid "Insert empty chunk after"
 msgstr "Wstaw pustą część po"
 
-#: templates/catalogue/chunk_add.html:13
+#: templates/catalogue/chunk_add.html:17
 msgid "Add chunk"
 msgstr "Dodaj część"
 
-#: templates/catalogue/chunk_edit.html:5
+#: templates/catalogue/chunk_edit.html:5 templates/catalogue/chunk_edit.html:9
 #: templates/catalogue/book_list/book.html:8
 #: templates/catalogue/book_list/chunk.html:6
 msgid "Chunk settings"
 msgstr "Ustawienia części"
 
-#: templates/catalogue/chunk_edit.html:10
+#: templates/catalogue/chunk_edit.html:14
 msgid "Book"
 msgstr "Książka"
 
 #: templates/catalogue/document_create_missing.html:5
+#: templates/catalogue/document_create_missing.html:9
 msgid "Create a new book"
 msgstr "Utwórz nową książkę"
 
-#: templates/catalogue/document_create_missing.html:11
+#: templates/catalogue/document_create_missing.html:15
 msgid "Create book"
 msgstr "Utwórz książkę"
 
-#: templates/catalogue/document_upload.html:8
-msgid "Bulk documents upload"
+#: templates/catalogue/document_list.html:7
+msgid "Book list"
+msgstr "Lista książek"
+
+#: templates/catalogue/document_upload.html:5
+msgid "Bulk document upload"
 msgstr "Hurtowe dodawanie dokumentów"
 
 #: templates/catalogue/document_upload.html:11
+msgid "Bulk documents upload"
+msgstr "Hurtowe dodawanie dokumentów"
+
+#: templates/catalogue/document_upload.html:14
 msgid ""
 "Please submit a ZIP with UTF-8 encoded XML files. Files not ending with "
 "<code>.xml</code> will be ignored."
@@ -308,91 +357,136 @@ msgstr ""
 "Proszę wskazać archiwum ZIP z plikami XML w kodowaniu UTF-8. Pliki nie "
 "kończące się na <code>.xml</code> zostaną zignorowane."
 
-#: templates/catalogue/document_upload.html:17 templatetags/catalogue.py:35
+#: templates/catalogue/document_upload.html:20
+#: templates/catalogue/upload_pdf.html:16 templatetags/catalogue.py:36
 msgid "Upload"
 msgstr "Załaduj"
 
-#: templates/catalogue/document_upload.html:24
+#: templates/catalogue/document_upload.html:27
 msgid ""
 "There have been some errors. No files have been added to the repository."
 msgstr "Wystąpiły błędy. Żadne pliki nie zostały dodane do repozytorium."
 
-#: templates/catalogue/document_upload.html:25
+#: templates/catalogue/document_upload.html:28
 msgid "Offending files"
 msgstr "Błędne pliki"
 
-#: templates/catalogue/document_upload.html:33
+#: templates/catalogue/document_upload.html:36
 msgid "Correct files"
 msgstr "Poprawne pliki"
 
-#: templates/catalogue/document_upload.html:44
+#: templates/catalogue/document_upload.html:47
 msgid "Files have been successfully uploaded to the repository."
 msgstr "Pliki zostały dodane do repozytorium."
 
-#: templates/catalogue/document_upload.html:45
+#: templates/catalogue/document_upload.html:48
 msgid "Uploaded files"
 msgstr "Dodane pliki"
 
-#: templates/catalogue/document_upload.html:55
+#: templates/catalogue/document_upload.html:58
 msgid "Skipped files"
 msgstr "Pominięte pliki"
 
-#: templates/catalogue/document_upload.html:56
+#: templates/catalogue/document_upload.html:59
 msgid "Files skipped due to no <code>.xml</code> extension"
 msgstr "Pliki pominięte z powodu braku rozszerzenia <code>.xml</code>."
 
-#: templates/catalogue/my_page.html:21
-msgid "Your last edited documents"
-msgstr "Twoje ostatnie edycje"
+#: templates/catalogue/image_detail.html:26
+msgid "Editor"
+msgstr "Edytor"
 
-#: templates/catalogue/my_page.html:30 templates/catalogue/user_page.html:13
-msgid "Recent activity for"
-msgstr "Ostatnia aktywność dla:"
+#: templates/catalogue/image_detail.html:28
+msgid "Proceed to the editor."
+msgstr "Przejdź do edytora."
 
-#: templates/catalogue/user_list.html:7 templatetags/catalogue.py:31
-msgid "Users"
-msgstr "Użytkownicy"
-
-#: templates/catalogue/wall.html:28
-msgid "not logged in"
-msgstr "nie zalogowany"
+#: templates/catalogue/image_list.html:7
+msgid "Image list"
+msgstr "Lista obrazów"
 
-#: templates/catalogue/wall.html:33
-msgid "No activity recorded."
-msgstr "Nie zanotowano aktywności."
-
-#: templates/catalogue/book_list/book.html:7
-#: templates/catalogue/book_list/book.html:28
-msgid "Book settings"
-msgstr "Ustawienia książki"
-
-#: templates/catalogue/book_list/book_list.html:22
-msgid "Show hidden books"
-msgstr "Pokaż ukryte książki"
+#: templates/catalogue/image_short.html:4
+msgid "Image settings"
+msgstr "Ustawienia obrazu"
 
+#: templates/catalogue/image_table.html:19
 #: templates/catalogue/book_list/book_list.html:27
 msgid "Search in book titles"
 msgstr "Szukaj w tytułach książek"
 
+#: templates/catalogue/image_table.html:24
 #: templates/catalogue/book_list/book_list.html:32
 msgid "stage"
 msgstr "etap"
 
+#: templates/catalogue/image_table.html:26
+#: templates/catalogue/image_table.html:37
 #: templates/catalogue/book_list/book_list.html:34
 #: templates/catalogue/book_list/book_list.html:45
-#: templates/catalogue/book_list/book_list.html:64
+#: templates/catalogue/book_list/book_list.html:66
 msgid "none"
 msgstr "brak"
 
+#: templates/catalogue/image_table.html:35
 #: templates/catalogue/book_list/book_list.html:43
 msgid "editor"
 msgstr "redaktor"
 
-#: templates/catalogue/book_list/book_list.html:54
+#: templates/catalogue/image_table.html:46
+#: templates/catalogue/book_list/book_list.html:56
 msgid "status"
 msgstr "status"
 
-#: templates/catalogue/book_list/book_list.html:88
+#: templates/catalogue/image_table.html:63
+#, python-format
+msgid "%(c)s image"
+msgid_plural "%(c)s images"
+msgstr[0] "%(c)s obraz"
+msgstr[1] "%(c)s obrazy"
+msgstr[2] "%(c)s obrazów"
+
+#: templates/catalogue/image_table.html:68
+msgid "No images found."
+msgstr "Nie znaleziono obrazów."
+
+#: templates/catalogue/my_page.html:15 templatetags/catalogue.py:27
+msgid "My page"
+msgstr "Moja strona"
+
+#: templates/catalogue/my_page.html:24
+msgid "Your last edited documents"
+msgstr "Twoje ostatnie edycje"
+
+#: templates/catalogue/my_page.html:33 templates/catalogue/user_page.html:16
+msgid "Recent activity for"
+msgstr "Ostatnia aktywność dla:"
+
+#: templates/catalogue/upload_pdf.html:5
+#: templates/catalogue/upload_pdf.html:11
+msgid "PDF file upload"
+msgstr "Ładowanie pliku PDF"
+
+#: templates/catalogue/user_list.html:6 templates/catalogue/user_list.html:11
+#: templatetags/catalogue.py:32
+msgid "Users"
+msgstr "Użytkownicy"
+
+#: templates/catalogue/wall.html:29
+msgid "not logged in"
+msgstr "nie zalogowany"
+
+#: templates/catalogue/wall.html:34
+msgid "No activity recorded."
+msgstr "Nie zanotowano aktywności."
+
+#: templates/catalogue/book_list/book.html:7
+#: templates/catalogue/book_list/book.html:28
+msgid "Book settings"
+msgstr "Ustawienia książki"
+
+#: templates/catalogue/book_list/book_list.html:22
+msgid "Show hidden books"
+msgstr "Pokaż ukryte książki"
+
+#: templates/catalogue/book_list/book_list.html:90
 #, python-format
 msgid "%(c)s book"
 msgid_plural "%(c)s books"
@@ -400,67 +494,67 @@ msgstr[0] "%(c)s książka"
 msgstr[1] "%(c)s książki"
 msgstr[2] "%(c)s książek"
 
-#: templates/catalogue/book_list/book_list.html:93
+#: templates/catalogue/book_list/book_list.html:95
 msgid "No books found."
 msgstr "Nie znaleziono książek."
 
-#: templates/catalogue/book_list/book_list.html:99
+#: templates/catalogue/book_list/book_list.html:101
 msgid "Set stage"
 msgstr "Ustaw etap"
 
-#: templates/catalogue/book_list/book_list.html:100
+#: templates/catalogue/book_list/book_list.html:102
 msgid "Set user"
 msgstr "Przypisz redaktora"
 
-#: templates/catalogue/book_list/book_list.html:102
+#: templates/catalogue/book_list/book_list.html:104
 msgid "Project"
 msgstr "Projekt"
 
-#: templates/catalogue/book_list/book_list.html:103
+#: templates/catalogue/book_list/book_list.html:105
 msgid "Mark publishable"
 msgstr "Oznacz do publikacji"
 
-#: templates/catalogue/book_list/book_list.html:104
+#: templates/catalogue/book_list/book_list.html:106
 msgid "Mark not publishable"
 msgstr "Odznacz do publikacji"
 
-#: templates/catalogue/book_list/book_list.html:105
+#: templates/catalogue/book_list/book_list.html:107
 msgid "Other user"
 msgstr "Inny użytkownik"
 
-#: templatetags/book_list.py:84
+#: templatetags/book_list.py:84 templatetags/book_list.py:152
 msgid "publishable"
 msgstr "do publikacji"
 
-#: templatetags/book_list.py:85
+#: templatetags/book_list.py:85 templatetags/book_list.py:153
 msgid "changed"
 msgstr "zmienione"
 
-#: templatetags/book_list.py:86
+#: templatetags/book_list.py:86 templatetags/book_list.py:154
 msgid "published"
 msgstr "opublikowane"
 
-#: templatetags/book_list.py:87
+#: templatetags/book_list.py:87 templatetags/book_list.py:155
 msgid "unpublished"
 msgstr "nie opublikowane"
 
-#: templatetags/book_list.py:88
+#: templatetags/book_list.py:88 templatetags/book_list.py:156
 msgid "empty"
 msgstr "puste"
 
-#: templatetags/catalogue.py:27
-msgid "My page"
-msgstr "Moja strona"
-
 #: templatetags/catalogue.py:30
 msgid "All"
 msgstr "Wszystkie"
 
-#: templatetags/catalogue.py:34
+#: templatetags/catalogue.py:31
+msgid "Images"
+msgstr "Obrazy"
+
+#: templatetags/catalogue.py:35
 msgid "Add"
 msgstr "Dodaj"
 
-#: templatetags/catalogue.py:37
+#: templatetags/catalogue.py:38
 msgid "Covers"
 msgstr "Okładki"
 
@@ -476,9 +570,6 @@ msgstr "Zmiana"
 msgid "Comment"
 msgstr "Komentarz"
 
-#~ msgid "Infobox"
-#~ msgstr "Informacje"
-
 #~ msgid "Admin"
 #~ msgstr "Administracja"
 
diff --git a/apps/catalogue/migrations/0012_auto__add_imagepublishrecord__add_imagechange__add_unique_imagechange_.py b/apps/catalogue/migrations/0012_auto__add_imagepublishrecord__add_imagechange__add_unique_imagechange_.py
new file mode 100644 (file)
index 0000000..599e103
--- /dev/null
@@ -0,0 +1,270 @@
+# -*- coding: 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 'ImagePublishRecord'
+        db.create_table(u'catalogue_imagepublishrecord', (
+            (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+            ('image', self.gf('django.db.models.fields.related.ForeignKey')(related_name='publish_log', to=orm['catalogue.Image'])),
+            ('timestamp', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
+            ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])),
+            ('change', self.gf('django.db.models.fields.related.ForeignKey')(related_name='publish_log', to=orm['catalogue.ImageChange'])),
+        ))
+        db.send_create_signal('catalogue', ['ImagePublishRecord'])
+
+        # Adding model 'ImageChange'
+        db.create_table(u'catalogue_imagechange', (
+            (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+            ('author', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, blank=True)),
+            ('author_name', self.gf('django.db.models.fields.CharField')(max_length=128, null=True, blank=True)),
+            ('author_email', self.gf('django.db.models.fields.CharField')(max_length=128, null=True, blank=True)),
+            ('revision', self.gf('django.db.models.fields.IntegerField')(db_index=True)),
+            ('parent', self.gf('django.db.models.fields.related.ForeignKey')(default=None, related_name='children', null=True, blank=True, to=orm['catalogue.ImageChange'])),
+            ('merge_parent', self.gf('django.db.models.fields.related.ForeignKey')(default=None, related_name='merge_children', null=True, blank=True, to=orm['catalogue.ImageChange'])),
+            ('description', self.gf('django.db.models.fields.TextField')(default='', blank=True)),
+            ('created_at', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now, db_index=True)),
+            ('publishable', self.gf('django.db.models.fields.BooleanField')(default=False)),
+            ('tree', self.gf('django.db.models.fields.related.ForeignKey')(related_name='change_set', to=orm['catalogue.Image'])),
+            ('data', self.gf('django.db.models.fields.files.FileField')(max_length=100)),
+        ))
+        db.send_create_signal('catalogue', ['ImageChange'])
+
+        # Adding M2M table for field tags on 'ImageChange'
+        db.create_table(u'catalogue_imagechange_tags', (
+            ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+            ('imagechange', models.ForeignKey(orm['catalogue.imagechange'], null=False)),
+            ('imagetag', models.ForeignKey(orm['catalogue.imagetag'], null=False))
+        ))
+        db.create_unique(u'catalogue_imagechange_tags', ['imagechange_id', 'imagetag_id'])
+
+        # Adding unique constraint on 'ImageChange', fields ['tree', 'revision']
+        db.create_unique(u'catalogue_imagechange', ['tree_id', 'revision'])
+
+        # Adding model 'ImageTag'
+        db.create_table(u'catalogue_imagetag', (
+            (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+            ('name', self.gf('django.db.models.fields.CharField')(max_length=64)),
+            ('slug', self.gf('django.db.models.fields.SlugField')(max_length=64, unique=True, null=True, blank=True)),
+            ('ordering', self.gf('django.db.models.fields.IntegerField')()),
+        ))
+        db.send_create_signal('catalogue', ['ImageTag'])
+
+        # Adding model 'Image'
+        db.create_table(u'catalogue_image', (
+            (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+            ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, blank=True)),
+            ('image', self.gf('django.db.models.fields.files.FileField')(max_length=100)),
+            ('title', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)),
+            ('slug', self.gf('django.db.models.fields.SlugField')(unique=True, max_length=50)),
+            ('public', self.gf('django.db.models.fields.BooleanField')(default=True, db_index=True)),
+            ('_short_html', self.gf('django.db.models.fields.TextField')(null=True, blank=True)),
+            ('_new_publishable', self.gf('django.db.models.fields.NullBooleanField')(null=True, blank=True)),
+            ('_published', self.gf('django.db.models.fields.NullBooleanField')(null=True, blank=True)),
+            ('_changed', self.gf('django.db.models.fields.NullBooleanField')(null=True, blank=True)),
+            ('stage', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.ImageTag'], null=True, blank=True)),
+            ('head', self.gf('django.db.models.fields.related.ForeignKey')(default=None, to=orm['catalogue.ImageChange'], null=True, blank=True)),
+            ('creator', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='created_image', null=True, to=orm['auth.User'])),
+        ))
+        db.send_create_signal('catalogue', ['Image'])
+
+
+    def backwards(self, orm):
+        # Removing unique constraint on 'ImageChange', fields ['tree', 'revision']
+        db.delete_unique(u'catalogue_imagechange', ['tree_id', 'revision'])
+
+        # Deleting model 'ImagePublishRecord'
+        db.delete_table(u'catalogue_imagepublishrecord')
+
+        # Deleting model 'ImageChange'
+        db.delete_table(u'catalogue_imagechange')
+
+        # Removing M2M table for field tags on 'ImageChange'
+        db.delete_table('catalogue_imagechange_tags')
+
+        # Deleting model 'ImageTag'
+        db.delete_table(u'catalogue_imagetag')
+
+        # Deleting model 'Image'
+        db.delete_table(u'catalogue_image')
+
+
+    models = {
+        u'auth.group': {
+            'Meta': {'object_name': 'Group'},
+            u'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': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        u'auth.permission': {
+            'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        u'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': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+            u'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': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+        },
+        'catalogue.book': {
+            'Meta': {'ordering': "['title', 'slug']", 'object_name': 'Book'},
+            '_new_publishable': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
+            '_on_track': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
+            '_published': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
+            '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            '_single': ('django.db.models.fields.NullBooleanField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
+            'dc_cover_image': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['cover.Image']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
+            'dc_slug': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '128', 'null': 'True', 'blank': 'True'}),
+            'gallery': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': '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', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
+            'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Project']", 'null': 'True', 'blank': 'True'}),
+            'public': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '128'}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'})
+        },
+        'catalogue.bookpublishrecord': {
+            'Meta': {'ordering': "['-timestamp']", 'object_name': 'BookPublishRecord'},
+            'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.Book']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"})
+        },
+        'catalogue.chunk': {
+            'Meta': {'ordering': "['number']", 'unique_together': "[['book', 'number'], ['book', 'slug']]", 'object_name': 'Chunk'},
+            '_changed': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
+            '_hidden': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
+            '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}),
+            'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'created_chunk'", 'null': 'True', 'to': u"orm['auth.User']"}),
+            'gallery_start': ('django.db.models.fields.IntegerField', [], {'default': '1', 'null': 'True', 'blank': 'True'}),
+            'head': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['catalogue.ChunkChange']", 'null': 'True', 'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'number': ('django.db.models.fields.IntegerField', [], {}),
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'}),
+            'stage': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ChunkTag']", 'null': 'True', 'blank': 'True'}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'})
+        },
+        'catalogue.chunkchange': {
+            'Meta': {'ordering': "('created_at',)", 'unique_together': "(['tree', 'revision'],)", 'object_name': 'ChunkChange'},
+            'author': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}),
+            'author_email': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
+            'author_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
+            'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}),
+            'data': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
+            'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'merge_parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'merge_children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}),
+            'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}),
+            'publishable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'revision': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
+            'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'change_set'", 'symmetrical': 'False', 'to': "orm['catalogue.ChunkTag']"}),
+            'tree': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'change_set'", 'to': "orm['catalogue.Chunk']"})
+        },
+        'catalogue.chunkpublishrecord': {
+            'Meta': {'object_name': 'ChunkPublishRecord'},
+            'book_record': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.BookPublishRecord']"}),
+            'change': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.ChunkChange']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'catalogue.chunktag': {
+            'Meta': {'ordering': "['ordering']", 'object_name': 'ChunkTag'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+            'ordering': ('django.db.models.fields.IntegerField', [], {}),
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'})
+        },
+        'catalogue.image': {
+            'Meta': {'ordering': "['title']", 'object_name': 'Image'},
+            '_changed': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
+            '_new_publishable': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
+            '_published': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
+            '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'created_image'", 'null': 'True', 'to': u"orm['auth.User']"}),
+            'head': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['catalogue.ImageChange']", 'null': 'True', 'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'image': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
+            'public': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50'}),
+            'stage': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ImageTag']", 'null': 'True', 'blank': 'True'}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'})
+        },
+        'catalogue.imagechange': {
+            'Meta': {'ordering': "('created_at',)", 'unique_together': "(['tree', 'revision'],)", 'object_name': 'ImageChange'},
+            'author': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}),
+            'author_email': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
+            'author_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
+            'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}),
+            'data': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
+            'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'merge_parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'merge_children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ImageChange']"}),
+            'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ImageChange']"}),
+            'publishable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'revision': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
+            'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'change_set'", 'symmetrical': 'False', 'to': "orm['catalogue.ImageTag']"}),
+            'tree': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'change_set'", 'to': "orm['catalogue.Image']"})
+        },
+        'catalogue.imagepublishrecord': {
+            'Meta': {'ordering': "['-timestamp']", 'object_name': 'ImagePublishRecord'},
+            'change': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.ImageChange']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'image': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.Image']"}),
+            'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"})
+        },
+        'catalogue.imagetag': {
+            'Meta': {'ordering': "['ordering']", 'object_name': 'ImageTag'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+            'ordering': ('django.db.models.fields.IntegerField', [], {}),
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'})
+        },
+        'catalogue.project': {
+            'Meta': {'ordering': "['name']", 'object_name': 'Project'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
+            'notes': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'})
+        },
+        u'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'}),
+            u'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'})
+        },
+        u'cover.image': {
+            'Meta': {'object_name': 'Image'},
+            'author': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'download_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'unique': 'True', 'null': 'True', 'blank': 'True'}),
+            'file': ('django.db.models.fields.files.ImageField', [], {'max_length': '100'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'license_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'license_url': ('django.db.models.fields.URLField', [], {'max_length': '255', 'blank': 'True'}),
+            'source_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+        }
+    }
+
+    complete_apps = ['catalogue']
\ No newline at end of file
index bd069f1..d0015c7 100755 (executable)
@@ -5,7 +5,9 @@
 #
 from catalogue.models.project import Project
 from catalogue.models.chunk import Chunk
-from catalogue.models.publish_log import BookPublishRecord, ChunkPublishRecord
+from catalogue.models.image import Image
+from catalogue.models.publish_log import (BookPublishRecord,
+    ChunkPublishRecord, ImagePublishRecord)
 from catalogue.models.book import Book
 from catalogue.models.listeners import *
 
diff --git a/apps/catalogue/models/image.py b/apps/catalogue/models/image.py
new file mode 100755 (executable)
index 0000000..7b5ce1f
--- /dev/null
@@ -0,0 +1,165 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
+from django.conf import settings
+from django.contrib.sites.models import Site
+from django.db import models
+from django.template.loader import render_to_string
+from django.utils.translation import ugettext_lazy as _
+from catalogue.helpers import cached_in_field
+from catalogue.tasks import refresh_instance
+from dvcs import models as dvcs_models
+
+
+class Image(dvcs_models.Document):
+    """ An editable chunk of text. Every Book text is divided into chunks. """
+    REPO_PATH = settings.CATALOGUE_IMAGE_REPO_PATH
+
+    image = models.FileField(_('image'), upload_to='catalogue/images')
+    title = models.CharField(_('title'), max_length=255, blank=True)
+    slug = models.SlugField(_('slug'), unique=True)
+    public = models.BooleanField(_('public'), default=True, db_index=True)
+
+    # cache
+    _short_html = models.TextField(null=True, blank=True, editable=False)
+    _new_publishable = models.NullBooleanField(editable=False)
+    _published = models.NullBooleanField(editable=False)
+    _changed = models.NullBooleanField(editable=False)
+
+    class Meta:
+        app_label = 'catalogue'
+        ordering = ['title']
+        verbose_name = _('image')
+        verbose_name_plural = _('images')
+        permissions = [('can_pubmark_image', 'Can mark images for publishing')]
+
+    # Representing
+    # ============
+
+    def __unicode__(self):
+        return self.title
+
+    @models.permalink
+    def get_absolute_url(self):
+        return ("catalogue_image", [self.slug])
+
+    def correct_about(self):
+        return "http://%s%s" % (
+            Site.objects.get_current().domain,
+            self.get_absolute_url()
+        )
+
+    # State & cache
+    # =============
+
+    def last_published(self):
+        try:
+            return self.publish_log.all()[0].timestamp
+        except IndexError:
+            return None
+
+    def assert_publishable(self):
+        from librarian.picture import WLPicture
+        from librarian import NoDublinCore, ParseError, ValidationError
+
+        class SelfImageStore(object):
+            def path(self_, slug, mime_type):
+                """Returns own file object. Ignores slug ad mime_type."""
+                return open(self.image.path)
+
+        publishable = self.publishable()
+        assert publishable, _("There is no publishable revision")
+        picture_xml = publishable.materialize()
+
+        try:
+            picture = WLPicture.from_string(picture_xml.encode('utf-8'),
+                    image_store=SelfImageStore)
+        except ParseError, e:
+            raise AssertionError(_('Invalid XML') + ': ' + str(e))
+        except NoDublinCore:
+            raise AssertionError(_('No Dublin Core found.'))
+        except ValidationError, e:
+            raise AssertionError(_('Invalid Dublin Core') + ': ' + str(e))
+
+        valid_about = self.correct_about()
+        assert picture.picture_info.about == valid_about, \
+                _("rdf:about is not") + " " + valid_about
+
+    def publishable_error(self):
+        try:
+            return self.assert_publishable()
+        except AssertionError, e:
+            return e
+        else:
+            return None
+
+    def accessible(self, request):
+        return self.public or request.user.is_authenticated()
+
+    def is_new_publishable(self):
+        change = self.publishable()
+        if not change:
+            return False
+        return not change.publish_log.exists()
+    new_publishable = cached_in_field('_new_publishable')(is_new_publishable)
+
+    def is_published(self):
+        return self.publish_log.exists()
+    published = cached_in_field('_published')(is_published)
+
+    def is_changed(self):
+        if self.head is None:
+            return False
+        return not self.head.publishable
+    changed = cached_in_field('_changed')(is_changed)
+
+    @cached_in_field('_short_html')
+    def short_html(self):
+        return render_to_string(
+                    'catalogue/image_short.html', {'image': self})
+
+    def refresh(self):
+        """This should be done offline."""
+        self.short_html
+        self.single
+        self.new_publishable
+        self.published
+
+    def touch(self):
+        update = {
+            "_changed": self.is_changed(),
+            "_short_html": None,
+            "_new_publishable": self.is_new_publishable(),
+            "_published": self.is_published(),
+        }
+        Image.objects.filter(pk=self.pk).update(**update)
+        refresh_instance(self)
+
+    def refresh(self):
+        """This should be done offline."""
+        self.changed
+        self.short_html
+
+
+    # Publishing
+    # ==========
+
+    def publish(self, user):
+        """Publishes the picture on behalf of a (local) user."""
+        from base64 import b64encode
+        import apiclient
+        from catalogue.signals import post_publish
+
+        self.assert_publishable()
+        change = self.publishable()
+        picture_xml = change.materialize()
+        picture_data = open(self.image.path).read()
+        apiclient.api_call(user, "pictures/", {
+                "picture_xml": picture_xml,
+                "picture_image_data": b64encode(picture_data),
+            })
+        # record the publish
+        log = self.publish_log.create(user=user, change=change)
+        post_publish.send(sender=log)
index 532f1e7..f98fba4 100755 (executable)
@@ -5,7 +5,8 @@
 #
 from django.contrib.auth.models import User
 from django.db import models
-from catalogue.models import Book, Chunk
+from catalogue.models import (Book, Chunk, Image, BookPublishRecord,
+        ImagePublishRecord)
 from catalogue.signals import post_publish
 from dvcs.signals import post_publishable
 
@@ -23,6 +24,11 @@ def chunk_changed(sender, instance, created, **kwargs):
 models.signals.post_save.connect(chunk_changed, sender=Chunk)
 
 
+def image_changed(sender, instance, created, **kwargs):
+    instance.touch()
+models.signals.post_save.connect(image_changed, sender=Image)
+
+
 def user_changed(sender, instance, *args, **kwargs):
     books = set()
     for c in instance.chunk_set.all():
@@ -34,16 +40,24 @@ models.signals.post_save.connect(user_changed, sender=User)
 
 
 def publish_listener(sender, *args, **kwargs):
-    sender.book.touch()
-    for c in sender.book:
-        c.touch()
+    if isinstance(sender, BookPublishRecord):
+        sender.book.touch()
+        for c in sender.book:
+            c.touch()
+    elif isinstance(sender, ImagePublishRecord):
+        sender.image.touch()
 post_publish.connect(publish_listener)
 
 
+def chunk_publishable_listener(sender, *args, **kwargs):
+    sender.tree.touch()
+    if isinstance(sender.tree, Chunk):
+        sender.tree.book.touch()
+post_publishable.connect(chunk_publishable_listener)
+
 def publishable_listener(sender, *args, **kwargs):
     sender.tree.touch()
-    sender.tree.book.touch()
-post_publishable.connect(publishable_listener)
+post_publishable.connect(publishable_listener, sender=Image)
 
 
 def listener_create(sender, instance, created, **kwargs):
index f422e37..6cc86d0 100755 (executable)
@@ -6,7 +6,7 @@
 from django.contrib.auth.models import User
 from django.db import models
 from django.utils.translation import ugettext_lazy as _
-from catalogue.models import Chunk
+from catalogue.models import Chunk, Image
 
 
 class BookPublishRecord(models.Model):
@@ -37,3 +37,18 @@ class ChunkPublishRecord(models.Model):
         app_label = 'catalogue'
         verbose_name = _('chunk publish record')
         verbose_name = _('chunk publish records')
+
+
+class ImagePublishRecord(models.Model):
+    """A record left after publishing an Image."""
+
+    image = models.ForeignKey(Image, verbose_name=_('image'), related_name='publish_log')
+    timestamp = models.DateTimeField(_('time'), auto_now_add=True)
+    user = models.ForeignKey(User, verbose_name=_('user'))
+    change = models.ForeignKey(Image.change_model, related_name='publish_log', verbose_name=_('change'))
+
+    class Meta:
+        app_label = 'catalogue'
+        ordering = ['-timestamp']
+        verbose_name = _('image publish record')
+        verbose_name = _('image publish records')
index c88b538..3bb8afb 100755 (executable)
@@ -3,6 +3,9 @@
 {% load wall %}
 
 
+{% block titleextra %}{% trans "Activity" %}{% endblock %}
+
+
 {% block content %}
 
 <h1><a href='{% url "catalogue_activity" prev_day.isoformat %}'>&lt;</a>
index d2af462..9a56d72 100644 (file)
@@ -2,10 +2,11 @@
 {% load catalogue %}
 <!DOCTYPE html>
 <html>
-<head>
+<head lang="{{ LANGUAGE_CODE }}">
     <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
     {% compressed_css 'catalogue' %}
-    <title>{% block title %}{% trans "Platforma Redakcyjna" %}{% endblock title %}</title>
+    <title>{% block title %}{% trans "Platforma Redakcyjna" %} ::
+        {% block titleextra %}{% endblock %}{% endblock title %}</title>
     {% block add_css %}{% endblock %}
 </head>
 <body>
@@ -13,7 +14,7 @@
 <div id="tabs-nav">
 
     <a href="{% url 'catalogue_document_list' %}">
-        <img id="logo" src="{{ STATIC_URL }}img/wl-orange.png" />
+        <img id="logo" src="{{ STATIC_URL }}img/wl-orange.png" alt="Platforma" />
     </a>
 
     <div id="tabs-nav-left">
index 76a5962..c1ecc29 100755 (executable)
@@ -1,6 +1,8 @@
 {% extends "catalogue/base.html" %}
 {% load i18n %}
 
+{% block titleextra %}{% trans "Append book" %}{% endblock %}
+
 {% block leftcolumn %}
        <form enctype="multipart/form-data" method="POST" action="">
     {% csrf_token %}
index eea6a6c..4db8a9b 100755 (executable)
@@ -1,6 +1,10 @@
 {% extends "catalogue/base.html" %}
 {% load book_list comments i18n %}
 
+
+{% block titleextra %}{{ book.title }}{% endblock %}
+
+
 {% block content %}
 
 
index 3fffa96..43fe0ea 100755 (executable)
@@ -1,6 +1,10 @@
 {% extends "catalogue/base.html" %}
 {% load i18n %}
 
+
+{% block titleextra %}{% trans "Edit book" %}{% endblock %}
+
+
 {% block leftcolumn %}
        <form enctype="multipart/form-data" method="POST" action="">
     {% csrf_token %}
diff --git a/apps/catalogue/templates/catalogue/book_html.html b/apps/catalogue/templates/catalogue/book_html.html
new file mode 100755 (executable)
index 0000000..af4cfa7
--- /dev/null
@@ -0,0 +1,30 @@
+{% load i18n %}
+{% load compressed %}
+<!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">
+    <head>
+        <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
+        <title>{{ book.title }}</title>
+    </head>
+    <body>
+        <div id="menu">
+            <ul>
+                <li><a href="#toc">{% trans "Table of contents" %}</a></li>
+                <li><a href="#nota_red">{% trans "Edit. note" %}</a></li>
+                <li><a href="#info">{% trans "Infobox" %}</a></li>
+            </ul>
+        </div>
+        <div id="info">
+            {#% book_info book %#}
+        </div>
+        <div id="header">
+            <div id="logo">
+                <a href="/"><img src="http://static.wolnelektury.pl/img/logo.png" alt="WolneLektury.pl - logo" /></a>
+            </div>
+        </div>
+
+        {{ html|safe }}
+
+    </body>
+</html>
index 90ae183..8e25436 100755 (executable)
@@ -2,7 +2,7 @@
 {% load pagination_tags %}
 
 
-<form name='filter' action=''>
+<form name='filter' action='{{ request.path }}'>
 <input type='hidden' name="title" value="{{ request.GET.title }}" />
 <input type='hidden' name="stage" value="{{ request.GET.stage }}" />
 {% if not viewed_user %}
@@ -48,6 +48,8 @@
                         {% endif %}value="{{ user.username }}">{{ user.first_name }} {{ user.last_name }} ({{ user.count }})</option>
                 {% endfor %}
             </select></th>
+        {% else %}
+            <th style='display: none'></th>
         {% endif %}
 
         <th><select name="status" class="filter">
@@ -83,7 +85,7 @@
             {% endif %}
         {% endwith %}
     {% endfor %}
-    <tr><th class='paginator' colspan="5">
+    <tr><th class='paginator' colspan="6">
         {% paginate %}
         {% blocktrans count c=cnt %}{{c}} book{% plural %}{{c}} books{% endblocktrans %}</th></tr>
     </tbody>
index b287479..f813b6f 100755 (executable)
@@ -1,6 +1,10 @@
 {% extends "catalogue/base.html" %}
 {% load i18n %}
 
+
+{% block titleextra %}{% trans "Split chunk" %}{% endblock %}
+
+
 {% block content %}
     <h1>{% trans "Split chunk" %}</h1>
 
index 3c1f3bf..2006226 100755 (executable)
@@ -1,6 +1,10 @@
 {% extends "catalogue/base.html" %}
 {% load i18n %}
 
+
+{% block titleextra %}{% trans "Chunk settings" %}{% endblock %}
+
+
 {% block content %}
     <h1>{% trans "Chunk settings" %}</h1>
 
index 47c99f9..aa2ce06 100644 (file)
@@ -1,6 +1,10 @@
 {% extends "catalogue/base.html" %}
 {% load i18n %}
 
+
+{% block titleextra %}{% trans "Create a new book" %}{% endblock %}
+
+
 {% block content %}
     <h1>{% trans "Create a new book" %}</h1>
 
index 294c629..f0bd50c 100644 (file)
@@ -4,6 +4,8 @@
 {% load catalogue book_list %}
 {% load compressed %}
 
+{% block titleextra %}{% trans "Book list" %}{% endblock %}
+
 
 {% block add_js %}
 {% compressed_js 'book_list' %}
index 1863774..009d154 100644 (file)
@@ -2,6 +2,9 @@
 {% load i18n %}
 
 
+{% block titleextra %}{% trans "Bulk document upload" %}{% endblock %}
+
+
 {% block leftcolumn %}
 
 
@@ -11,7 +14,7 @@
 {% trans "Please submit a ZIP with UTF-8 encoded XML files. Files not ending with <code>.xml</code> will be ignored." %}
 </p>
 
-<form enctype="multipart/form-data" method="POST" action="">
+<form enctype="multipart/form-data" method="POST" action="{{ request.path }}">
 {% csrf_token %}
 {{ form.as_p }}
 <p><button type="submit">{% trans "Upload" %}</button></p>
diff --git a/apps/catalogue/templates/catalogue/image_detail.html b/apps/catalogue/templates/catalogue/image_detail.html
new file mode 100755 (executable)
index 0000000..d791bfd
--- /dev/null
@@ -0,0 +1,80 @@
+{% extends "catalogue/base.html" %}
+{% load book_list comments i18n %}
+
+
+{% block titleextra %}{{ object.title }}{% endblock %}
+
+
+{% block content %}
+
+
+<h1>{{ object.title }}</h1>
+
+
+{% if editable %}<form method='POST'>{% csrf_token %}{% endif %}
+<table class='editable'><tbody>
+    {{ form.as_table }}
+    {% if editable %}
+        <tr><td></td><td><button type="submit">{% trans "Save" %}</button></td></tr>
+    {% endif %}
+</tbody></table>
+{% if editable %}</form>{% endif %}
+
+
+
+<div class='section'>
+    <h2>{% trans "Editor" %}</h2>
+
+    <p><a href="{% url 'wiki_img_editor' object.slug %}">{% trans "Proceed to the editor." %}</a></p>
+</div>
+
+
+
+<div class='section'>
+
+
+<h2>{% trans "Publication" %}</h2>
+
+<p>{% trans "Last published" %}: 
+    {% if object.last_published %}
+        {{ object.last_published }}
+    {% else %}
+        &mdash;
+    {% endif %}
+</p>
+
+{% if publishable %}
+    {% if user.is_authenticated %}
+        <!--
+        Angel photos:
+        Angels in Ely Cathedral (http://www.flickr.com/photos/21804434@N02/4483220595/) /
+        mira66 (http://www.flickr.com/photos/21804434@N02/) /
+        CC BY 2.0 (http://creativecommons.org/licenses/by/2.0/)
+        -->
+        <form method="POST" action="{% url 'catalogue_publish_image' object.slug %}">{% csrf_token %}
+            <!--img src="{{ STATIC_URL }}img/angel-left.png" style="vertical-align: middle" /-->
+            <button id="publish-button" type="submit">
+                <span>{% trans "Publish" %}</span></button>
+            <!--img src="{{ STATIC_URL }}img/angel-right.png" style="vertical-align: middle" /-->
+            </form>
+    {% else %}
+        <a href="{% url 'login' %}">{% trans "Log in to publish." %}</a>
+    {% endif %}
+{% else %}
+    <p>{% trans "This book can't be published yet, because:" %}</p>
+    <ul><li>{{ publishable_error }}</li></ul>
+{% endif %}
+
+</div>
+
+
+<div class='section'>
+    <h2>{% trans "Comments" %}</h2>
+
+    {% render_comment_list for object %}
+    {% with object.get_absolute_url as next %}
+        {% render_comment_form for object %}
+    {% endwith %}
+</div>
+
+{% endblock content %}
diff --git a/apps/catalogue/templates/catalogue/image_list.html b/apps/catalogue/templates/catalogue/image_list.html
new file mode 100755 (executable)
index 0000000..c6916e8
--- /dev/null
@@ -0,0 +1,13 @@
+{% extends "catalogue/base.html" %}
+
+{% load i18n %}
+{% load catalogue book_list %}
+
+
+{% block titleextra %}{% trans "Image list" %}{% endblock %}
+
+
+
+{% block content %}
+    {% image_list %}
+{% endblock content %}
diff --git a/apps/catalogue/templates/catalogue/image_short.html b/apps/catalogue/templates/catalogue/image_short.html
new file mode 100755 (executable)
index 0000000..e64733b
--- /dev/null
@@ -0,0 +1,18 @@
+{% load i18n %}
+
+<tr>
+    <td><a href="{% url 'catalogue_image' image.slug %}" title='{% trans "Image settings" %}'>[B]</a></td>
+    <td><a target="_blank"
+                href="{% url 'wiki_img_editor' image.slug %}">
+                {{ image.title }}</a></td>
+    <td>{% if image.stage %}
+        {{ image.stage }}
+    {% else %}–
+    {% endif %}</td>
+    <td class='user-column'>{% if image.user %}<a href="{% url 'catalogue_user' image.user.username %}">{{ image.user.first_name }} {{ image.user.last_name }}</a>{% endif %}</td>
+    <td>
+        {% if image.published %}P{% endif %}
+        {% if image.new_publishable %}p{% endif %}
+        {% if image.changed %}+{% endif %}
+    </td>
+</tr>
diff --git a/apps/catalogue/templates/catalogue/image_table.html b/apps/catalogue/templates/catalogue/image_table.html
new file mode 100755 (executable)
index 0000000..b152946
--- /dev/null
@@ -0,0 +1,69 @@
+{% load i18n %}
+{% load pagination_tags %}
+
+
+<form name='filter' action='{{ request.path }}'>
+<input type='hidden' name="title" value="{{ request.GET.title }}" />
+<input type='hidden' name="stage" value="{{ request.GET.stage }}" />
+{% if not viewed_user %}
+    <input type='hidden' name="user" value="{{ request.GET.user }}" />
+{% endif %}
+<input type='hidden' name="status" value="{{ request.GET.status }}" />
+</form>
+
+<table id="file-list"{% if viewed_user %} class="book-list-user"{% endif %}>
+    <thead><tr>
+        <th></th>
+        <th class='book-search-column'>
+            <form>
+            <input title='{% trans "Search in book titles" %}' name="title"
+                class='text-filter' value="{{ request.GET.title }}" />
+            </form>
+        </th>
+        <th><select name="stage" class="filter">
+            <option value=''>- {% trans "stage" %} -</option>
+            <option {% if request.GET.stage == '-' %}selected="selected"
+                    {% endif %}value="-">- {% trans "none" %} -</option>
+            {% for stage in stages %}
+                <option {% if request.GET.stage == stage.slug %}selected="selected"
+                    {% endif %}value="{{ stage.slug }}">{{ stage.name }}</option>
+            {% endfor %}
+        </select></th>
+
+        {% if not viewed_user %}
+            <th><select name="user" class="filter">
+                <option value=''>- {% trans "editor" %} -</option>
+                <option {% if request.GET.user == '-' %}selected="selected"
+                        {% endif %}value="-">- {% trans "none" %} -</option>
+                {% for user in users %}
+                    <option {% if request.GET.user == user.username %}selected="selected"
+                        {% endif %}value="{{ user.username }}">{{ user.first_name }} {{ user.last_name }} ({{ user.count }})</option>
+                {% endfor %}
+            </select></th>
+        {% endif %}
+
+        <th><select name="status" class="filter">
+            <option value=''>- {% trans "status" %} -</option>
+            {% for state, label in states %}
+                <option {% if request.GET.status == state %}selected="selected"
+                        {% endif %}value='{{ state }}'>{{ label }}</option>
+            {% endfor %}
+        </select></th>
+
+    </tr></thead>
+
+    {% with cnt=objects|length %}
+    {% autopaginate objects 100 %}
+    <tbody>
+    {% for item in objects %}
+        {{ item.short_html|safe }}
+    {% endfor %}
+    <tr><th class='paginator' colspan="5">
+        {% paginate %}
+        {% blocktrans count c=cnt %}{{c}} image{% plural %}{{c}} images{% endblocktrans %}</th></tr>
+    </tbody>
+    {% endwith %}
+</table>
+{% if not objects %}
+    <p>{% trans "No images found." %}</p>
+{% endif %}
index fd4e84e..c6b61ce 100755 (executable)
@@ -12,6 +12,9 @@
 {% compressed_css 'book_list' %}
 {% endblock %}
 
+{% block titleextra %}{% trans "My page" %}{% endblock %}
+
+
 {% block leftcolumn %}
     {% book_list request.user %}
 {% endblock leftcolumn %}
diff --git a/apps/catalogue/templates/catalogue/upload_pdf.html b/apps/catalogue/templates/catalogue/upload_pdf.html
new file mode 100755 (executable)
index 0000000..265b84a
--- /dev/null
@@ -0,0 +1,20 @@
+{% extends "catalogue/base.html" %}
+{% load i18n %}
+
+
+{% block titleextra %}{% trans "PDF file upload" %}{% endblock %}
+
+
+{% block content %}
+
+
+<h2>{% trans "PDF file upload" %}</h2>
+
+<form enctype="multipart/form-data" method="POST" action="">
+{% csrf_token %}
+{{ form.as_p }}
+<p><button type="submit">{% trans "Upload" %}</button></p>
+</form>
+
+
+{% endblock content %}
index c4b1dfc..2643405 100755 (executable)
@@ -2,6 +2,10 @@
 
 {% load i18n %}
 
+
+{% block titleextra %}{% trans "Users" %}{% endblock %}
+
+
 {% block leftcolumn %}
 
 <h1>{% trans "Users" %}</h1>
index 89b4ece..4be4ca3 100755 (executable)
@@ -4,6 +4,9 @@
 {% load catalogue book_list wall %}
 
 
+{% block titleextra %}{{ viewed_user|nice_name }}{% endblock %}
+
+
 {% block leftcolumn %}
     <h1>{{ viewed_user|nice_name }}</h1>
     {% book_list viewed_user %}
index 25550fe..d7f6c9e 100755 (executable)
@@ -7,7 +7,8 @@
     <li class="{{ item.tag }}{% if not item.user %} anonymous{% endif %}">
         <div class='gravatar'>
             {% if item.get_email %}
-                {% gravatar_img_for_email item.get_email 32 %}
+                <img src="{% gravatar_for_email item.get_email 32 %}"
+                    height="32" width="32" alt='Avatar' />
                 <br/>
             {% endif %}
         </div>
index 14149e8..1357c32 100755 (executable)
@@ -5,7 +5,7 @@ from django.db.models import Q, Count
 from django import template
 from django.utils.translation import ugettext_lazy as _
 from django.contrib.auth.models import User
-from catalogue.models import Chunk, Project
+from catalogue.models import Chunk, Image, Project
 
 register = template.Library()
 
@@ -145,3 +145,58 @@ def book_list(context, user=None):
     })
 
     return new_context
+
+
+
+_image_states = [
+        ('publishable', _('publishable'), Q(_new_publishable=True)),
+        ('changed', _('changed'), Q(_changed=True)),
+        ('published', _('published'), Q(_published=True)),
+        ('unpublished', _('unpublished'), Q(_published=False)),
+        ('empty', _('empty'), Q(head=None)),
+    ]
+_image_states_options = [s[:2] for s in _image_states]
+_image_states_dict = dict([(s[0], s[2]) for s in _image_states])
+
+def image_list_filter(request, **kwargs):
+
+    def arg_or_GET(field):
+        return kwargs.get(field, request.GET.get(field))
+
+    images = Image.objects.all()
+
+    if not request.user.is_authenticated():
+        images = images.filter(public=True)
+
+    state = arg_or_GET('status')
+    if state in _image_states_dict:
+        images = images.filter(_image_states_dict[state])
+
+    images = foreign_filter(images, arg_or_GET('user'), 'user', User, 'username')
+    images = foreign_filter(images, arg_or_GET('stage'), 'stage', Image.tag_model, 'slug')
+    images = search_filter(images, arg_or_GET('title'), ['title', 'title'])
+    return images
+
+
+@register.inclusion_tag('catalogue/image_table.html', takes_context=True)
+def image_list(context, user=None):
+    request = context['request']
+
+    if user:
+        filters = {"user": user}
+        new_context = {"viewed_user": user}
+    else:
+        filters = {}
+        new_context = {"users": User.objects.annotate(
+                count=Count('image')).filter(count__gt=0).order_by(
+                '-count', 'last_name', 'first_name')}
+
+    new_context.update({
+        "filters": True,
+        "request": request,
+        "objects": image_list_filter(request, **filters),
+        "stages": Image.tag_model.objects.all(),
+        "states": _image_states_options,
+    })
+
+    return new_context
index 8d5ff65..07c5cf9 100644 (file)
@@ -28,6 +28,7 @@ def main_tabs(context):
 
     tabs.append(Tab('activity', _('Activity'), reverse("catalogue_activity")))
     tabs.append(Tab('all', _('All'), reverse("catalogue_document_list")))
+    tabs.append(Tab('images', _('Images'), reverse("catalogue_image_list")))
     tabs.append(Tab('users', _('Users'), reverse("catalogue_users")))
 
     if user.has_perm('catalogue.add_book'):
index e72b88d..3627bf0 100644 (file)
@@ -9,6 +9,11 @@ from catalogue.views import GalleryView
 urlpatterns = patterns('catalogue.views',
     url(r'^$', RedirectView.as_view(url='catalogue/')),
 
+    url(r'^images/$', 'image_list', name='catalogue_image_list'),
+    url(r'^image/(?P<slug>[^/]+)/$', 'image', name="catalogue_image"),
+    url(r'^image/(?P<slug>[^/]+)/publish$', 'publish_image',
+            name="catalogue_publish_image"),
+
     url(r'^catalogue/$', 'document_list', name='catalogue_document_list'),
     url(r'^user/$', 'my', name='catalogue_user'),
     url(r'^user/(?P<username>[^/]+)/$', 'user', name='catalogue_user'),
@@ -26,7 +31,6 @@ urlpatterns = patterns('catalogue.views',
         'create_missing', name='catalogue_create_missing'),
 
     url(r'^book/(?P<slug>[^/]+)/publish$', 'publish', name="catalogue_publish"),
-    #url(r'^(?P<name>[^/]+)/publish/(?P<version>\d+)$', 'publish', name="catalogue_publish"),
 
     url(r'^book/(?P<slug>[^/]+)/$', 'book', name="catalogue_book"),
     url(r'^book/(?P<slug>[^/]+)/gallery/$',
index ebc3575..01e4d1f 100644 (file)
@@ -14,18 +14,18 @@ from django.db.models import Count, Q
 from django.db import transaction
 from django import http
 from django.http import Http404, HttpResponse, HttpResponseForbidden
-from django.shortcuts import get_object_or_404, render, render_to_response
+from django.shortcuts import get_object_or_404, render
 from django.utils.encoding import iri_to_uri
 from django.utils.http import urlquote_plus
 from django.utils.translation import ugettext_lazy as _
 from django.views.decorators.http import require_POST
-from django.template import RequestContext
 
 from apiclient import NotAuthorizedError
 from catalogue import forms
 from catalogue import helpers
 from catalogue.helpers import active_tab
-from catalogue.models import Book, Chunk, BookPublishRecord, ChunkPublishRecord, Project
+from catalogue.models import (Book, Chunk, Image, BookPublishRecord, 
+        ChunkPublishRecord, ImagePublishRecord, Project)
 from fileupload.views import UploadView
 
 #
@@ -42,6 +42,12 @@ def document_list(request):
     return render(request, 'catalogue/document_list.html')
 
 
+@active_tab('images')
+@never_cache
+def image_list(request, user=None):
+    return render(request, 'catalogue/image_list.html')
+
+
 @never_cache
 def user(request, username):
     user = get_object_or_404(User, username=username)
@@ -239,8 +245,7 @@ def book_html(request, slug):
 
     # book_themes = book_themes.items()
     # book_themes.sort(key=lambda s: s[0].sort_key)
-    return render_to_response('catalogue/book_text.html', locals(),
-        context_instance=RequestContext(request))
+    return render(request, 'catalogue/book_text.html', locals())
 
 
 @never_cache
@@ -315,6 +320,36 @@ def book(request, slug):
     })
 
 
+def image(request, slug):
+    image = get_object_or_404(Image, slug=slug)
+    if not image.accessible(request):
+        return HttpResponseForbidden("Not authorized.")
+
+    if request.user.has_perm('catalogue.change_image'):
+        if request.method == "POST":
+            form = forms.ImageForm(request.POST, instance=image)
+            if form.is_valid():
+                form.save()
+                return http.HttpResponseRedirect(image.get_absolute_url())
+        else:
+            form = forms.ImageForm(instance=image)
+        editable = True
+    else:
+        form = forms.ReadonlyImageForm(instance=image)
+        editable = False
+
+    publish_error = image.publishable_error()
+    publishable = publish_error is None
+
+    return render(request, "catalogue/image_detail.html", {
+        "object": image,
+        "publishable": publishable,
+        "publishable_error": publish_error,
+        "form": form,
+        "editable": editable,
+    })
+
+
 @permission_required('catalogue.add_chunk')
 def chunk_add(request, slug, chunk):
     try:
@@ -488,6 +523,23 @@ def publish(request, slug):
         return http.HttpResponseRedirect(book.get_absolute_url())
 
 
+@require_POST
+@login_required
+def publish_image(request, slug):
+    image = get_object_or_404(Image, slug=slug)
+    if not image.accessible(request):
+        return HttpResponseForbidden("Not authorized.")
+
+    try:
+        image.publish(request.user)
+    except NotAuthorizedError:
+        return http.HttpResponseRedirect(reverse('apiclient_oauth'))
+    except BaseException, e:
+        return http.HttpResponse(e)
+    else:
+        return http.HttpResponseRedirect(image.get_absolute_url())
+
+
 class GalleryView(UploadView):
     def get_object(self, request, slug):
         book = get_object_or_404(Book, slug=slug)
index 4c3a1ff..dfd85c2 100644 (file)
Binary files a/apps/dvcs/locale/pl/LC_MESSAGES/django.mo and b/apps/dvcs/locale/pl/LC_MESSAGES/django.mo differ
index 64ddfd7..c0365d5 100644 (file)
@@ -7,16 +7,15 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2011-10-03 15:35+0200\n"
-"PO-Revision-Date: 2011-10-03 15:35+0100\n"
+"POT-Creation-Date: 2011-12-14 15:25+0100\n"
+"PO-Revision-Date: 2011-12-14 15:27+0100\n"
 "Last-Translator: Radek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
 "Language: \n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
-"|| n%100>=20) ? 1 : 2)\n"
+"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n"
 
 #: models.py:19
 msgid "name"
@@ -30,86 +29,96 @@ msgstr "slug"
 msgid "ordering"
 msgstr "kolejność"
 
-#: models.py:29
-msgid "tag"
-msgstr "tag"
-
-#: models.py:30 models.py:196
-msgid "tags"
-msgstr "tagi"
-
-#: models.py:72
+#: models.py:70
 msgid "author"
 msgstr "autor"
 
-#: models.py:73
+#: models.py:71
 msgid "author name"
 msgstr "imię i nazwisko autora"
 
-#: models.py:75 models.py:79
+#: models.py:73
+#: models.py:77
 msgid "Used if author is not set."
 msgstr "Używane, gdy nie jest ustawiony autor."
 
-#: models.py:77
+#: models.py:75
 msgid "author email"
 msgstr "e-mail autora"
 
-#: models.py:81
+#: models.py:79
 msgid "revision"
 msgstr "rewizja"
 
-#: models.py:85
+#: models.py:83
 msgid "parent"
 msgstr "rodzic"
 
-#: models.py:90
+#: models.py:88
 msgid "merge parent"
 msgstr "drugi rodzic"
 
-#: models.py:93
+#: models.py:91
 msgid "description"
 msgstr "opis"
 
-#: models.py:96
+#: models.py:94
 msgid "publishable"
 msgstr "do publikacji"
 
-#: models.py:102
+#: models.py:176
+msgid "tag"
+msgstr "tag"
+
+#: models.py:176
+#: models.py:178
+#: models.py:194
+#: models.py:196
+msgid "for:"
+msgstr "dla:"
+
+#: models.py:178
+#: models.py:202
+msgid "tags"
+msgstr "tagi"
+
+#: models.py:194
 msgid "change"
 msgstr "zmiana"
 
-#: models.py:103
+#: models.py:196
 msgid "changes"
 msgstr "zmiany"
 
-#: models.py:195
+#: models.py:201
 msgid "document"
 msgstr "dokument"
 
-#: models.py:197
+#: models.py:203
 msgid "data"
 msgstr "dane"
 
-#: models.py:211
+#: models.py:217
 msgid "stage"
 msgstr "etap"
 
-#: models.py:219
+#: models.py:225
 msgid "head"
 msgstr "głowica"
 
-#: models.py:220
+#: models.py:226
 msgid "This document's current head."
 msgstr "Aktualna wersja dokumentu."
 
-#: models.py:224
+#: models.py:230
 msgid "creator"
 msgstr "utworzył"
 
-#: models.py:239
+#: models.py:245
 msgid "user"
 msgstr "użytkownik"
 
-#: models.py:239
+#: models.py:245
 msgid "Work assignment."
 msgstr "Przypisanie pracy użytkownikowi."
+
index cf8d75d..ec64794 100644 (file)
@@ -6,7 +6,7 @@ from django.core.files.base import ContentFile
 from django.core.files.storage import FileSystemStorage
 from django.db import models, transaction
 from django.db.models.base import ModelBase
-from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import string_concat, ugettext_lazy as _
 from mercurial import simplemerge
 
 from django.conf import settings
@@ -26,8 +26,6 @@ class Tag(models.Model):
     class Meta:
         abstract = True
         ordering = ['ordering']
-        verbose_name = _("tag")
-        verbose_name_plural = _("tags")
 
     def __unicode__(self):
         return self.name
@@ -99,8 +97,6 @@ class Change(models.Model):
         abstract = True
         ordering = ('created_at',)
         unique_together = ['tree', 'revision']
-        verbose_name = _("change")
-        verbose_name_plural = _("changes")
 
     def __unicode__(self):
         return u"Id: %r, Tree %r, Parent %r, Data: %s" % (self.id, self.tree_id, self.parent_id, self.data)
@@ -175,6 +171,10 @@ def create_tag_model(model):
 
     class Meta(Tag.Meta):
         app_label = model._meta.app_label
+        verbose_name = string_concat(_("tag"), " ", _("for:"), " ", 
+                model._meta.verbose_name)
+        verbose_name_plural = string_concat(_("tags"), " ", _("for:"), " ",
+                model._meta.verbose_name)
 
     attrs = {
         '__module__': model.__module__,
@@ -189,6 +189,10 @@ def create_change_model(model):
 
     class Meta(Change.Meta):
         app_label = model._meta.app_label
+        verbose_name = string_concat(_("change"), " ", _("for:"), " ",
+                model._meta.verbose_name)
+        verbose_name_plural = string_concat(_("changes"), " ", _("for:"), " ",
+                model._meta.verbose_name)
 
     attrs = {
         '__module__': model.__module__,
index e164a9e..891e9e8 100644 (file)
@@ -1,14 +1,14 @@
 {% extends "base.html" %}
 {% load toolbar_tags i18n %}
 
-{% block title %}{{ book.title }} - {{ block.super }}{% endblock %}
+{% block titleextra %}{{ chunk.pretty_title }}{% endblock %}
 {% block extrahead %}
 {% load compressed %}
 {% compressed_css 'detail' %}
 {% endblock %}
 
 {% block extrabody %}
-<script type="text/javascript" charset="utf-8">
+<script type="text/javascript">
     var STATIC_URL = '{{STATIC_URL}}';
 </script>
 {% compressed_js 'detail' %}
@@ -27,7 +27,7 @@
 </div>
 
 <div id="header">
-    <h1><a href="{% url 'catalogue_document_list' %}"><img src="{{STATIC_URL}}icons/go-home.png"/><a href="{% url 'catalogue_document_list' %}">Strona<br>główna</a></h1>
+    <h1><a href="{% url 'catalogue_document_list' %}"><img src="{{STATIC_URL}}icons/go-home.png"  alt="Home" /></a><a href="{% url 'catalogue_document_list' %}">Strona<br>główna</a></h1>
     <div id="tools">
         <a href="{{ REDMINE_URL }}projects/wl-publikacje/wiki/Pomoc" target="_blank">
         {% trans "Help" %}</a>
index c2fc155..6f1793b 100644 (file)
@@ -1,6 +1,6 @@
 {% load i18n %}
 <div id="revert_dialog" class="dialog" data-ui-jsclass="RevertDialog">
-       <form method="POST" action="">
+       <form method="POST" action="#">
     {% csrf_token %}
        <p>{{ forms.text_revert.comment.label }}</p>
        <p class="help_text">
@@ -36,7 +36,7 @@
        <p data-ui-error-for="__all__"> </p>
 
        <p class="action_area">
-               <button type="submit" class"ok" data-ui-action="revert">{% trans "Revert" %}</button>
+               <button type="submit" class="ok" data-ui-action="revert">{% trans "Revert" %}</button>
                <button type="button" class="cancel" data-ui-action="cancel">{% trans "Cancel" %}</button>
        </p>
        </form>
index 31c5b01..b133045 100644 (file)
@@ -1,6 +1,6 @@
 {% load i18n %}
 <div id="save_dialog" class="dialog" data-ui-jsclass="SaveDialog">
-       <form method="POST" action="">
+       <form method="POST" action="#">
     {% csrf_token %}
        <p>{{ forms.text_save.comment.label }}</p>
        <p class="help_text">
@@ -52,7 +52,7 @@
        <p data-ui-error-for="__all__"> </p>
 
        <p class="action_area">
-               <button type="submit" class"ok" data-ui-action="save">Zapisz</button>
+               <button type="submit" class="ok" data-ui-action="save">Zapisz</button>
                <button type="button" class="cancel" data-ui-action="cancel">Anuluj</button>
        </p>
        </form>
index f7a0851..8e54242 100644 (file)
@@ -15,6 +15,6 @@
     <div class="annotations-list">
     </div>
     <div class="spinner">
-        <img src='/media/static/img/spinner.gif' />
+        <img src='/media/static/img/spinner.gif' alt="Loading" />
     </div>
 </div>
index f478e12..7f17ce5 100644 (file)
@@ -1,4 +1,4 @@
 {% load i18n %}
 <li id="AnnotationsPerspective" data-ui-related="side-annotations" data-ui-jsclass="AnnotationsPerspective">
-    <super title="{% trans "Annotations" %}">[1]</super>
+    <a href="#"><sup title="{% trans "Annotations" %}">[1]</sup></a>
 </li>
index a69c8ce..4e57ea5 100644 (file)
@@ -9,9 +9,10 @@
             <img src="{{STATIC_URL}}icons/go-previous.png"/>
         </button>
         <input type="text" size="3" maxlength="3" value="0" class="page-number" />
-        <span id="imagesCount" id="">/0</span>
+        <span id="imagesCount">/0</span>
         <button class="next-page" alt="{% trans "Next" %}" title="{% trans "Next" %}">
-            <img src="{{STATIC_URL}}icons/go-next.png"/>
+            <img src="{{STATIC_URL}}icons/go-next.png"
+                alt="{% trans "Next" %}" title="{% trans "Next" %}"/>
         </button>
         <button class="zoom-in">{% trans "Zoom in" %}</button>
         <button class="zoom-out">{% trans "Zoom out" %}</button>
@@ -21,6 +22,6 @@
     <div class="error_message">
     </div>
     <div class="gallery-image">
-        <img src="{{MEDIA_URL}}images/empty.png" />
+        <img src="{{MEDIA_URL}}images/empty.png" alt="no image" />
     </div>
 </div>
index 20505d6..0ad3add 100644 (file)
@@ -1,4 +1,4 @@
 {% load i18n %}
 <li id="ScanGalleryPerspective" data-ui-related="side-gallery" data-ui-jsclass="ScanGalleryPerspective" class='active'>
-    <img src="{{STATIC_URL}}icons/image-x-generic.png" alt="{% trans "Gallery" %}" title="{% trans "Gallery" %}" />
+    <a href="#"><img src="{{STATIC_URL}}icons/image-x-generic.png" alt="{% trans "Gallery" %}" title="{% trans "Gallery" %}" /></a>
 </li>
index bf39a33..e9375cd 100644 (file)
@@ -1,4 +1,4 @@
 {% load i18n %}
 <li id="HistoryPerspective" data-ui-related="history-view-editor" data-ui-jsclass="HistoryPerspective">
-    <span>{% trans "History" %}</span>
+    <a href="#">{% trans "History" %}</a>
 </li>
index 89e0fae..22b6d66 100644 (file)
@@ -2,5 +2,5 @@
 <li id="CodeMirrorPerspective"
        data-ui-related="source-editor"
        data-ui-jsclass="CodeMirrorPerspective">
-    <span>{% trans "Source code" %}</span>
+    <a href="#">{% trans "Source code" %}</a>
 </li>
\ No newline at end of file
index 8a57769..49ad5fe 100644 (file)
@@ -14,7 +14,7 @@
                </div>
 
                <h2>
-                       <label for="title">{% trans "Title" %}:</label>
+                       <label>{% trans "Title" %}:</label>
                        <span data-ui-editable="true" data-edit-target="meta.displayTitle"
                        >{{ chunk.pretty_name }}</span>
                </h2>
                <p>
                        <label>{% trans "Current version" %}:</label>
                        {{ chunk.revision }} ({{ chunk.head.created_at }})
+               </p>
                <p>
                        <label>{% trans "Last edited by" %}:</label>
                        {{ chunk.head.author }}
                </p>
                <p>
-                       <label for="gallery">{% trans "Link to gallery" %}:</label>
+                       <label>{% trans "Link to gallery" %}:</label>
                        <span data-ui-editable="true" data-edit-target="meta.galleryLink"
                        >{{ chunk.book.gallery }}</span>
                </p>
index 856b3d7..ea7ae74 100644 (file)
@@ -1,4 +1,4 @@
 {% load i18n %}
 <li id="SummaryPerspective" data-ui-related="summary-view-editor" data-ui-jsclass="SummaryPerspective">
-    <span>{% trans "Summary" %}</span>
+    <a href="#">{% trans "Summary" %}</a>
 </li>
index 718ec49..ec853cd 100644 (file)
@@ -1,4 +1,4 @@
 {% load i18n %}
 <li id="VisualPerspective" data-ui-related="simple-editor" data-ui-jsclass="VisualPerspective">
-    <span>{% trans "Visual editor" %}</span>
+    <a href="#">{% trans "Visual editor" %}</a>
 </li>
diff --git a/apps/wiki_img/__init__.py b/apps/wiki_img/__init__.py
new file mode 100644 (file)
index 0000000..c53f0e7
--- /dev/null
@@ -0,0 +1 @@
+  # pragma: no cover
diff --git a/apps/wiki_img/forms.py b/apps/wiki_img/forms.py
new file mode 100644 (file)
index 0000000..555f264
--- /dev/null
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
+from django import forms
+from django.utils.translation import ugettext_lazy as _
+from wiki.forms import DocumentTextSaveForm
+from catalogue.models import Image
+
+
+class ImageSaveForm(DocumentTextSaveForm):
+    """Form for saving document's text."""
+
+    stage_completed = forms.ModelChoiceField(
+        queryset=Image.tag_model.objects.all(),
+        required=False,
+        label=_(u"Completed"),
+        help_text=_(u"If you completed a life cycle stage, select it."),
+    )
diff --git a/apps/wiki_img/locale/pl/LC_MESSAGES/django.mo b/apps/wiki_img/locale/pl/LC_MESSAGES/django.mo
new file mode 100644 (file)
index 0000000..3987728
Binary files /dev/null and b/apps/wiki_img/locale/pl/LC_MESSAGES/django.mo differ
diff --git a/apps/wiki_img/locale/pl/LC_MESSAGES/django.po b/apps/wiki_img/locale/pl/LC_MESSAGES/django.po
new file mode 100644 (file)
index 0000000..432d289
--- /dev/null
@@ -0,0 +1,307 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Platforma Redakcyjna\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2011-12-14 15:25+0100\n"
+"PO-Revision-Date: 2011-12-14 15:26+0100\n"
+"Last-Translator: Radek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>\n"
+"Language-Team: Fundacja Nowoczesna Polska <fundacja@nowoczesnapolska.org.pl>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: forms.py:18
+msgid "Completed"
+msgstr "Ukończono"
+
+#: forms.py:19
+msgid "If you completed a life cycle stage, select it."
+msgstr "Jeśli został ukończony etap prac, wskaż go."
+
+#: views.py:126
+msgid "Publishable"
+msgstr "Gotowe do publikacji"
+
+#: templates/wiki_img/base.html:15
+msgid "Platforma Redakcyjna"
+msgstr ""
+
+#: templates/wiki_img/diff_table.html:5
+msgid "Old version"
+msgstr "Stara wersja"
+
+#: templates/wiki_img/diff_table.html:6
+msgid "New version"
+msgstr "Nowa wersja"
+
+#: templates/wiki_img/document_details_base.html:31
+msgid "Help"
+msgstr "Pomoc"
+
+#: templates/wiki_img/document_details_base.html:33
+msgid "Version"
+msgstr "Wersja"
+
+#: templates/wiki_img/document_details_base.html:33
+msgid "Unknown"
+msgstr "nieznana"
+
+#: templates/wiki_img/document_details_base.html:35
+#: templates/wiki_img/tag_dialog.html:15
+msgid "Save"
+msgstr "Zapisz"
+
+#: templates/wiki_img/document_details_base.html:36
+msgid "Save attempt in progress"
+msgstr "Trwa zapisywanie"
+
+#: templates/wiki_img/document_details_base.html:37
+msgid "There is a newer version of this document!"
+msgstr "Istnieje nowsza wersja tego dokumentu!"
+
+#: templates/wiki_img/tag_dialog.html:16
+msgid "Cancel"
+msgstr "Anuluj"
+
+#: templates/wiki_img/tabs/history_view.html:5
+msgid "Compare versions"
+msgstr "Porównaj wersje"
+
+#: templates/wiki_img/tabs/history_view.html:8
+msgid "Mark for publishing"
+msgstr "Oznacz do publikacji"
+
+#: templates/wiki_img/tabs/history_view.html:11
+msgid "Revert document"
+msgstr "Przywróć wersję"
+
+#: templates/wiki_img/tabs/history_view.html:14
+msgid "View version"
+msgstr "Zobacz wersję"
+
+#: templates/wiki_img/tabs/motifs_editor.html:4
+#: templates/wiki_img/tabs/motifs_editor_item.html:3
+msgid "Motifs"
+msgstr "Motywy"
+
+#: templates/wiki_img/tabs/motifs_editor.html:5
+#: templates/wiki_img/tabs/objects_editor.html:5
+msgid "Add"
+msgstr "Dodaj"
+
+#: templates/wiki_img/tabs/objects_editor.html:4
+msgid "Object name"
+msgstr "Nazwa obiektu"
+
+#: templates/wiki_img/tabs/objects_editor_item.html:3
+msgid "Objects"
+msgstr "Obiekty"
+
+#: templates/wiki_img/tabs/source_editor_item.html:5
+msgid "Source code"
+msgstr "Kod źródłowy"
+
+#: templates/wiki_img/tabs/summary_view.html:8
+msgid "Title"
+msgstr "Tytuł"
+
+#: templates/wiki_img/tabs/summary_view.html:13
+msgid "Document ID"
+msgstr "ID dokumentu"
+
+#: templates/wiki_img/tabs/summary_view.html:17
+msgid "Current version"
+msgstr "Aktualna wersja"
+
+#: templates/wiki_img/tabs/summary_view.html:20
+msgid "Last edited by"
+msgstr "Ostatnio edytowane przez"
+
+#: templates/wiki_img/tabs/summary_view_item.html:4
+msgid "Summary"
+msgstr "Podsumowanie"
+
+#~ msgid "First correction"
+#~ msgstr "Autokorekta"
+
+#~ msgid "Tagging"
+#~ msgstr "Tagowanie"
+
+#~ msgid "Initial Proofreading"
+#~ msgstr "Korekta"
+
+#~ msgid "Annotation Proofreading"
+#~ msgstr "Sprawdzenie przypisów źródła"
+
+#~ msgid "Modernisation"
+#~ msgstr "Uwspółcześnienie"
+
+#~ msgid "Annotations"
+#~ msgstr "Przypisy"
+
+#~ msgid "Themes"
+#~ msgstr "Motywy"
+
+#~ msgid "Editor's Proofreading"
+#~ msgstr "Ostateczna redakcja literacka"
+
+#~ msgid "Technical Editor's Proofreading"
+#~ msgstr "Ostateczna redakcja techniczna"
+
+#~ msgid "ZIP file"
+#~ msgstr "Plik ZIP"
+
+#~ msgid "Author"
+#~ msgstr "Autor"
+
+#~ msgid "Your name"
+#~ msgstr "Imię i nazwisko"
+
+#~ msgid "Author's email"
+#~ msgstr "E-mail autora"
+
+#~ msgid "Your email address, so we can show a gravatar :)"
+#~ msgstr "Adres e-mail, żebyśmy mogli pokazać gravatar :)"
+
+#~ msgid "Your comments"
+#~ msgstr "Twój komentarz"
+
+#~ msgid "Describe changes you made."
+#~ msgstr "Opisz swoje zmiany"
+
+#~ msgid "Finished stage: %s"
+#~ msgstr "Ukończony etap: %s"
+
+#~ msgid "name"
+#~ msgstr "nazwa"
+
+#~ msgid "theme"
+#~ msgstr "motyw"
+
+#~ msgid "themes"
+#~ msgstr "motywy"
+
+#~ msgid "Title already used for %s"
+#~ msgstr "Nazwa taka sama jak dla pliku %s"
+
+#~ msgid "Title already used in repository."
+#~ msgstr "Plik o tej nazwie już istnieje w repozytorium."
+
+#~ msgid "File should be UTF-8 encoded."
+#~ msgstr "Plik powinien mieć kodowanie UTF-8."
+
+#~ msgid "Tag added"
+#~ msgstr "Dodano tag"
+
+#~ msgid "Create document"
+#~ msgstr "Utwórz dokument"
+
+#~ msgid "Click to open/close gallery"
+#~ msgstr "Kliknij, aby (ro)zwinąć galerię"
+
+#~ msgid "Clear filter"
+#~ msgstr "Wyczyść filtr"
+
+#~ msgid "Your last edited documents"
+#~ msgstr "Twoje ostatnie edycje"
+
+#~ msgid "Bulk documents upload"
+#~ msgstr "Hurtowe dodawanie dokumentów"
+
+#~ msgid ""
+#~ "Please submit a ZIP with UTF-8 encoded XML files. Files not ending with "
+#~ "<code>.xml</code> will be ignored."
+#~ msgstr ""
+#~ "Proszę wskazać archiwum ZIP z plikami XML w kodowaniu UTF-8. Pliki nie "
+#~ "kończące się na <code>.xml</code> zostaną zignorowane."
+
+#~ msgid "Upload"
+#~ msgstr "Dodaj"
+
+#~ msgid ""
+#~ "There have been some errors. No files have been added to the repository."
+#~ msgstr "Wystąpiły błędy. Żadne pliki nie zostały dodane do repozytorium."
+
+#~ msgid "Offending files"
+#~ msgstr "Błędne pliki"
+
+#~ msgid "Correct files"
+#~ msgstr "Poprawne pliki"
+
+#~ msgid "Files have been successfully uploaded to the repository."
+#~ msgstr "Pliki zostały dodane do repozytorium."
+
+#~ msgid "Uploaded files"
+#~ msgstr "Dodane pliki"
+
+#~ msgid "Skipped files"
+#~ msgstr "Pominięte pliki"
+
+#~ msgid "Files skipped due to no <code>.xml</code> extension"
+#~ msgstr "Pliki pominięte z powodu braku rozszerzenia <code>.xml</code>."
+
+#~ msgid "Refresh"
+#~ msgstr "Odśwież"
+
+#~ msgid "Previous"
+#~ msgstr "Poprzednie"
+
+#~ msgid "Next"
+#~ msgstr "Następne"
+
+#~ msgid "Zoom in"
+#~ msgstr "Powiększ"
+
+#~ msgid "Zoom out"
+#~ msgstr "Zmniejsz"
+
+#~ msgid "Gallery"
+#~ msgstr "Galeria"
+
+#~ msgid "Mark version"
+#~ msgstr "Oznacz wersję"
+
+#~ msgid "History"
+#~ msgstr "Historia"
+
+#~ msgid "Search"
+#~ msgstr "Szukaj"
+
+#~ msgid "Replace with"
+#~ msgstr "Zamień na"
+
+#~ msgid "Replace"
+#~ msgstr "Zamień"
+
+#~ msgid "Options"
+#~ msgstr "Opcje"
+
+#~ msgid "Case sensitive"
+#~ msgstr "Rozróżniaj wielkość liter"
+
+#~ msgid "From cursor"
+#~ msgstr "Zacznij od kursora"
+
+#~ msgid "Search and replace"
+#~ msgstr "Znajdź i zamień"
+
+#~ msgid "Link to gallery"
+#~ msgstr "Link do galerii"
+
+#~ msgid "Insert theme"
+#~ msgstr "Wstaw motyw"
+
+#~ msgid "Insert annotation"
+#~ msgstr "Wstaw przypis"
+
+#~ msgid "Insert special character"
+#~ msgstr "Wstaw znak specjalny"
+
+#~ msgid "Visual editor"
+#~ msgstr "Edytor wizualny"
diff --git a/apps/wiki_img/models.py b/apps/wiki_img/models.py
new file mode 100644 (file)
index 0000000..b685324
--- /dev/null
@@ -0,0 +1,5 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
diff --git a/apps/wiki_img/templates/wiki_img/diff_table.html b/apps/wiki_img/templates/wiki_img/diff_table.html
new file mode 100644 (file)
index 0000000..818c38c
--- /dev/null
@@ -0,0 +1,21 @@
+{% load i18n %}
+<table class="diff_table">
+       <thead>
+               <tr>
+                       <th colspan="2">{% trans "Old version" %}</th>
+                       <th colspan="2">{% trans "New version" %}</th>
+               </tr>
+       </thead>
+<tbody>
+{% for an, a, bn, b, has_change in changes %}
+
+<tr class="{% if has_change %}change{% endif %}">
+<td>{{an}}</td>
+<td class="left">{{ a|safe }}&nbsp;</td>
+<td>{{bn}}</td>
+<td class="right">{{ b|safe }}&nbsp;</td>
+</tr>
+
+{% endfor %}
+</tbody>
+</table>
\ No newline at end of file
diff --git a/apps/wiki_img/templates/wiki_img/document_details.html b/apps/wiki_img/templates/wiki_img/document_details.html
new file mode 100644 (file)
index 0000000..0dfc9ae
--- /dev/null
@@ -0,0 +1,38 @@
+{% extends "wiki_img/document_details_base.html" %}
+{% load i18n %}
+
+{% block extrabody %}
+{{ block.super }}
+<script src="{{ STATIC_URL }}js/lib/codemirror-0.8/codemirror.js" type="text/javascript" charset="utf-8">
+</script>
+<script src="{{ STATIC_URL }}js/wiki_img/loader.js" type="text/javascript" charset="utf-8"> </script>
+{% endblock %}
+
+{% block tabs-menu %}
+    {% include "wiki_img/tabs/summary_view_item.html" %}
+    {% include "wiki_img/tabs/motifs_editor_item.html" %}
+    {% include "wiki_img/tabs/objects_editor_item.html" %}
+    {% include "wiki_img/tabs/source_editor_item.html" %}
+    {% include "wiki/tabs/history_view_item.html" %}
+{% endblock %}
+
+{% block tabs-content %}
+    {% include "wiki_img/tabs/summary_view.html" %}
+    {% include "wiki_img/tabs/motifs_editor.html" %}
+    {% include "wiki_img/tabs/objects_editor.html" %}
+    {% include "wiki_img/tabs/source_editor.html" %}
+    {% include "wiki_img/tabs/history_view.html" %}
+{% endblock %}
+
+{% block dialogs %}
+    {% include "wiki/save_dialog.html" %}
+    {% include "wiki/revert_dialog.html" %}
+    {% if can_pubmark %}
+        {% include "wiki/pubmark_dialog.html" %}
+    {% endif %}
+{% endblock %}
+
+{% block editor-class %}
+    sideless
+{% endblock %}
+
diff --git a/apps/wiki_img/templates/wiki_img/document_details_base.html b/apps/wiki_img/templates/wiki_img/document_details_base.html
new file mode 100644 (file)
index 0000000..642c916
--- /dev/null
@@ -0,0 +1,52 @@
+{% extends "base.html" %}
+{% load toolbar_tags i18n %}
+
+{% block title %}{{ document.title }} - {{ block.super }}{% endblock %}
+{% block extrahead %}
+{% load compressed %}
+{% compressed_css 'detail' %}
+{% endblock %}
+
+{% block extrabody %}
+<script type="text/javascript">
+    var STATIC_URL = '{{STATIC_URL}}';
+</script>
+{% compressed_js 'wiki_img' %}
+{% endblock %}
+
+{% block maincontent %}
+<div id="document-meta"
+       data-object-id="{{ document.pk }}" style="display:none">
+
+       <span data-key="revision">{{ revision }}</span>
+    <span data-key="diff">{{ request.GET.diff }}</span>
+
+       {% block meta-extra %} {% endblock %}
+</div>
+
+<div id="header">
+    <h1><a href="{% url 'catalogue_document_list' %}"><img alt="Home" src="{{STATIC_URL}}icons/go-home.png"/></a><a href="{% url 'catalogue_document_list' %}">Strona<br/>główna</a></h1>
+    <div id="tools">
+        <a href="{{ REDMINE_URL }}projects/wl-publikacje/wiki/Pomoc" target="_blank">
+        {% trans "Help" %}</a>
+        | {% include "registration/head_login.html" %}
+        | {% trans "Version" %}: <span id="document-revision">{% trans "Unknown" %}</span>
+               {% if not readonly %}
+            | <button style="margin-left: 6px" id="save-button">{% trans "Save" %}</button>
+                       <span id='save-attempt-info'>{% trans "Save attempt in progress" %}</span>
+            <span id='out-of-date-info'>{% trans "There is a newer version of this document!" %}</span>
+               {% endif %}
+    </div>
+    <ol id="tabs" class="tabs">
+       {% block tabs-menu %} {% endblock %}
+    </ol>
+</div>
+<div id="splitter">
+    <div id="editor" class="{% block editor-class %} {% endblock %}">
+       {% block tabs-content %} {% endblock %}
+    </div>
+</div>
+
+{% block dialogs %} {% endblock %}
+
+{% endblock %}
diff --git a/apps/wiki_img/templates/wiki_img/document_details_readonly.html b/apps/wiki_img/templates/wiki_img/document_details_readonly.html
new file mode 100644 (file)
index 0000000..ca38838
--- /dev/null
@@ -0,0 +1,26 @@
+{% extends "wiki_img/document_details_base.html" %}
+{% load i18n %}
+
+{% block extrabody %}
+{{ block.super }}
+<script src="{{ STATIC_URL }}js/lib/codemirror-0.8/codemirror.js" type="text/javascript" charset="utf-8">
+</script>
+<script src="{{ STATIC_URL }}js/wiki_img/loader_readonly.js" type="text/javascript" charset="utf-8"> </script>
+{% endblock %}
+
+{% block tabs-menu %}
+    {% include "wiki_img/tabs/motifs_editor_item.html" %}
+    {% include "wiki_img/tabs/objects_editor_item.html" %}
+    {% include "wiki_img/tabs/source_editor_item.html" %}
+{% endblock %}
+
+{% block tabs-content %}
+    {% include "wiki_img/tabs/motifs_editor.html" %}
+    {% include "wiki_img/tabs/objects_editor.html" %}
+    {% include "wiki_img/tabs/source_editor.html" %}
+{% endblock %}
+
+{% block editor-class %}
+    sideless
+{% endblock %}
+
diff --git a/apps/wiki_img/templates/wiki_img/tabs/history_view.html b/apps/wiki_img/templates/wiki_img/tabs/history_view.html
new file mode 100755 (executable)
index 0000000..dcb62ec
--- /dev/null
@@ -0,0 +1,40 @@
+{% load i18n %}
+<div id="history-view-editor" class="editor" style="display: none">
+    <div class="toolbar">
+       <button type="button" id="make-diff-button"
+                       data-enabled-when="2" disabled="disabled">{% trans "Compare versions" %}</button>
+        {% if can_pubmark %}
+               <button type="button" id="pubmark-changeset-button"
+                       data-enabled-when="1" disabled="disabled">{% trans "Mark for publishing" %}</button>
+        {% endif %}
+               <button type="button" id="doc-revert-button"
+                       data-enabled-when="1" disabled="disabled">{% trans "Revert document" %}</button>
+               <button id="open-preview-button" disabled="disabled"
+                       data-enabled-when="1"
+                       data-basehref="{% url 'wiki_img_editor_readonly' document.slug %}">{% trans "View version" %}</button>
+
+       </div>
+    <div id="history-view">
+        <p class="message-box" style="display:none;"></p>
+
+               <table id="changes-list-container">
+        <tbody id="changes-list">
+        </tbody>
+               <tbody style="display: none;">
+                       <tr class="entry row-stub">
+                       <td data-stub-value="version"></td>
+                       <td>
+                <span data-stub-value="date"></span>
+               <br/><span data-stub-value="author"></span>
+                               <br />
+                               <span data-stub-value="description"></span>
+                       </td>
+                       <td>
+                <div data-stub-value="publishable"></div>
+                <div data-stub-value="tag"></div>
+                       </td>
+               </tr>
+               </tbody>
+               </table>
+    </div>
+</div>
diff --git a/apps/wiki_img/templates/wiki_img/tabs/motifs_editor.html b/apps/wiki_img/templates/wiki_img/tabs/motifs_editor.html
new file mode 100644 (file)
index 0000000..c064505
--- /dev/null
@@ -0,0 +1,17 @@
+{% load i18n %}
+<div id="motifs-editor" class="editor" style="display: none">
+    <div class="toolbar">
+        <input class='tag-name' title='{% trans "Motifs" %}' />
+        <button class='add'>{% trans "Add" %}</button>
+
+        <span class="objects-list">
+        </span>
+
+        <div class="toolbar-end">
+        </div>
+    </div>
+
+    <div class='scrolled'>
+        <img src="{{ document.image.url }}" class='area-selectable' alt="Tagged image" />
+    </div>
+</div>
diff --git a/apps/wiki_img/templates/wiki_img/tabs/motifs_editor_item.html b/apps/wiki_img/templates/wiki_img/tabs/motifs_editor_item.html
new file mode 100644 (file)
index 0000000..a5a3c34
--- /dev/null
@@ -0,0 +1,4 @@
+{% load i18n %}
+<li id="MotifsPerspective" data-ui-related="motifs-editor" data-ui-jsclass="MotifsPerspective">
+    <a href="#">{% trans "Motifs" %}</a>
+</li>
diff --git a/apps/wiki_img/templates/wiki_img/tabs/objects_editor.html b/apps/wiki_img/templates/wiki_img/tabs/objects_editor.html
new file mode 100644 (file)
index 0000000..b4149e2
--- /dev/null
@@ -0,0 +1,17 @@
+{% load i18n %}
+<div id="objects-editor" class="editor" style="display: none">
+    <div class="toolbar">
+        <input class='tag-name' title='{% trans "Object name" %}' />
+        <button class='add'>{% trans "Add" %}</button>
+
+        <span class="objects-list">
+        </span>
+
+        <div class="toolbar-end">
+        </div>
+    </div>
+
+    <div class='scrolled'>
+        <img src="{{ document.image.url }}" class='area-selectable' alt="Tagged image" />
+    </div>
+</div>
diff --git a/apps/wiki_img/templates/wiki_img/tabs/objects_editor_item.html b/apps/wiki_img/templates/wiki_img/tabs/objects_editor_item.html
new file mode 100644 (file)
index 0000000..9fc3af9
--- /dev/null
@@ -0,0 +1,4 @@
+{% load i18n %}
+<li id="ObjectsPerspective" data-ui-related="objects-editor" data-ui-jsclass="ObjectsPerspective">
+    <a href="#">{% trans "Objects" %}</a>
+</li>
diff --git a/apps/wiki_img/templates/wiki_img/tabs/source_editor.html b/apps/wiki_img/templates/wiki_img/tabs/source_editor.html
new file mode 100644 (file)
index 0000000..a1316a7
--- /dev/null
@@ -0,0 +1,4 @@
+{% load toolbar_tags i18n %}
+<div id="source-editor" class="editor">
+    <textarea id="codemirror_placeholder">&lt;br/&gt;</textarea>
+</div>
\ No newline at end of file
diff --git a/apps/wiki_img/templates/wiki_img/tabs/source_editor_item.html b/apps/wiki_img/templates/wiki_img/tabs/source_editor_item.html
new file mode 100644 (file)
index 0000000..22b6d66
--- /dev/null
@@ -0,0 +1,6 @@
+{% load i18n %}
+<li id="CodeMirrorPerspective"
+       data-ui-related="source-editor"
+       data-ui-jsclass="CodeMirrorPerspective">
+    <a href="#">{% trans "Source code" %}</a>
+</li>
\ No newline at end of file
diff --git a/apps/wiki_img/templates/wiki_img/tabs/summary_view.html b/apps/wiki_img/templates/wiki_img/tabs/summary_view.html
new file mode 100644 (file)
index 0000000..a908f55
--- /dev/null
@@ -0,0 +1,24 @@
+{% load i18n %}
+{% load wiki %}
+<div id="summary-view-editor" class="editor" style="display: none">
+    <!-- <div class="toolbar">
+    </div> -->
+    <div id="summary-view">
+               <h2>
+                       <label for="title">{% trans "Title" %}:</label>
+                       <span data-ui-editable="true" data-edit-target="meta.displayTitle"
+                       >{{ document.name|wiki_title }}</span>
+               </h2>
+               <p>
+                       <label>{% trans "Document ID" %}:</label>
+                       <span>{{ document.name }}</span>
+               </p>
+               <p>
+                       <label>{% trans "Current version" %}:</label>
+                       {{ document_info.revision }} ({{document_info.date}})
+               <p>
+                       <label>{% trans "Last edited by" %}:</label>
+                       {{document_info.author}}
+               </p>
+       </div>
+</div>
diff --git a/apps/wiki_img/templates/wiki_img/tabs/summary_view_item.html b/apps/wiki_img/templates/wiki_img/tabs/summary_view_item.html
new file mode 100644 (file)
index 0000000..bae3ea5
--- /dev/null
@@ -0,0 +1,5 @@
+{% load i18n %}
+{% load wiki %}
+<li id="SummaryPerspective" data-ui-related="summary-view-editor" data-ui-jsclass="SummaryPerspective">
+    <a href="#">{% trans "Summary" %}</a>
+</li>
diff --git a/apps/wiki_img/tests.py b/apps/wiki_img/tests.py
new file mode 100644 (file)
index 0000000..6577737
--- /dev/null
@@ -0,0 +1,19 @@
+from nose.tools import *
+import wiki.models as models
+import shutil
+import tempfile
+
+
+class TestStorageBase:
+    def setUp(self):
+        self.dirpath = tempfile.mkdtemp(prefix='nosetest_')
+
+    def tearDown(self):
+        shutil.rmtree(self.dirpath)
+
+
+class TestDocumentStorage(TestStorageBase):
+
+    def test_storage_empty(self):
+        storage = models.DocumentStorage(self.dirpath)
+        eq_(storage.all(), [])
diff --git a/apps/wiki_img/urls.py b/apps/wiki_img/urls.py
new file mode 100644 (file)
index 0000000..6a516f3
--- /dev/null
@@ -0,0 +1,24 @@
+# -*- coding: utf-8
+from django.conf.urls import patterns, url
+
+
+urlpatterns = patterns('wiki_img.views',
+    url(r'^edit/(?P<slug>[^/]+)/$',
+        'editor', name="wiki_img_editor"),
+
+    url(r'^readonly/(?P<slug>[^/]+)/$',
+        'editor_readonly', name="wiki_img_editor_readonly"),
+
+    url(r'^text/(?P<image_id>\d+)/$',
+        'text', name="wiki_img_text"),
+
+    url(r'^history/(?P<object_id>\d+)/$',
+        'history', name="wiki_img_history"),
+
+    url(r'^revert/(?P<object_id>\d+)/$',
+        'revert', name='wiki_img_revert'),
+
+    url(r'^diff/(?P<object_id>\d+)/$', 'diff', name="wiki_img_diff"),
+    url(r'^pubmark/(?P<object_id>\d+)/$', 'pubmark', name="wiki_img_pubmark"),
+
+)
diff --git a/apps/wiki_img/views.py b/apps/wiki_img/views.py
new file mode 100644 (file)
index 0000000..08a0a03
--- /dev/null
@@ -0,0 +1,213 @@
+import os
+import functools
+import logging
+logger = logging.getLogger("fnp.wiki_img")
+
+from django.core.urlresolvers import reverse
+from wiki.helpers import (JSONResponse, JSONFormInvalid, JSONServerError,
+                ajax_require_permission)
+
+from django import http
+from django.shortcuts import get_object_or_404, render
+from django.views.decorators.http import require_GET, require_POST
+from django.conf import settings
+from django.utils.formats import localize
+from django.utils.translation import ugettext as _
+
+from catalogue.models import Image
+from wiki import forms
+from wiki import nice_diff
+from wiki_img.forms import ImageSaveForm
+
+#
+# Quick hack around caching problems, TODO: use ETags
+#
+from django.views.decorators.cache import never_cache
+
+
+@never_cache
+def editor(request, slug, template_name='wiki_img/document_details.html'):
+    doc = get_object_or_404(Image, slug=slug)
+
+    return render(request, template_name, {
+        'document': doc,
+        'forms': {
+            "text_save": ImageSaveForm(user=request.user, prefix="textsave"),
+            "text_revert": forms.DocumentTextRevertForm(prefix="textrevert"),
+            "pubmark": forms.DocumentPubmarkForm(prefix="pubmark"),
+        },
+        'can_pubmark': request.user.has_perm('catalogue.can_pubmark_image'),
+        'REDMINE_URL': settings.REDMINE_URL,
+    })
+
+
+@require_GET
+def editor_readonly(request, slug, template_name='wiki_img/document_details_readonly.html'):
+    doc = get_object_or_404(Image, slug=slug)
+    try:
+        revision = request.GET['revision']
+    except (KeyError):
+        raise Http404
+
+    return render(request, template_name, {
+        'document': doc,
+        'revision': revision,
+        'readonly': True,
+        'REDMINE_URL': settings.REDMINE_URL,
+    })
+
+
+@never_cache
+def text(request, image_id):
+    doc = get_object_or_404(Image, pk=image_id)
+    if request.method == 'POST':
+        form = ImageSaveForm(request.POST, user=request.user, prefix="textsave")
+        if form.is_valid():
+            if request.user.is_authenticated():
+                author = request.user
+            else:
+                author = None
+            text = form.cleaned_data['text']
+            parent_revision = form.cleaned_data['parent_revision']
+            if parent_revision is not None:
+                parent = doc.at_revision(parent_revision)
+            else:
+                parent = None
+            stage = form.cleaned_data['stage_completed']
+            tags = [stage] if stage else []
+            publishable = (form.cleaned_data['publishable'] and
+                    request.user.has_perm('catalogue.can_pubmark_image'))
+            doc.commit(author=author,
+                   text=text,
+                   parent=parent,
+                   description=form.cleaned_data['comment'],
+                   tags=tags,
+                   author_name=form.cleaned_data['author_name'],
+                   author_email=form.cleaned_data['author_email'],
+                   publishable=publishable,
+                )
+            revision = doc.revision()
+            return JSONResponse({
+                'text': doc.materialize() if parent_revision != revision else None,
+                'meta': {},
+                'revision': revision,
+            })
+        else:
+            return JSONFormInvalid(form)
+    else:
+        revision = request.GET.get("revision", None)
+        
+        try:
+            revision = int(revision)
+        except (ValueError, TypeError):
+            revision = doc.revision()
+
+        if revision is not None:
+            text = doc.at_revision(revision).materialize()
+        else:
+            text = ''
+
+        return JSONResponse({
+            'text': text,
+            'meta': {},
+            'revision': revision,
+        })
+
+
+@never_cache
+def history(request, object_id):
+    # TODO: pagination
+    doc = get_object_or_404(Image, pk=object_id)
+    if not doc.accessible(request):
+        return HttpResponseForbidden("Not authorized.")
+
+    changes = []
+    for change in doc.history().reverse():
+        changes.append({
+                "version": change.revision,
+                "description": change.description,
+                "author": change.author_str(),
+                "date": localize(change.created_at),
+                "publishable": _("Publishable") + "\n" if change.publishable else "",
+                "tag": ',\n'.join(unicode(tag) for tag in change.tags.all()),
+            })
+    return JSONResponse(changes)
+
+
+@never_cache
+@require_POST
+def revert(request, object_id):
+    form = forms.DocumentTextRevertForm(request.POST, prefix="textrevert")
+    if form.is_valid():
+        doc = get_object_or_404(Image, pk=object_id)
+        if not doc.accessible(request):
+            return HttpResponseForbidden("Not authorized.")
+
+        revision = form.cleaned_data['revision']
+
+        comment = form.cleaned_data['comment']
+        comment += "\n#revert to %s" % revision
+
+        if request.user.is_authenticated():
+            author = request.user
+        else:
+            author = None
+
+        before = doc.revision()
+        logger.info("Reverting %s to %s", object_id, revision)
+        doc.at_revision(revision).revert(author=author, description=comment)
+
+        return JSONResponse({
+            'text': doc.materialize() if before != doc.revision() else None,
+            'meta': {},
+            'revision': doc.revision(),
+        })
+    else:
+        return JSONFormInvalid(form)
+
+
+@never_cache
+def diff(request, object_id):
+    revA = int(request.GET.get('from', 0))
+    revB = int(request.GET.get('to', 0))
+
+    if revA > revB:
+        revA, revB = revB, revA
+
+    if revB == 0:
+        revB = None
+
+    doc = get_object_or_404(Image, pk=object_id)
+    if not doc.accessible(request):
+        return HttpResponseForbidden("Not authorized.")
+
+    # allow diff from the beginning
+    if revA:
+        docA = doc.at_revision(revA).materialize()
+    else:
+        docA = ""
+    docB = doc.at_revision(revB).materialize()
+
+    return http.HttpResponse(nice_diff.html_diff_table(docA.splitlines(),
+                                         docB.splitlines(), context=3))
+
+
+@require_POST
+@ajax_require_permission('catalogue.can_pubmark_image')
+def pubmark(request, object_id):
+    form = forms.DocumentPubmarkForm(request.POST, prefix="pubmark")
+    if form.is_valid():
+        doc = get_object_or_404(Image, pk=object_id)
+        if not doc.accessible(request):
+            return HttpResponseForbidden("Not authorized.")
+
+        revision = form.cleaned_data['revision']
+        publishable = form.cleaned_data['publishable']
+        change = doc.at_revision(revision)
+        if publishable != change.publishable:
+            change.set_publishable(publishable)
+            return JSONResponse({"message": _("Revision marked")})
+        else:
+            return JSONResponse({"message": _("Nothing changed")})
+    else:
+        return JSONFormInvalid(form)
index 6351b43..75a1b6f 160000 (submodule)
@@ -1 +1 @@
-Subproject commit 6351b433475b8119c8cd0cf7a8c72c623308c5d3
+Subproject commit 75a1b6f79da05e694d2528769cad5e964d761f34
index 4565fe4..735743d 100644 (file)
@@ -121,6 +121,7 @@ INSTALLED_APPS = (
     'cover',
     'dvcs',
     'wiki',
+    'wiki_img',
     'toolbar',
     'apiclient',
     'email_mangler',
index 6143729..483e0ed 100644 (file)
@@ -22,6 +22,7 @@ PIPELINE_CSS = {
             'css/summary.css',
             'css/html.css',
             'css/jquery.autocomplete.css',
+            'css/imgareaselect-default.css', #img!
             'css/dialogs.css',
         ),
         'output_filename': 'compressed/detail_styles.css',
@@ -84,6 +85,38 @@ PIPELINE_JS = {
         ),
         'output_filename': 'compressed/detail_scripts.js',
      },
+    'wiki_img': {
+        'source_filenames': (
+                # libraries
+                'js/lib/jquery-1.4.2.min.js',
+                'js/lib/jquery/jquery.autocomplete.js',
+                'js/lib/jquery/jquery.blockui.js',
+                'js/lib/jquery/jquery.elastic.js',
+                'js/lib/jquery/jquery.imgareaselect.js',
+                'js/button_scripts.js',
+                'js/slugify.js',
+
+                # wiki scripts
+                'js/wiki_img/wikiapi.js',
+
+                # base UI
+                'js/wiki_img/base.js',
+                'js/wiki/toolbar.js',
+
+                # dialogs
+                'js/wiki/dialog_save.js',
+                'js/wiki/dialog_revert.js',
+                'js/wiki/dialog_pubmark.js',
+
+                # views
+                'js/wiki_img/view_editor_objects.js',
+                'js/wiki_img/view_editor_motifs.js',
+                'js/wiki/view_editor_source.js',
+                'js/wiki/view_history.js',
+                'js/wiki/view_column_diff.js',
+        ),
+        'output_filename': 'compressed/detail_img_scripts.js',
+     },
     'catalogue': {
         'source_filenames': (
                 'js/catalogue/catalogue.js',
index f25db1e..b5e4fc4 100644 (file)
@@ -46,7 +46,7 @@ td {
     margin-bottom: -1px;
     border-width: 1px;
     border-style: solid;
-    border-color: rgba(0,0,0,0);
+    border-color: transparent;
 }
 
 #tabs-nav-left .active {
@@ -111,11 +111,11 @@ td {
 
 #last-edited-list .date {
        font-size: 70%;
-       color: grey;
+       color: #808080;
 }
 
 a, a:visited, a:active {
-       color: #bf6000;
+       color: #a05000;
        text-decoration: none;
 }
 
diff --git a/redakcja/static/css/imgareaselect-default.css b/redakcja/static/css/imgareaselect-default.css
new file mode 100644 (file)
index 0000000..e9c8592
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * imgAreaSelect default style
+ */
+
+.imgareaselect-border1 {
+       background: url(/media/static/img/jquery.imgareaselect/border-v.gif) repeat-y left top;
+}
+
+.imgareaselect-border2 {
+    background: url(/media/static/img/jquery.imgareaselect/border-h.gif) repeat-x left top;
+}
+
+.imgareaselect-border3 {
+    background: url(/media/static/img/jquery.imgareaselect/border-v.gif) repeat-y right top;
+}
+
+.imgareaselect-border4 {
+    background: url(/media/static/img/jquery.imgareaselect/border-h.gif) repeat-x left bottom;
+}
+
+.imgareaselect-border1, .imgareaselect-border2,
+.imgareaselect-border3, .imgareaselect-border4 {
+    opacity: 0.5;
+    filter: alpha(opacity=50);
+}
+
+.imgareaselect-handle {
+    background-color: #fff;
+    border: solid 1px #000;
+    opacity: 0.5;
+    filter: alpha(opacity=50);
+}
+
+.imgareaselect-outer {
+    background-color: #000;
+    opacity: 0.5;
+    filter: alpha(opacity=50);
+}
+
+.imgareaselect-selection {  
+}
index b88fb35..a87d5a6 100644 (file)
@@ -69,6 +69,29 @@ body {
     overflow: hidden;
 }
 
+.sideless .editor {
+    right: 0;
+}
+.image-object {
+    padding-left: 1em;
+    font: 12px Sans, Helvetica, Verdana, sans-serif;
+}
+.image-object:hover {
+    cursor: pointer;
+}
+#objects-list .delete {
+    padding-left: 3px;
+    font: 10px Sans, Helvetica, Verdana, sans-serif;
+}
+#objects-list .delete:hover {
+    cursor: pointer;
+}
+
+#objects-list .active {
+    color: #800;
+}
+
+
 #editor.readonly .editor {
        right: 0px;
 }
@@ -111,6 +134,8 @@ body {
 
     font: 11px Helvetica, Verdana, sans-serif;
     font-weight: bold;
+
+    z-index: 100;
 }
 
 
@@ -176,6 +201,10 @@ body {
     float: left;
 }
 
+.tabs a {
+    color: black;
+}
+
 #tabs-right {
     float: right;
     padding-right: 1em;
@@ -365,3 +394,14 @@ img.tabclose {
 .saveNotify span {
     font-weight: bold;
 }
+
+
+
+.scrolled {
+    position: absolute;
+    top: 29px;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    overflow: auto;
+}
diff --git a/redakcja/static/img/jquery.imgareaselect/border-anim-h.gif b/redakcja/static/img/jquery.imgareaselect/border-anim-h.gif
new file mode 100644 (file)
index 0000000..ec9f5da
Binary files /dev/null and b/redakcja/static/img/jquery.imgareaselect/border-anim-h.gif differ
diff --git a/redakcja/static/img/jquery.imgareaselect/border-anim-v.gif b/redakcja/static/img/jquery.imgareaselect/border-anim-v.gif
new file mode 100644 (file)
index 0000000..331cc90
Binary files /dev/null and b/redakcja/static/img/jquery.imgareaselect/border-anim-v.gif differ
diff --git a/redakcja/static/img/jquery.imgareaselect/border-h.gif b/redakcja/static/img/jquery.imgareaselect/border-h.gif
new file mode 100644 (file)
index 0000000..a2aa5b0
Binary files /dev/null and b/redakcja/static/img/jquery.imgareaselect/border-h.gif differ
diff --git a/redakcja/static/img/jquery.imgareaselect/border-v.gif b/redakcja/static/img/jquery.imgareaselect/border-v.gif
new file mode 100644 (file)
index 0000000..4bfd555
Binary files /dev/null and b/redakcja/static/img/jquery.imgareaselect/border-v.gif differ
diff --git a/redakcja/static/js/lib/jquery/jquery.imgareaselect.js b/redakcja/static/js/lib/jquery/jquery.imgareaselect.js
new file mode 100644 (file)
index 0000000..7e1b790
--- /dev/null
@@ -0,0 +1,730 @@
+/*
+ * imgAreaSelect jQuery plugin
+ * version 0.9.10
+ *
+ * Copyright (c) 2008-2013 Michal Wojciechowski (odyniec.net)
+ *
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ * http://odyniec.net/projects/imgareaselect/
+ *
+ */
+
+(function($) {
+
+var abs = Math.abs,
+    max = Math.max,
+    min = Math.min,
+    round = Math.round;
+
+function div() {
+    return $('<div/>');
+}
+
+$.imgAreaSelect = function (img, options) {
+    var
+
+        $img = $(img),
+
+        imgLoaded,
+
+        $box = div(),
+        $area = div(),
+        $border = div().add(div()).add(div()).add(div()),
+        $outer = div().add(div()).add(div()).add(div()),
+        $handles = $([]),
+
+        $areaOpera,
+
+        left, top,
+
+        imgOfs = { left: 0, top: 0 },
+
+        imgWidth, imgHeight,
+
+        $parent,
+
+        parOfs = { left: 0, top: 0 },
+
+        zIndex = 0,
+
+        position = 'absolute',
+
+        startX, startY,
+
+        scaleX, scaleY,
+
+        resize,
+
+        minWidth, minHeight, maxWidth, maxHeight,
+
+        aspectRatio,
+
+        shown,
+
+        x1, y1, x2, y2,
+
+        selection = { x1: 0, y1: 0, x2: 0, y2: 0, width: 0, height: 0 },
+
+        docElem = document.documentElement,
+
+        ua = navigator.userAgent,
+
+        $p, d, i, o, w, h, adjusted;
+
+    function viewX(x) {
+        return x + imgOfs.left - parOfs.left;
+    }
+
+    function viewY(y) {
+        return y + imgOfs.top - parOfs.top;
+    }
+
+    function selX(x) {
+        return x - imgOfs.left + parOfs.left;
+    }
+
+    function selY(y) {
+        return y - imgOfs.top + parOfs.top;
+    }
+
+    function evX(event) {
+        return event.pageX - parOfs.left;
+    }
+
+    function evY(event) {
+        return event.pageY - parOfs.top;
+    }
+
+    function getSelection(noScale) {
+        var sx = noScale || scaleX, sy = noScale || scaleY;
+
+        return { x1: round(selection.x1 * sx),
+            y1: round(selection.y1 * sy),
+            x2: round(selection.x2 * sx),
+            y2: round(selection.y2 * sy),
+            width: round(selection.x2 * sx) - round(selection.x1 * sx),
+            height: round(selection.y2 * sy) - round(selection.y1 * sy) };
+    }
+
+    function setSelection(x1, y1, x2, y2, noScale) {
+        var sx = noScale || scaleX, sy = noScale || scaleY;
+
+        selection = {
+            x1: round(x1 / sx || 0),
+            y1: round(y1 / sy || 0),
+            x2: round(x2 / sx || 0),
+            y2: round(y2 / sy || 0)
+        };
+
+        selection.width = selection.x2 - selection.x1;
+        selection.height = selection.y2 - selection.y1;
+    }
+
+    function adjust() {
+        if (!imgLoaded || !$img.width())
+            return;
+
+        imgOfs = { left: round($img.offset().left), top: round($img.offset().top) };
+
+        imgWidth = $img.innerWidth();
+        imgHeight = $img.innerHeight();
+
+        imgOfs.top += ($img.outerHeight() - imgHeight) >> 1;
+        imgOfs.left += ($img.outerWidth() - imgWidth) >> 1;
+
+        minWidth = round(options.minWidth / scaleX) || 0;
+        minHeight = round(options.minHeight / scaleY) || 0;
+        maxWidth = round(min(options.maxWidth / scaleX || 1<<24, imgWidth));
+        maxHeight = round(min(options.maxHeight / scaleY || 1<<24, imgHeight));
+
+        if ($().jquery == '1.3.2' && position == 'fixed' &&
+            !docElem['getBoundingClientRect'])
+        {
+            imgOfs.top += max(document.body.scrollTop, docElem.scrollTop);
+            imgOfs.left += max(document.body.scrollLeft, docElem.scrollLeft);
+        }
+
+        parOfs = /absolute|relative/.test($parent.css('position')) ?
+            { left: round($parent.offset().left) - $parent.scrollLeft(),
+                top: round($parent.offset().top) - $parent.scrollTop() } :
+            position == 'fixed' ?
+                { left: $(document).scrollLeft(), top: $(document).scrollTop() } :
+                { left: 0, top: 0 };
+
+        left = viewX(0);
+        top = viewY(0);
+
+        if (selection.x2 > imgWidth || selection.y2 > imgHeight)
+            doResize();
+    }
+
+    function update(resetKeyPress) {
+        if (!shown) return;
+
+        $box.css({ left: viewX(selection.x1), top: viewY(selection.y1) })
+            .add($area).width(w = selection.width).height(h = selection.height);
+
+        $area.add($border).add($handles).css({ left: 0, top: 0 });
+
+        $border
+            .width(max(w - $border.outerWidth() + $border.innerWidth(), 0))
+            .height(max(h - $border.outerHeight() + $border.innerHeight(), 0));
+
+        $($outer[0]).css({ left: left, top: top,
+            width: selection.x1, height: imgHeight });
+        $($outer[1]).css({ left: left + selection.x1, top: top,
+            width: w, height: selection.y1 });
+        $($outer[2]).css({ left: left + selection.x2, top: top,
+            width: imgWidth - selection.x2, height: imgHeight });
+        $($outer[3]).css({ left: left + selection.x1, top: top + selection.y2,
+            width: w, height: imgHeight - selection.y2 });
+
+        w -= $handles.outerWidth();
+        h -= $handles.outerHeight();
+
+        switch ($handles.length) {
+        case 8:
+            $($handles[4]).css({ left: w >> 1 });
+            $($handles[5]).css({ left: w, top: h >> 1 });
+            $($handles[6]).css({ left: w >> 1, top: h });
+            $($handles[7]).css({ top: h >> 1 });
+        case 4:
+            $handles.slice(1,3).css({ left: w });
+            $handles.slice(2,4).css({ top: h });
+        }
+
+        if (resetKeyPress !== false) {
+            if ($.imgAreaSelect.onKeyPress != docKeyPress)
+                $(document).unbind($.imgAreaSelect.keyPress,
+                    $.imgAreaSelect.onKeyPress);
+
+            if (options.keys)
+                $(document)[$.imgAreaSelect.keyPress](
+                    $.imgAreaSelect.onKeyPress = docKeyPress);
+        }
+
+        if (msie && $border.outerWidth() - $border.innerWidth() == 2) {
+            $border.css('margin', 0);
+            setTimeout(function () { $border.css('margin', 'auto'); }, 0);
+        }
+    }
+
+    function doUpdate(resetKeyPress) {
+        adjust();
+        update(resetKeyPress);
+        x1 = viewX(selection.x1); y1 = viewY(selection.y1);
+        x2 = viewX(selection.x2); y2 = viewY(selection.y2);
+    }
+
+    function hide($elem, fn) {
+        options.fadeSpeed ? $elem.fadeOut(options.fadeSpeed, fn) : $elem.hide();
+
+    }
+
+    function areaMouseMove(event) {
+        var x = selX(evX(event)) - selection.x1,
+            y = selY(evY(event)) - selection.y1;
+
+        if (!adjusted) {
+            adjust();
+            adjusted = true;
+
+            $box.one('mouseout', function () { adjusted = false; });
+        }
+
+        resize = '';
+
+        if (options.resizable) {
+            if (y <= options.resizeMargin)
+                resize = 'n';
+            else if (y >= selection.height - options.resizeMargin)
+                resize = 's';
+            if (x <= options.resizeMargin)
+                resize += 'w';
+            else if (x >= selection.width - options.resizeMargin)
+                resize += 'e';
+        }
+
+        $box.css('cursor', resize ? resize + '-resize' :
+            options.movable ? 'move' : '');
+        if ($areaOpera)
+            $areaOpera.toggle();
+    }
+
+    function docMouseUp(event) {
+        $('body').css('cursor', '');
+        if (options.autoHide || selection.width * selection.height == 0)
+            hide($box.add($outer), function () { $(this).hide(); });
+
+        $(document).unbind('mousemove', selectingMouseMove);
+        $box.mousemove(areaMouseMove);
+
+        options.onSelectEnd(img, getSelection());
+    }
+
+    function areaMouseDown(event) {
+        if (event.which != 1) return false;
+
+        adjust();
+
+        if (resize) {
+            $('body').css('cursor', resize + '-resize');
+
+            x1 = viewX(selection[/w/.test(resize) ? 'x2' : 'x1']);
+            y1 = viewY(selection[/n/.test(resize) ? 'y2' : 'y1']);
+
+            $(document).mousemove(selectingMouseMove)
+                .one('mouseup', docMouseUp);
+            $box.unbind('mousemove', areaMouseMove);
+        }
+        else if (options.movable) {
+            startX = left + selection.x1 - evX(event);
+            startY = top + selection.y1 - evY(event);
+
+            $box.unbind('mousemove', areaMouseMove);
+
+            $(document).mousemove(movingMouseMove)
+                .one('mouseup', function () {
+                    options.onSelectEnd(img, getSelection());
+
+                    $(document).unbind('mousemove', movingMouseMove);
+                    $box.mousemove(areaMouseMove);
+                });
+        }
+        else
+            $img.mousedown(event);
+
+        return false;
+    }
+
+    function fixAspectRatio(xFirst) {
+        if (aspectRatio)
+            if (xFirst) {
+                x2 = max(left, min(left + imgWidth,
+                    x1 + abs(y2 - y1) * aspectRatio * (x2 > x1 || -1)));
+
+                y2 = round(max(top, min(top + imgHeight,
+                    y1 + abs(x2 - x1) / aspectRatio * (y2 > y1 || -1))));
+                x2 = round(x2);
+            }
+            else {
+                y2 = max(top, min(top + imgHeight,
+                    y1 + abs(x2 - x1) / aspectRatio * (y2 > y1 || -1)));
+                x2 = round(max(left, min(left + imgWidth,
+                    x1 + abs(y2 - y1) * aspectRatio * (x2 > x1 || -1))));
+                y2 = round(y2);
+            }
+    }
+
+    function doResize() {
+        x1 = min(x1, left + imgWidth);
+        y1 = min(y1, top + imgHeight);
+
+        if (abs(x2 - x1) < minWidth) {
+            x2 = x1 - minWidth * (x2 < x1 || -1);
+
+            if (x2 < left)
+                x1 = left + minWidth;
+            else if (x2 > left + imgWidth)
+                x1 = left + imgWidth - minWidth;
+        }
+
+        if (abs(y2 - y1) < minHeight) {
+            y2 = y1 - minHeight * (y2 < y1 || -1);
+
+            if (y2 < top)
+                y1 = top + minHeight;
+            else if (y2 > top + imgHeight)
+                y1 = top + imgHeight - minHeight;
+        }
+
+        x2 = max(left, min(x2, left + imgWidth));
+        y2 = max(top, min(y2, top + imgHeight));
+
+        fixAspectRatio(abs(x2 - x1) < abs(y2 - y1) * aspectRatio);
+
+        if (abs(x2 - x1) > maxWidth) {
+            x2 = x1 - maxWidth * (x2 < x1 || -1);
+            fixAspectRatio();
+        }
+
+        if (abs(y2 - y1) > maxHeight) {
+            y2 = y1 - maxHeight * (y2 < y1 || -1);
+            fixAspectRatio(true);
+        }
+
+        selection = { x1: selX(min(x1, x2)), x2: selX(max(x1, x2)),
+            y1: selY(min(y1, y2)), y2: selY(max(y1, y2)),
+            width: abs(x2 - x1), height: abs(y2 - y1) };
+
+        update();
+
+        options.onSelectChange(img, getSelection());
+    }
+
+    function selectingMouseMove(event) {
+        x2 = /w|e|^$/.test(resize) || aspectRatio ? evX(event) : viewX(selection.x2);
+        y2 = /n|s|^$/.test(resize) || aspectRatio ? evY(event) : viewY(selection.y2);
+
+        doResize();
+
+        return false;
+
+    }
+
+    function doMove(newX1, newY1) {
+        x2 = (x1 = newX1) + selection.width;
+        y2 = (y1 = newY1) + selection.height;
+
+        $.extend(selection, { x1: selX(x1), y1: selY(y1), x2: selX(x2),
+            y2: selY(y2) });
+
+        update();
+
+        options.onSelectChange(img, getSelection());
+    }
+
+    function movingMouseMove(event) {
+        x1 = max(left, min(startX + evX(event), left + imgWidth - selection.width));
+        y1 = max(top, min(startY + evY(event), top + imgHeight - selection.height));
+
+        doMove(x1, y1);
+
+        event.preventDefault();
+
+        return false;
+    }
+
+    function startSelection() {
+        $(document).unbind('mousemove', startSelection);
+        adjust();
+
+        x2 = x1;
+        y2 = y1;
+
+        doResize();
+
+        resize = '';
+
+        if (!$outer.is(':visible'))
+            $box.add($outer).hide().fadeIn(options.fadeSpeed||0);
+
+        shown = true;
+
+        $(document).unbind('mouseup', cancelSelection)
+            .mousemove(selectingMouseMove).one('mouseup', docMouseUp);
+        $box.unbind('mousemove', areaMouseMove);
+
+        options.onSelectStart(img, getSelection());
+    }
+
+    function cancelSelection() {
+        $(document).unbind('mousemove', startSelection)
+            .unbind('mouseup', cancelSelection);
+        hide($box.add($outer));
+
+        setSelection(selX(x1), selY(y1), selX(x1), selY(y1));
+
+        if (!(this instanceof $.imgAreaSelect)) {
+            options.onSelectChange(img, getSelection());
+            options.onSelectEnd(img, getSelection());
+        }
+    }
+
+    function imgMouseDown(event) {
+        if (event.which != 1 || $outer.is(':animated')) return false;
+
+        adjust();
+        startX = x1 = evX(event);
+        startY = y1 = evY(event);
+
+        $(document).mousemove(startSelection).mouseup(cancelSelection);
+
+        return false;
+    }
+
+    function windowResize() {
+        doUpdate(false);
+    }
+
+    function imgLoad() {
+        imgLoaded = true;
+
+        setOptions(options = $.extend({
+            classPrefix: 'imgareaselect',
+            movable: true,
+            parent: 'body',
+            resizable: true,
+            resizeMargin: 10,
+            onInit: function () {},
+            onSelectStart: function () {},
+            onSelectChange: function () {},
+            onSelectEnd: function () {}
+        }, options));
+
+        $box.add($outer).css({ visibility: '' });
+
+        if (options.show) {
+            shown = true;
+            adjust();
+            update();
+            $box.add($outer).hide().fadeIn(options.fadeSpeed||0);
+        }
+
+        setTimeout(function () { options.onInit(img, getSelection()); }, 0);
+    }
+
+    var docKeyPress = function(event) {
+        var k = options.keys, d, t, key = event.keyCode;
+
+        d = !isNaN(k.alt) && (event.altKey || event.originalEvent.altKey) ? k.alt :
+            !isNaN(k.ctrl) && event.ctrlKey ? k.ctrl :
+            !isNaN(k.shift) && event.shiftKey ? k.shift :
+            !isNaN(k.arrows) ? k.arrows : 10;
+
+        if (k.arrows == 'resize' || (k.shift == 'resize' && event.shiftKey) ||
+            (k.ctrl == 'resize' && event.ctrlKey) ||
+            (k.alt == 'resize' && (event.altKey || event.originalEvent.altKey)))
+        {
+            switch (key) {
+            case 37:
+                d = -d;
+            case 39:
+                t = max(x1, x2);
+                x1 = min(x1, x2);
+                x2 = max(t + d, x1);
+                fixAspectRatio();
+                break;
+            case 38:
+                d = -d;
+            case 40:
+                t = max(y1, y2);
+                y1 = min(y1, y2);
+                y2 = max(t + d, y1);
+                fixAspectRatio(true);
+                break;
+            default:
+                return;
+            }
+
+            doResize();
+        }
+        else {
+            x1 = min(x1, x2);
+            y1 = min(y1, y2);
+
+            switch (key) {
+            case 37:
+                doMove(max(x1 - d, left), y1);
+                break;
+            case 38:
+                doMove(x1, max(y1 - d, top));
+                break;
+            case 39:
+                doMove(x1 + min(d, imgWidth - selX(x2)), y1);
+                break;
+            case 40:
+                doMove(x1, y1 + min(d, imgHeight - selY(y2)));
+                break;
+            default:
+                return;
+            }
+        }
+
+        return false;
+    };
+
+    function styleOptions($elem, props) {
+        for (var option in props)
+            if (options[option] !== undefined)
+                $elem.css(props[option], options[option]);
+    }
+
+    function setOptions(newOptions) {
+        if (newOptions.parent)
+            ($parent = $(newOptions.parent)).append($box.add($outer));
+
+        $.extend(options, newOptions);
+
+        adjust();
+
+        if (newOptions.handles != null) {
+            $handles.remove();
+            $handles = $([]);
+
+            i = newOptions.handles ? newOptions.handles == 'corners' ? 4 : 8 : 0;
+
+            while (i--)
+                $handles = $handles.add(div());
+
+            $handles.addClass(options.classPrefix + '-handle').css({
+                position: 'absolute',
+                fontSize: 0,
+                zIndex: zIndex + 1 || 1
+            });
+
+            if (!parseInt($handles.css('width')) >= 0)
+                $handles.width(5).height(5);
+
+            if (o = options.borderWidth)
+                $handles.css({ borderWidth: o, borderStyle: 'solid' });
+
+            styleOptions($handles, { borderColor1: 'border-color',
+                borderColor2: 'background-color',
+                borderOpacity: 'opacity' });
+        }
+
+        scaleX = options.imageWidth / imgWidth || 1;
+        scaleY = options.imageHeight / imgHeight || 1;
+
+        if (newOptions.x1 != null) {
+            setSelection(newOptions.x1, newOptions.y1, newOptions.x2,
+                newOptions.y2);
+            newOptions.show = !newOptions.hide;
+        }
+
+        if (newOptions.keys)
+            options.keys = $.extend({ shift: 1, ctrl: 'resize' },
+                newOptions.keys);
+
+        $outer.addClass(options.classPrefix + '-outer');
+        $area.addClass(options.classPrefix + '-selection');
+        for (i = 0; i++ < 4;)
+            $($border[i-1]).addClass(options.classPrefix + '-border' + i);
+
+        styleOptions($area, { selectionColor: 'background-color',
+            selectionOpacity: 'opacity' });
+        styleOptions($border, { borderOpacity: 'opacity',
+            borderWidth: 'border-width' });
+        styleOptions($outer, { outerColor: 'background-color',
+            outerOpacity: 'opacity' });
+        if (o = options.borderColor1)
+            $($border[0]).css({ borderStyle: 'solid', borderColor: o });
+        if (o = options.borderColor2)
+            $($border[1]).css({ borderStyle: 'dashed', borderColor: o });
+
+        $box.append($area.add($border).add($areaOpera)).append($handles);
+
+        if (msie) {
+            if (o = ($outer.css('filter')||'').match(/opacity=(\d+)/))
+                $outer.css('opacity', o[1]/100);
+            if (o = ($border.css('filter')||'').match(/opacity=(\d+)/))
+                $border.css('opacity', o[1]/100);
+        }
+
+        if (newOptions.hide)
+            hide($box.add($outer));
+        else if (newOptions.show && imgLoaded) {
+            shown = true;
+            $box.add($outer).fadeIn(options.fadeSpeed||0);
+            doUpdate();
+        }
+
+        aspectRatio = (d = (options.aspectRatio || '').split(/:/))[0] / d[1];
+
+        $img.add($outer).unbind('mousedown', imgMouseDown);
+
+        if (options.disable || options.enable === false) {
+            $box.unbind('mousemove', areaMouseMove).unbind('mousedown', areaMouseDown);
+            $(window).unbind('resize', windowResize);
+        }
+        else {
+            if (options.enable || options.disable === false) {
+                if (options.resizable || options.movable)
+                    $box.mousemove(areaMouseMove).mousedown(areaMouseDown);
+
+                $(window).resize(windowResize);
+            }
+
+            if (!options.persistent)
+                $img.add($outer).mousedown(imgMouseDown);
+        }
+
+        options.enable = options.disable = undefined;
+    }
+
+    this.remove = function () {
+        setOptions({ disable: true });
+        $box.add($outer).remove();
+    };
+
+    this.getOptions = function () { return options; };
+
+    this.setOptions = setOptions;
+
+    this.getSelection = getSelection;
+
+    this.setSelection = setSelection;
+
+    this.cancelSelection = cancelSelection;
+
+    this.update = doUpdate;
+
+    var msie = (/msie ([\w.]+)/i.exec(ua)||[])[1],
+        opera = /opera/i.test(ua),
+        safari = /webkit/i.test(ua) && !/chrome/i.test(ua);
+
+    $p = $img;
+
+    while ($p.length) {
+        zIndex = max(zIndex,
+            !isNaN($p.css('z-index')) ? $p.css('z-index') : zIndex);
+        if ($p.css('position') == 'fixed')
+            position = 'fixed';
+
+        $p = $p.parent(':not(body)');
+    }
+
+    zIndex = options.zIndex || zIndex;
+
+    if (msie)
+        $img.attr('unselectable', 'on');
+
+    $.imgAreaSelect.keyPress = msie || safari ? 'keydown' : 'keypress';
+
+    if (opera)
+
+        $areaOpera = div().css({ width: '100%', height: '100%',
+            position: 'absolute', zIndex: zIndex + 2 || 2 });
+
+    $box.add($outer).css({ visibility: 'hidden', position: position,
+        overflow: 'hidden', zIndex: zIndex || '0' });
+    $box.css({ zIndex: zIndex + 2 || 2 });
+    $area.add($border).css({ position: 'absolute', fontSize: 0 });
+
+    img.complete || img.readyState == 'complete' || !$img.is('img') ?
+        imgLoad() : $img.one('load', imgLoad);
+
+    if (!imgLoaded && msie && msie >= 7)
+        img.src = img.src;
+};
+
+$.fn.imgAreaSelect = function (options) {
+    options = options || {};
+
+    this.each(function () {
+        if ($(this).data('imgAreaSelect')) {
+            if (options.remove) {
+                $(this).data('imgAreaSelect').remove();
+                $(this).removeData('imgAreaSelect');
+            }
+            else
+                $(this).data('imgAreaSelect').setOptions(options);
+        }
+        else if (!options.remove) {
+            if (options.enable === undefined && options.disable === undefined)
+                options.enable = true;
+
+            $(this).data('imgAreaSelect', new $.imgAreaSelect(this, options));
+        }
+    });
+
+    if (options.instance)
+        return $(this).data('imgAreaSelect');
+
+    return this;
+};
+
+})(jQuery);
index 99ceb97..1223992 100644 (file)
@@ -50,6 +50,7 @@ $(function()
                 * TABS
                 */
         $('.tabs li').live('click', function(event, callback) {
+            event.preventDefault();
                        $.wiki.switchToTab(this);
         });
 
index c22e9d7..a859ec6 100644 (file)
@@ -24,6 +24,7 @@ $(function()
                 * TABS
                 */
         $('#tabs li').live('click', function(event, callback) {
+            event.preventDefault();
                        $.wiki.switchToTab(this);
         });
 
index 8fb9358..547456f 100644 (file)
@@ -19,7 +19,7 @@
                                lineNumbers: true,
                                width: "100%",
                                height: "100%",
-                               tabMode: 'spaces',
+                               tabMode: 'default',
                                indentUnit: 0,
                                readOnly: CurrentDocument.readonly || false,
                                initCallback: function(){
diff --git a/redakcja/static/js/wiki_img/base.js b/redakcja/static/js/wiki_img/base.js
new file mode 100644 (file)
index 0000000..ffe5a01
--- /dev/null
@@ -0,0 +1,329 @@
+(function($)
+{
+       var noop = function() { };
+
+       $.wiki = {
+               perspectives: {},
+               cls: {},
+               state: {
+                       "version": 1,
+                       "perspectives": {
+                "CodeMirrorPerspective": {}
+                       }
+               }
+       };
+
+       $.wiki.loadConfig = function() {
+               if(!window.localStorage)
+                       return;
+
+               try {
+                       var value = window.localStorage.getItem(CurrentDocument.id) || "{}";
+                       var config = JSON.parse(value);
+
+                       if (config.version == $.wiki.state.version) {
+                               $.wiki.state.perspectives = $.extend($.wiki.state.perspectives, config.perspectives);
+                       }
+               } catch(e) {
+                       console.log("Failed to load config, using default.");
+               }
+
+               console.log("Loaded:", $.wiki.state, $.wiki.state.version);
+       };
+
+       $(window).bind('unload', function() {
+               if(window.localStorage)
+                       window.localStorage.setItem(CurrentDocument.id, JSON.stringify($.wiki.state));
+       })
+
+
+       $.wiki.activePerspective = function() {
+               return this.perspectives[$("#tabs li.active").attr('id')];
+       };
+
+       $.wiki.exitContext = function() {
+               var ap = this.activePerspective();
+               if(ap) ap.onExit();
+               return ap;
+       };
+
+       $.wiki.enterContext = function(ap) {
+               if(ap) ap.onEnter();
+       };
+
+       $.wiki.isDirty = function() {
+               var ap = this.activePerspective();
+               return (!!CurrentDocument && CurrentDocument.has_local_changes) || ap.dirty();
+       };
+
+       $.wiki.newTab = function(doc, title, klass) {
+               var base_id = 'id' + Math.floor(Math.random()* 5000000000);
+               var id = (''+klass)+'_' + base_id;
+               var $tab = $('<li id="'+id+'" data-ui-related="'+base_id+'" data-ui-jsclass="'+klass+'" >'
+                               + title + '<img src="'+STATIC_URL+'icons/close.png" class="tabclose"></li>');
+               var $view = $('<div class="editor '+klass+'" id="'+base_id+'"> </div>');
+
+               this.perspectives[id] = new $.wiki[klass]({
+                       doc: doc,
+                       id: id,
+                       base_id: base_id,
+               });
+
+               $('#tabs').append($tab);
+               $view.hide().appendTo('#editor');
+               return {
+                       tab: $tab[0],
+                       view: $view[0],
+               };
+       };
+
+       $.wiki.initTab = function(options) {
+               var klass = $(options.tab).attr('data-ui-jsclass');
+
+               return new $.wiki[klass]({
+                       doc: options.doc,
+                       id: $(options.tab).attr('id'),
+                       callback: function() {
+                               $.wiki.perspectives[this.perspective_id] = this;
+                               if(options.callback)
+                                       options.callback.call(this);
+                       }
+               });
+       };
+
+       $.wiki.perspectiveForTab = function(tab) { // element or id
+               return this.perspectives[ $(tab).attr('id')];
+       }
+
+       $.wiki.switchToTab = function(tab){
+               var self = this;
+               var $tab = $(tab);
+
+               if($tab.length != 1)
+                       $tab = $(DEFAULT_PERSPECTIVE);
+
+               var $old = $tab.closest('.tabs').find('.active');
+
+               $old.each(function(){
+                       $(this).removeClass('active');
+                       self.perspectives[$(this).attr('id')].onExit();
+                       $('#' + $(this).attr('data-ui-related')).hide();
+               });
+
+               /* show new */
+               $tab.addClass('active');
+               $('#' + $tab.attr('data-ui-related')).show();
+
+               console.log($tab);
+               console.log($.wiki.perspectives);
+
+               $.wiki.perspectives[$tab.attr('id')].onEnter();
+       };
+
+       /*
+        * Basic perspective.
+        */
+       $.wiki.Perspective = function(options) {
+               if(!options) return;
+
+               this.doc = options.doc;
+               if (options.id) {
+                       this.perspective_id = options.id;
+               }
+               else {
+                       this.perspective_id = '';
+               }
+
+               if(options.callback)
+                       options.callback.call(this);
+       };
+
+       $.wiki.Perspective.prototype.config = function() {
+               return $.wiki.state.perspectives[this.perspective_id];
+       }
+
+       $.wiki.Perspective.prototype.toString = function() {
+               return this.perspective_id;
+       };
+
+       $.wiki.Perspective.prototype.dirty = function() {
+               return true;
+       };
+
+       $.wiki.Perspective.prototype.onEnter = function () {
+               // called when perspective in initialized
+               if (!this.noupdate_hash_onenter) {
+                       document.location.hash = '#' + this.perspective_id;
+               }
+       };
+
+       $.wiki.Perspective.prototype.onExit = function () {
+               // called when user switches to another perspective
+               if (!this.noupdate_hash_onenter) {
+                       document.location.hash = '';
+               }
+       };
+
+       $.wiki.Perspective.prototype.destroy = function() {
+               // pass
+       };
+
+       $.wiki.Perspective.prototype.freezeState = function () {
+               // free UI state (don't store data here)
+       };
+
+       $.wiki.Perspective.prototype.unfreezeState = function (frozenState) {
+               // restore UI state
+       };
+
+       /*
+        * Stub rendering (used in generating history)
+        */
+       $.wiki.renderStub = function(params)
+       {
+               params = $.extend({ 'filters': {} }, params);
+               var $elem = params.stub.clone();
+               $elem.removeClass('row-stub');
+               params.container.append($elem);
+
+               $('*[data-stub-value]', $elem).each(function() {
+                       var $this = $(this);
+                       var field = $this.attr('data-stub-value');
+
+                       var value = params.data[field];
+
+                       if(params.filters[field])
+                               value = params.filters[field](value);
+
+                       if(value === null || value === undefined) return;
+
+                       if(!$this.attr('data-stub-target')) {
+                               $this.text(value);
+                       }
+                       else {
+                               $this.attr($this.attr('data-stub-target'), value);
+                               $this.removeAttr('data-stub-target');
+                               $this.removeAttr('data-stub-value');
+                       }
+               });
+
+               $elem.show();
+               return $elem;
+       };
+
+       /*
+        * Dialogs
+        */
+       function GenericDialog(element) {
+               if(!element) return;
+
+               var self = this;
+
+               self.$elem = $(element);
+
+               if(!self.$elem.attr('data-ui-initialized')) {
+                       console.log("Initializing dialog", this);
+                       self.initialize();
+                       self.$elem.attr('data-ui-initialized', true);
+               }
+
+               self.show();
+       };
+
+       GenericDialog.prototype = {
+
+               /*
+               * Steps to follow when the dialog in first loaded on page.
+               */
+               initialize: function(){
+                       var self = this;
+
+                       /* bind buttons */
+                       $('button[data-ui-action]', self.$elem).click(function(event) {
+                               event.preventDefault();
+
+                               var action = $(this).attr('data-ui-action');
+                               console.log("Button pressed, action: ", action);
+
+                               try {
+                                       self[action + "Action"].call(self);
+                               } catch(e) {
+                                       console.log("Action failed:", e);
+                                       // always hide on cancel
+                                       if(action == 'cancel')
+                                               self.hide();
+                               }
+                       });
+               },
+
+               /*
+                * Prepare dialog for user. Clear any unnessary data.
+               */
+               show: function() {
+                       $.blockUI({
+                               message: this.$elem,
+                               css: {
+                                       'top': '25%',
+                                       'left': '25%',
+                                       'width': '50%'
+                               }
+                       });
+               },
+
+               hide: function(){
+                       $.unblockUI();
+               },
+
+               cancelAction: function() {
+                       this.hide();
+               },
+
+               doneAction: function() {
+                       this.hide();
+               },
+
+               clearForm: function() {
+                       $("*[data-ui-error-for]", this.$elem).text('');
+               },
+
+               reportErrors: function(errors) {
+                       var global = $("*[data-ui-error-for='__all__']", this.$elem);
+                       var unassigned = [];
+
+                       for (var field_name in errors)
+                       {
+                               var span = $("*[data-ui-error-for='"+field_name+"']", this.$elem);
+
+                               if(!span.length) {
+                                       unassigned.push(field_name);
+                                       continue;
+                               }
+
+                               span.text(errors[field_name].join(' '));
+                       }
+
+                       if(unassigned.length > 0)
+                               global.text( global.text() + 'W formularzu wystąpiły błędy');
+               }
+       };
+
+       $.wiki.cls.GenericDialog = GenericDialog;
+
+       $.wiki.showDialog = function(selector, options) {
+               var elem = $(selector);
+
+               if(elem.length != 1) {
+                       console.log("Failed to show dialog:", selector, elem);
+                       return false;
+               }
+
+               try {
+                   var klass = elem.attr('data-ui-jsclass');
+                       return new $.wiki.cls[klass](elem, options);
+               } catch(e) {
+                       console.log("Failed to show dialog", selector, klass, e);
+                       return false;
+               }
+       };
+
+})(jQuery);
diff --git a/redakcja/static/js/wiki_img/loader.js b/redakcja/static/js/wiki_img/loader.js
new file mode 100644 (file)
index 0000000..c3fe03e
--- /dev/null
@@ -0,0 +1,137 @@
+if (!window.console) {
+    window.console = {
+        log: function(){
+        }
+    }
+}
+
+DEFAULT_PERSPECTIVE = "#MotifsPerspective";
+
+$(function()
+{
+       var tabs = $('ol#tabs li');
+       var gallery = null;
+       CurrentDocument = new $.wikiapi.WikiDocument("document-meta");
+
+       $.blockUI.defaults.baseZ = 10000;
+
+    function initialize()
+       {
+               $(document).keydown(function(event) {
+                       console.log("Received key:", event);
+               });
+
+               /* The save button */
+        $('#save-button').click(function(event){
+            event.preventDefault();
+                       $.wiki.showDialog('#save_dialog');
+        });
+
+               $('.editor').hide();
+
+               /*
+                * TABS
+                */
+        $('.tabs li').live('click', function(event, callback) {
+            event.preventDefault();
+                       $.wiki.switchToTab(this);
+        });
+
+               $('#tabs li > .tabclose').live('click', function(event, callback) {
+                       var $tab = $(this).parent();
+
+                       if($tab.is('.active'))
+                               $.wiki.switchToTab(DEFAULT_PERSPECTIVE);
+
+                       var p = $.wiki.perspectiveForTab($tab);
+                       p.destroy();
+
+                       return false;
+        });
+
+
+        /*$(window).resize(function(){
+            $('iframe').height($(window).height() - $('#tabs').outerHeight() - $('#source-editor .toolbar').outerHeight());
+        });
+
+        $(window).resize();*/
+
+        /*$('.vsplitbar').toggle(
+                       function() {
+                               $.wiki.state.perspectives.ScanGalleryPerspective.show = true;
+                               $('#sidebar').show();
+                               $('.vsplitbar').css('right', 480).addClass('active');
+                               $('#editor .editor').css('right', 510);
+                               $(window).resize();
+                               $.wiki.perspectiveForTab('#tabs-right .active').onEnter();
+                       },
+                       function() {
+                           var active_right = $.wiki.perspectiveForTab('#tabs-right .active');
+                               $.wiki.state.perspectives.ScanGalleryPerspective.show = false;
+                               $('#sidebar').hide();
+                               $('.vsplitbar').css('right', 0).removeClass('active');
+                               $(".vsplitbar-title").html("&uarr;&nbsp;" + active_right.vsplitbar + "&nbsp;&uarr;");
+                               $('#editor .editor').css('right', 30);
+                               $(window).resize();
+                               active_right.onExit();
+                       }
+               );*/
+
+        window.onbeforeunload = function(e) {
+            if($.wiki.isDirty()) {
+                               e.returnValue = "Na stronie mogą być nie zapisane zmiany.";
+                               return "Na stronie mogą być nie zapisane zmiany.";
+                       };
+        };
+
+               console.log("Fetching document's text");
+
+               $(document).bind('wlapi_document_changed', function(event, doc) {
+                       try {
+                               $('#document-revision').text(doc.revision);
+                       } catch(e) {
+                               console.log("Failed handler", e);
+                       }
+               });
+
+               CurrentDocument.fetch({
+                       success: function(){
+                               console.log("Fetch success");
+                               $('#loading-overlay').fadeOut();
+                               var active_tab = document.location.hash || DEFAULT_PERSPECTIVE;
+
+                               console.log("Initial tab is:", active_tab)
+                               $.wiki.switchToTab(active_tab);
+                       },
+                       failure: function() {
+                               $('#loading-overlay').fadeOut();
+                               alert("FAILURE");
+                       }
+               });
+    }; /* end of initialize() */
+
+
+       /* Load configuration */
+       $.wiki.loadConfig();
+
+       var initAll = function(a, f) {
+               if (a.length == 0) return f();
+
+               $.wiki.initTab({
+                       tab: a.pop(),
+                       doc: CurrentDocument,
+                       callback: function(){
+                               initAll(a, f);
+                       }
+               });
+       };
+
+
+       /*
+        * Initialize all perspectives
+        */
+       initAll( $.makeArray($('.tabs li')), initialize);
+       console.log(location.hash);
+});
+
+
diff --git a/redakcja/static/js/wiki_img/loader_readonly.js b/redakcja/static/js/wiki_img/loader_readonly.js
new file mode 100755 (executable)
index 0000000..99e5ad0
--- /dev/null
@@ -0,0 +1,93 @@
+if (!window.console) {
+    window.console = {
+        log: function(){
+        }
+    }
+}
+
+
+DEFAULT_PERSPECTIVE = "#MotifsPerspective";
+
+
+$(function()
+{
+       var tabs = $('ol#tabs li');
+       var gallery = null;
+
+       CurrentDocument = new $.wikiapi.WikiDocument("document-meta");
+       $.blockUI.defaults.baseZ = 10000;
+
+       function initialize()
+       {
+               $('.editor').hide();
+
+               /*
+                * TABS
+                */
+        $('#tabs li').live('click', function(event, callback) {
+            event.preventDefault();
+                       $.wiki.switchToTab(this);
+        });
+
+               $('#tabs li > .tabclose').live('click', function(event, callback) {
+                       var $tab = $(this).parent();
+
+                       if($tab.is('.active'))
+                               $.wiki.switchToTab(DEFAULT_PERSPECTIVE);
+
+                       var p = $.wiki.perspectiveForTab($tab);
+                       p.destroy();
+                       return false;
+        });
+/*
+        $(window).resize(function(){
+            $('iframe').height($(window).height() - $('#tabs').outerHeight() - $('#source-editor .toolbar').outerHeight());
+        });
+        */
+
+               $(document).bind('wlapi_document_changed', function(event, doc) {
+                       try {
+                               $('#document-revision').text(doc.revision);
+                       } catch(e) {
+                               console.log("Failed handler", e);
+                       }
+               });
+
+               CurrentDocument.fetch({
+                       success: function(){
+                               console.log("Fetch success");
+                               $('#loading-overlay').fadeOut();
+                               var active_tab = document.location.hash || DEFAULT_PERSPECTIVE;
+
+                               console.log("Initial tab is:", active_tab)
+                               $.wiki.switchToTab(active_tab);
+                       },
+                       failure: function() {
+                               $('#loading-overlay').fadeOut();
+                               alert("FAILURE");
+                       }
+               });
+    }; /* end of initialize() */
+
+       /* Load configuration */
+       $.wiki.loadConfig();
+
+       var initAll = function(a, f) {
+               if (a.length == 0) return f();
+
+               $.wiki.initTab({
+                       tab: a.pop(),
+                       doc: CurrentDocument,
+                       callback: function(){
+                               initAll(a, f);
+                       }
+               });
+       };
+
+
+       /*
+        * Initialize all perspectives
+        */
+       initAll( $.makeArray($('ol#tabs li')), initialize);
+       console.log(location.hash);
+});
diff --git a/redakcja/static/js/wiki_img/view_editor_motifs.js b/redakcja/static/js/wiki_img/view_editor_motifs.js
new file mode 100644 (file)
index 0000000..11cbef0
--- /dev/null
@@ -0,0 +1,151 @@
+(function($){
+
+    function MotifsPerspective(options){
+
+        var old_callback = options.callback;
+
+        options.callback = function(){
+            var self = this;
+
+            self.$tag_name = $('#motifs-editor .tag-name');
+            withThemes(function(canonThemes){
+                self.$tag_name.autocomplete(canonThemes, {
+                    autoFill: true,
+                    multiple: true,
+                    selectFirst: true,
+                    highlight: false
+                });
+            })
+
+            self.$objects_list = $('#motifs-editor .objects-list');
+
+            self.x1 = null;
+            self.x2 = null;
+            self.y1 = null;
+            self.y2 = null;
+
+            if (!CurrentDocument.readonly) {
+                self.ias = $('#motifs-editor img.area-selectable').imgAreaSelect({ handles: true, onSelectEnd: self._fillCoords(self), instance: true });
+                $('#motifs-editor .add').click(self._addObject(self));
+
+                $('.delete', self.$objects_list).live('click', function() {
+                    $(this).prev().trigger('click');
+                    if (window.confirm("Czy na pewno chcesz usunąć ten motyw?")) {
+                        $(this).prev().remove();
+                        $(this).remove();
+                    }
+                    self._resetSelection();
+                    return false;
+                });
+            }
+
+            $('.image-object', self.$objects_list).live('click', function(){
+                $('.active', self.$objects_list).removeClass('active');
+                $(this).addClass('active');
+                var coords = $(this).data('coords');
+                if (coords) {
+                    self.ias.setSelection.apply(self.ias, coords);
+                    self.ias.setOptions({ show: true });
+                }
+                else {
+                    self._resetSelection();
+                }
+            });
+
+            old_callback.call(this);
+        };
+
+        $.wiki.Perspective.call(this, options);
+    };
+
+    MotifsPerspective.prototype = new $.wiki.Perspective();
+
+    MotifsPerspective.prototype.freezeState = function(){
+
+    };
+
+    MotifsPerspective.prototype._resetSelection = function() {
+        var self = this;
+        self.x1 = self.x2 = self.y1 = self.y2 = null;
+        self.ias.setOptions({ hide: true });
+    }
+
+
+    MotifsPerspective.prototype._push = function(name, x1, y1, x2, y2) {
+        var $e = $('<span class="image-object"></span>')
+        $e.text(name);
+        if (x1 !== null)
+            $e.data('coords', [x1, y1, x2, y2]);
+        this.$objects_list.append($e);
+        this.$objects_list.append('<span class="delete">(x)</span>');
+    }
+
+
+    MotifsPerspective.prototype._addObject = function(self) {
+        return function() {
+            outputs = [];
+            chunks = self.$tag_name.val().split(',');
+            for (i in chunks) {
+                item = chunks[i].trim();
+                if (item == '')
+                    continue;
+                outputs.push(item.trim());
+            }
+            output = outputs.join(', ');
+
+            self._push(output, self.x1, self.y1, self.x2, self.y2);
+            self._resetSelection();
+        }
+    }
+
+    MotifsPerspective.prototype._fillCoords = function(self) {
+        return function(img, selection) {
+            $('.active', self.$objects_list).removeClass('active');
+            if (selection.x1 != selection.x2 && selection.y1 != selection.y2) {
+                self.x1 = selection.x1;
+                self.x2 = selection.x2;
+                self.y1 = selection.y1;
+                self.y2 = selection.y2;
+            }
+            else {
+                self.x1 = self.x2 = self.y1 = self.y2 = null;
+            }
+        }
+    }
+
+    MotifsPerspective.prototype.onEnter = function(success, failure){
+        var self = this;
+        this.$objects_list.children().remove();
+
+        $.each(this.doc.getImageItems('theme'), function(i, e) {
+            self._push.apply(self, e);
+        });
+
+        if (this.x1 !== null)
+            this.ias.setOptions({enable: true, show: true});
+        else
+            this.ias.setOptions({enable: true});
+
+        $.wiki.Perspective.prototype.onEnter.call(this);
+
+    };
+
+    MotifsPerspective.prototype.onExit = function(success, failure){
+        var self = this;
+        var motifs = [];
+        this.$objects_list.children(".image-object").each(function(i, e) {
+            var args = $(e).data('coords');
+            if (!args)
+                args = [null, null, null, null];
+            args.unshift($(e).text());
+            motifs.push(args);
+        })
+        self.doc.setImageItems('theme', motifs);
+
+        this.ias.setOptions({disable: true, hide: true});
+
+    };
+
+    $.wiki.MotifsPerspective = MotifsPerspective;
+
+})(jQuery);
diff --git a/redakcja/static/js/wiki_img/view_editor_objects.js b/redakcja/static/js/wiki_img/view_editor_objects.js
new file mode 100644 (file)
index 0000000..5cf014f
--- /dev/null
@@ -0,0 +1,142 @@
+(function($){
+
+    function ObjectsPerspective(options){
+
+        var old_callback = options.callback;
+
+        options.callback = function(){
+            var self = this;
+
+            self.$tag_name = $('#objects-editor .tag-name');
+            self.$objects_list = $('#objects-editor .objects-list');
+
+            self.x1 = null;
+            self.x2 = null;
+            self.y1 = null;
+            self.y2 = null;
+
+            if (!CurrentDocument.readonly) {
+                self.ias = $('#objects-editor img.area-selectable').imgAreaSelect({ handles: true, onSelectEnd: self._fillCoords(self), instance: true });
+                $('#objects-editor .add').click(self._addObject(self));
+
+                $('.delete', self.$objects_list).live('click', function() {
+                    $(this).prev().trigger('click');
+                    if (window.confirm("Czy na pewno chcesz usunąć ten obiekt?")) {
+                        $(this).prev().remove();
+                        $(this).remove();
+                    }
+                    self._resetSelection();
+                    return false;
+                });
+            }
+
+            $('.image-object', self.$objects_list).live('click', function(){
+                $('.active', self.$objects_list).removeClass('active');
+                $(this).addClass('active');
+                var coords = $(this).data('coords');
+                if (coords) {
+                    self.ias.setSelection.apply(self.ias, coords);
+                    self.ias.setOptions({ show: true });
+                }
+                else {
+                    self._resetSelection();
+                }
+            });
+
+            old_callback.call(this);
+        };
+
+        $.wiki.Perspective.call(this, options);
+    };
+
+    ObjectsPerspective.prototype = new $.wiki.Perspective();
+
+    ObjectsPerspective.prototype.freezeState = function(){
+
+    };
+
+    ObjectsPerspective.prototype._resetSelection = function() {
+        var self = this;
+        self.x1 = self.x2 = self.y1 = self.y2 = null;
+        self.ias.setOptions({ hide: true });
+    }
+
+
+    ObjectsPerspective.prototype._push = function(name, x1, y1, x2, y2) {
+        var $e = $('<span class="image-object"></span>')
+        $e.text(name);
+        if (x1 !== null)
+            $e.data('coords', [x1, y1, x2, y2]);
+        this.$objects_list.append($e);
+        this.$objects_list.append('<span class="delete">(x)</span>');
+    }
+
+
+    ObjectsPerspective.prototype._addObject = function(self) {
+        return function() {
+            outputs = [];
+            chunks = self.$tag_name.val().split(',');
+            for (i in chunks) {
+                item = chunks[i].trim();
+                if (item == '')
+                    continue;
+                outputs.push(item.trim());
+            }
+            output = outputs.join(', ');
+
+            self._push(output, self.x1, self.y1, self.x2, self.y2);
+            self._resetSelection();
+        }
+    }
+
+    ObjectsPerspective.prototype._fillCoords = function(self) {
+        return function(img, selection) {
+            $('.active', self.$objects_list).removeClass('active');
+            if (selection.x1 != selection.x2 && selection.y1 != selection.y2) {
+                self.x1 = selection.x1;
+                self.x2 = selection.x2;
+                self.y1 = selection.y1;
+                self.y2 = selection.y2;
+            }
+            else {
+                self.x1 = self.x2 = self.y1 = self.y2 = null;
+            }
+        }
+    }
+
+    ObjectsPerspective.prototype.onEnter = function(success, failure){
+        var self = this;
+        this.$objects_list.children().remove();
+
+        $.each(this.doc.getImageItems('object'), function(i, e) {
+            self._push.apply(self, e);
+        });
+
+        if (this.x1 !== null)
+            this.ias.setOptions({enable: true, show: true});
+        else
+            this.ias.setOptions({enable: true});
+
+        $.wiki.Perspective.prototype.onEnter.call(this);
+
+    };
+
+    ObjectsPerspective.prototype.onExit = function(success, failure){
+        var self = this;
+        var objects = [];
+        this.$objects_list.children(".image-object").each(function(i, e) {
+            var args = $(e).data('coords');
+            if (!args)
+                args = [null, null, null, null];
+            args.unshift($(e).text());
+            objects.push(args);
+        })
+        self.doc.setImageItems('object', objects);
+
+        this.ias.setOptions({disable: true, hide: true});
+
+    };
+
+    $.wiki.ObjectsPerspective = ObjectsPerspective;
+
+})(jQuery);
diff --git a/redakcja/static/js/wiki_img/wikiapi.js b/redakcja/static/js/wiki_img/wikiapi.js
new file mode 100644 (file)
index 0000000..81cd316
--- /dev/null
@@ -0,0 +1,406 @@
+(function($) {
+       $.wikiapi = {};
+       var noop = function() {
+       };
+       var noops = {
+               success: noop,
+               failure: noop
+       };
+       /*
+        * Return absolute reverse path of given named view. (at least he have it
+        * hard-coded in one place)
+        *
+        * TODO: think of a way, not to hard-code it here ;)
+        *
+        */
+       function reverse() {
+               var vname = arguments[0];
+               var base_path = "/images";
+
+               if (vname == "ajax_document_text") {
+                       return base_path + "/text/" + arguments[1] + "/";
+               }
+
+        if (vname == "ajax_document_revert") {
+            return base_path + "/revert/" + arguments[1] + '/';
+        }
+
+               if (vname == "ajax_document_history") {
+                       return base_path + "/history/" + arguments[1] + '/';
+               }
+
+               if (vname == "ajax_document_diff")
+                       return base_path + "/diff/" + arguments[1] + '/';
+
+               if (vname == "ajax_document_pubmark")
+                       return base_path + "/pubmark/" + arguments[1] + '/';
+
+               console.log("Couldn't reverse match:", vname);
+               return "/404.html";
+       };
+
+       /*
+        * Document Abstraction
+        */
+       function WikiDocument(element_id) {
+               var meta = $('#' + element_id);
+               this.id = meta.attr('data-object-id');
+
+               this.revision = $("*[data-key='revision']", meta).text();
+               this.readonly = !!$("*[data-key='readonly']", meta).text();
+
+               this.text = null;
+               this.has_local_changes = false;
+               this._lock = -1;
+               this._context_lock = -1;
+               this._lock_count = 0;
+       };
+
+       WikiDocument.prototype.triggerDocumentChanged = function() {
+               $(document).trigger('wlapi_document_changed', this);
+       };
+       /*
+        * Fetch text of this document.
+        */
+       WikiDocument.prototype.fetch = function(params) {
+               params = $.extend({}, noops, params);
+               var self = this;
+               $.ajax({
+                       method: "GET",
+                       url: reverse("ajax_document_text", self.id),
+                       data: {"commit": self.commit},
+                       dataType: 'json',
+                       success: function(data) {
+                               var changed = false;
+
+                               if (self.text === null || self.commit !== data.commit) {
+                                       self.text = data.text;
+                                       if (self.text === '') {
+                                           self.text = '<picture></picture>';
+                                       }
+                                       self.revision = data.revision;
+                    self.commit = data.commit;
+                                       changed = true;
+                                       self.triggerDocumentChanged();
+                               };
+
+                               self.has_local_changes = false;
+                               params['success'](self, changed);
+                       },
+                       error: function() {
+                               params['failure'](self, "Nie udało się wczytać treści dokumentu.");
+                       }
+               });
+       };
+       /*
+        * Fetch history of this document.
+        *
+        * from - First revision to fetch (default = 0) upto - Last revision to
+        * fetch (default = tip)
+        *
+        */
+       WikiDocument.prototype.fetchHistory = function(params) {
+               /* this doesn't modify anything, so no locks */
+               params = $.extend({}, noops, params);
+               var self = this;
+               $.ajax({
+                       method: "GET",
+                       url: reverse("ajax_document_history", self.id),
+                       dataType: 'json',
+                       data: {
+                               "from": params['from'],
+                               "upto": params['upto']
+                       },
+                       success: function(data) {
+                               params['success'](self, data);
+                       },
+                       error: function() {
+                               params['failure'](self, "Nie udało się wczytać historii dokumentu.");
+                       }
+               });
+       };
+       WikiDocument.prototype.fetchDiff = function(params) {
+               /* this doesn't modify anything, so no locks */
+               var self = this;
+               params = $.extend({
+                       'from': self.revision,
+                       'to': self.revision
+               }, noops, params);
+               $.ajax({
+                       method: "GET",
+                       url: reverse("ajax_document_diff", self.id),
+                       dataType: 'html',
+                       data: {
+                               "from": params['from'],
+                               "to": params['to']
+                       },
+                       success: function(data) {
+                               params['success'](self, data);
+                       },
+                       error: function() {
+                               params['failure'](self, "Nie udało się wczytać porównania wersji.");
+                       }
+               });
+       };
+
+       /*
+        * Set document's text
+        */
+       WikiDocument.prototype.setText = function(text) {
+               this.text = text;
+               this.has_local_changes = true;
+       };
+
+       /*
+        * Save text back to the server
+        */
+       WikiDocument.prototype.save = function(params) {
+               params = $.extend({}, noops, params);
+               var self = this;
+
+               if (!self.has_local_changes) {
+                       console.log("Abort: no changes.");
+                       return params['success'](self, false, "Nie ma zmian do zapisania.");
+               };
+
+               // Serialize form to dictionary
+               var data = {};
+               $.each(params['form'].serializeArray(), function() {
+                       data[this.name] = this.value;
+               });
+
+               data['textsave-text'] = self.text;
+
+               $.ajax({
+                       url: reverse("ajax_document_text", self.id),
+                       type: "POST",
+                       dataType: "json",
+                       data: data,
+                       success: function(data) {
+                               var changed = false;
+
+                $('#header').removeClass('saving');
+
+                               if (data.text) {
+                                       self.text = data.text;
+                                       self.revision = data.revision;
+                    self.commit = data.commit;
+                                       changed = true;
+                                       self.triggerDocumentChanged();
+                               };
+
+                               params['success'](self, changed, ((changed && "Udało się zapisać :)") || "Twoja wersja i serwera jest identyczna"));
+                       },
+                       error: function(xhr) {
+                if ($('#header').hasClass('saving')) {
+                    $('#header').removeClass('saving');
+                    $.blockUI({
+                        message: "<p>Nie udało się zapisać zmian. <br/><button onclick='$.unblockUI()'>OK</button></p>"
+                    })
+                }
+                else {
+                    try {
+                        params['failure'](self, $.parseJSON(xhr.responseText));
+                    }
+                    catch (e) {
+                        params['failure'](self, {
+                            "__message": "<p>Nie udało się zapisać - błąd serwera.</p>"
+                        });
+                    };
+                }
+
+                       }
+               });
+
+        $('#save-hide').click(function(){
+            $('#header').addClass('saving');
+            $.unblockUI();
+            $.wiki.blocking.unblock();
+        });
+       }; /* end of save() */
+
+    WikiDocument.prototype.revertToVersion = function(params) {
+        var self = this;
+        params = $.extend({}, noops, params);
+
+        if (params.revision >= this.revision) {
+            params.failure(self, 'Proszę wybrać rewizję starszą niż aktualna.');
+            return;
+        }
+
+        // Serialize form to dictionary
+        var data = {};
+        $.each(params['form'].serializeArray(), function() {
+            data[this.name] = this.value;
+        });
+
+        $.ajax({
+            url: reverse("ajax_document_revert", self.id),
+            type: "POST",
+            dataType: "json",
+            data: data,
+            success: function(data) {
+                if (data.text) {
+                    self.text = data.text;
+                    self.revision = data.revision;
+                    self.gallery = data.gallery;
+                    self.triggerDocumentChanged();
+
+                    params.success(self, "Udało się przywrócić wersję :)");
+                }
+                else {
+                    params.failure(self, "Przywracana wersja identyczna z aktualną. Anulowano przywracanie.");
+                }
+            },
+            error: function(xhr) {
+                params.failure(self, "Nie udało się przywrócić wersji - błąd serwera.");
+            }
+        });
+    };
+
+       WikiDocument.prototype.pubmark = function(params) {
+               params = $.extend({}, noops, params);
+               var self = this;
+               var data = {
+                       "pubmark-id": self.id,
+               };
+
+               /* unpack form */
+               $.each(params.form.serializeArray(), function() {
+                       data[this.name] = this.value;
+               });
+
+               $.ajax({
+                       url: reverse("ajax_document_pubmark", self.id),
+                       type: "POST",
+                       dataType: "json",
+                       data: data,
+                       success: function(data) {
+                               params.success(self, data.message);
+                       },
+                       error: function(xhr) {
+                               if (xhr.status == 403 || xhr.status == 401) {
+                                       params.failure(self, {
+                                               "__all__": ["Nie masz uprawnień lub nie jesteś zalogowany."]
+                                       });
+                               }
+                               else {
+                                       try {
+                                               params.failure(self, $.parseJSON(xhr.responseText));
+                                       }
+                                       catch (e) {
+                                               params.failure(self, {
+                                                       "__all__": ["Nie udało się - błąd serwera."]
+                                               });
+                                       };
+                               };
+                       }
+               });
+       };
+
+
+
+    WikiDocument.prototype.getImageItems = function(tag) {
+        var self = this;
+
+        var parser = new DOMParser();
+        var doc = parser.parseFromString(self.text, 'text/xml');
+        var error = $('parsererror', doc);
+
+        if (error.length != 0) {
+            return null;
+        }
+
+        var a = [];
+        $('sem[type="'+tag+'"]', doc).each(function(i, e) {
+            var $e = $(e);
+            var $div = $e.children().first()
+            var value = $e.attr(tag);
+            $e.find('div').each(function(i, div) {
+                var $div = $(div);
+                switch ($div.attr('type')) {
+                    case 'rect':
+                        a.push([
+                            value,
+                            $div.attr('x1'),
+                            $div.attr('y1'),
+                            $div.attr('x2'),
+                            $div.attr('y2')
+                        ]);
+                        break;
+                    case 'whole':
+                        a.push([
+                            value,
+                            null, null, null, null
+                        ]);
+                        break
+                }
+            });
+        });
+
+        return a;
+    }
+
+    WikiDocument.prototype.setImageItems = function(tag, items) {
+        var self = this;
+
+        var parser = new DOMParser();
+        var doc = parser.parseFromString(self.text, 'text/xml');
+        var serializer = new XMLSerializer();
+        var error = $('parsererror', doc);
+
+        if (error.length != 0) {
+            return null;
+        }
+
+        $('sem[type="'+tag+'"]', doc).remove();
+        $root = $(doc.firstChild);
+        $.each(items, function(i, e) {
+            var $sem = $(doc.createElement("sem"));
+            $sem.attr('type', tag);
+            $sem.attr(tag, e[0]);
+            $div = $(doc.createElement("div"));
+            if (e[1]) {
+                $div.attr('type', 'rect');
+                $div.attr('x1', e[1]);
+                $div.attr('y1', e[2]);
+                $div.attr('x2', e[3]);
+                $div.attr('y2', e[4]);
+            }
+            else {
+                $div.attr('type', 'whole');
+            }
+            $sem.append($div);
+            $root.append($sem);
+        });
+        self.setText(serializer.serializeToString(doc));
+    }
+
+
+       $.wikiapi.WikiDocument = WikiDocument;
+})(jQuery);
+
+
+
+// Wykonuje block z załadowanymi kanonicznymi motywami
+function withThemes(code_block, onError)
+{
+    if (typeof withThemes.canon == 'undefined') {
+        $.ajax({
+            url: '/editor/themes',
+            dataType: 'text',
+            success: function(data) {
+                withThemes.canon = data.split('\n');
+                code_block(withThemes.canon);
+            },
+            error: function() {
+                withThemes.canon = null;
+                code_block(withThemes.canon);
+            }
+        })
+    }
+    else {
+        code_block(withThemes.canon);
+    }
+}
+
index 3e725d4..595007d 100644 (file)
@@ -1,10 +1,10 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
-    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<!DOCTYPE html >
 {% load i18n %}
-<html xmlns="http://www.w3.org/1999/xhtml">
-    <head>
-        <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
-        <title>{% block title %}{% trans "Platforma Redakcyjna" %}{% block subtitle %}{% endblock subtitle %}{% endblock title%}</title>
+<html>
+    <head lang="{{ LANGUAGE_CODE }}">
+        <meta charset="utf-8" />
+        <title>{% block title %}{% trans "Platforma Redakcyjna" %} ::
+            {% block titleextra %}{% endblock titleextra %}{% endblock title%}</title>
         {% block extrahead %}
         {% endblock %}
     </head>
@@ -12,7 +12,7 @@
 
     <div id="loading-overlay">
        <div id="loading-message">
-               <img src="{{STATIC_URL}}img/spinner.gif" />
+               <img src="{{STATIC_URL}}img/spinner.gif" alt="Loading" />
                <p>{% trans "Loading" %}</p>
        </div>
        </div>
index b3da1ee..c0629fa 100644 (file)
@@ -27,6 +27,7 @@ urlpatterns = patterns('',
     url(r'^documents/', include('catalogue.urls')),
     url(r'^apiclient/', include('apiclient.urls')),
     url(r'^editor/', include('wiki.urls')),
+    url(r'^images/', include('wiki_img.urls')),
     url(r'^cover/', include('cover.urls')),
 )
 
diff --git a/scripts/image.xml b/scripts/image.xml
new file mode 100644 (file)
index 0000000..7276015
--- /dev/null
@@ -0,0 +1,28 @@
+<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"></dc:creator>
+      <dc:title xml:lang="la"></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/">Sławiec, Urszula</dc:contributor.editor>
+      <dc:contributor.editor xml:lang="pl" xmlns:dc="http://purl.org/dc/elements/1.1/">Kucharska-Hornung, Katarzyna</dc:contributor.editor>
+      <dc:subject.period xml:lang="pl"></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"></dc:description.dimensions>
+      <dc:description.medium xml:lang="pl"></dc:description.medium>
+      <dc:identifier.url xml:lang="pl"></dc:identifier.url>
+      <dc:source.URL xml:lang="pl"></dc:source.URL>
+      <dc:source xml:lang="pl"></dc:source>
+      <dc:rights xml:lang="pl">Domena publiczna</dc:rights>
+      <dc:date.pd xml:lang="pl"></dc:date.pd>
+      <dc:type>Image</dc:type>
+      <dc:format xml:lang="pl">image/png</dc:format>
+      <dc:format.dimensions xml:lang="pl"></dc:format.dimensions>
+      <dc:format.checksum.sha1 xml:lang="pl" xmlns:dc="http://purl.org/dc/elements/1.1/"></dc:format.checksum.sha1>
+      <dc:date xml:lang="pl"></dc:date>
+      <dc:language xml:lang="pl" xmlns:dc="http://purl.org/dc/elements/1.1/">pol</dc:language>
+    </rdf:Description>
+  </rdf:RDF>
+</picture>
diff --git a/scripts/import_image.py b/scripts/import_image.py
new file mode 100755 (executable)
index 0000000..30ca8c0
--- /dev/null
@@ -0,0 +1,62 @@
+#!/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.
+#
+import sys
+sys.path.append('.')
+sys.path.append('./apps')
+sys.path.append('./lib')
+
+from django.core.management import setup_environ
+from redakcja import settings
+from redakcja import localsettings
+
+setup_environ(settings)
+settings.CATALOGUE_REPO_PATH = localsettings.CATALOGUE_REPO_PATH
+settings.CATALOGUE_IMAGE_REPO_PATH = localsettings.CATALOGUE_IMAGE_REPO_PATH
+settings.MEDIA_ROOT = localsettings.MEDIA_ROOT
+settings.STATIC_ROOT = localsettings.STATIC_ROOT
+
+
+from catalogue.models import  Image
+from django.core.files import File
+import re
+from os import path
+from django.contrib.auth.models import User
+from django.conf import settings
+
+
+
+user = {
+    'obj': User.objects.get(username='marcinkoziej'),
+    'name': 'Marcin Koziej',
+    'email': 'marcinkoziej@nowoczesnapolska.org.pl'
+    }
+
+files = sys.argv[1:]
+
+xml = open(path.dirname(__file__)+"/image.xml").read().decode('utf-8')
+
+for filename in files:
+    dfile = File(open(filename))
+    img = Image()
+    name = path.splitext(path.basename(filename))[0]
+    print filename, name
+    try:
+        old = Image.objects.get(slug=name)
+        print "deleting old %s" % name
+        old.delete()
+    except:
+        pass
+    
+
+    img.slug = name
+    img.title = name
+    img.image.save(filename, dfile)
+
+    img.save()
+    img.commit(xml, author=user['obj'], author_name=user['name'], author_email=user['email'])
+
+
+