From: Radek Czajka Date: Fri, 10 Dec 2010 14:24:59 +0000 (+0100) Subject: total mess: some random experiments with images and lqc's dvcs X-Git-Url: https://git.mdrn.pl/redakcja.git/commitdiff_plain/07689901a9bf30daeccf8a1ceb7193fe771eb3ac total mess: some random experiments with images and lqc's dvcs --- diff --git a/apps/dvcs/__init__.py b/apps/dvcs/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/dvcs/admin.py b/apps/dvcs/admin.py new file mode 100644 index 00000000..c81d3b7b --- /dev/null +++ b/apps/dvcs/admin.py @@ -0,0 +1,5 @@ +from django.contrib.admin import site +from dvcs.models import Document, Change + +site.register(Document) +site.register(Change) diff --git a/apps/dvcs/models.py b/apps/dvcs/models.py new file mode 100644 index 00000000..ea83ff07 --- /dev/null +++ b/apps/dvcs/models.py @@ -0,0 +1,151 @@ +from django.db import models +from django.contrib.auth.models import User +from django.utils.translation import ugettext_lazy as _ +from mercurial import mdiff, simplemerge +import pickle + +class Change(models.Model): + """ + Single document change related to previous change. The "parent" + argument points to the version against which this change has been + recorded. Initial text will have a null parent. + + Data contains a pickled diff needed to reproduce the initial document. + """ + author = models.ForeignKey(User, null=True, blank=True) + patch = models.TextField(blank=True) + tree = models.ForeignKey('Document') + + parent = models.ForeignKey('self', + null=True, blank=True, default=None, + related_name="children") + + merge_parent = models.ForeignKey('self', + null=True, blank=True, default=None, + related_name="merge_children") + + description = models.TextField(blank=True, default='') + created_at = models.DateTimeField(auto_now_add=True) + + class Meta: + ordering = ('created_at',) + + def __unicode__(self): + return u"Id: %r, Tree %r, Parent %r, Patch '''\n%s'''" % (self.id, self.tree_id, self.parent_id, self.patch) + + @staticmethod + def make_patch(src, dst): + if isinstance(src, unicode): + src = src.encode('utf-8') + if isinstance(dst, unicode): + dst = dst.encode('utf-8') + return pickle.dumps(mdiff.textdiff(src, dst)) + + def materialize(self): + changes = Change.objects.exclude(parent=None).filter( + tree=self.tree, + created_at__lte=self.created_at).order_by('created_at') + text = u'' + for change in changes: + text = change.apply_to(text) + return text + + def make_child(self, patch, author, description): + return self.children.create(patch=patch, + tree=self.tree, author=author, + description=description) + + def make_merge_child(self, patch, author, description): + return self.merge_children.create(patch=patch, + tree=self.tree, author=author, + description=description) + + def apply_to(self, text): + return mdiff.patch(text, pickle.loads(self.patch.encode('ascii'))) + + def merge_with(self, other, author, description=u"Automatic merge."): + assert self.tree_id == other.tree_id # same tree + if other.parent_id == self.pk: + # immediate child + return other + + local = self.materialize() + base = other.merge_parent.materialize() + remote = other.apply_to(base) + + merge = simplemerge.Merge3Text(base, local, remote) + result = ''.join(merge.merge_lines()) + patch = self.make_patch(local, result) + return self.children.create( + patch=patch, merge_parent=other, tree=self.tree, + author=author, description=description) + + +class Document(models.Model): + """ + File in repository. + """ + creator = models.ForeignKey(User, null=True, blank=True) + head = models.ForeignKey(Change, + null=True, blank=True, default=None, + help_text=_("This document's current head.")) + + def __unicode__(self): + return u"{0}, HEAD: {1}".format(self.id, self.head_id) + + @models.permalink + def get_absolute_url(self): + return ('dvcs.views.document_data', (), { + 'document_id': self.id, + 'version': self.head_id, + }) + + def materialize(self, version=None): + if self.head is None: + return u'' + if version is None: + version = self.head + elif not isinstance(version, Change): + version = self.change_set.get(pk=version) + return version.materialize() + + def commit(self, **kwargs): + if 'parent' not in kwargs: + parent = self.head + else: + parent = kwargs['parent'] + if not isinstance(parent, Change): + parent = Change.objects.get(pk=kwargs['parent']) + + if 'patch' not in kwargs: + if 'text' not in kwargs: + raise ValueError("You must provide either patch or target document.") + patch = Change.make_patch(self.materialize(version=parent), kwargs['text']) + else: + if 'text' in kwargs: + raise ValueError("You can provide only text or patch - not both") + patch = kwargs['patch'] + + old_head = self.head + if parent != old_head: + change = parent.make_merge_child(patch, kwargs['author'], kwargs.get('description', '')) + # not Fast-Forward - perform a merge + self.head = old_head.merge_with(change, author=kwargs['author']) + else: + self.head = parent.make_child(patch, kwargs['author'], kwargs.get('description', '')) + self.save() + return self.head + + def history(self): + return self.changes.all() + + @staticmethod + def listener_initial_commit(sender, instance, created, **kwargs): + if created: + instance.head = Change.objects.create( + author=instance.creator, + patch=pickle.dumps(mdiff.textdiff('', '')), + tree=instance) + instance.save() + +models.signals.post_save.connect(Document.listener_initial_commit, sender=Document) diff --git a/apps/dvcs/tests.py b/apps/dvcs/tests.py new file mode 100644 index 00000000..0c712957 --- /dev/null +++ b/apps/dvcs/tests.py @@ -0,0 +1,164 @@ +from django.test import TestCase +from dvcs.models import Change, Document +from django.contrib.auth.models import User + +class DocumentModelTests(TestCase): + + def setUp(self): + self.user = User.objects.create_user("tester", "tester@localhost.local") + + def assertTextEqual(self, given, expected): + return self.assertEqual(given, expected, + "Expected '''%s'''\n differs from text: '''%s'''" % (expected, given) + ) + + def test_empty_file(self): + doc = Document.objects.create(name=u"Sample Document", creator=self.user) + self.assert_(doc.head is not None) + self.assertEqual(doc.materialize(), u"") + + def test_single_commit(self): + doc = Document.objects.create(name=u"Sample Document", creator=self.user) + doc.commit(text=u"Ala ma kota", description="Commit #1", author=self.user) + self.assert_(doc.head is not None) + self.assertEqual(doc.change_set.count(), 2) + self.assertEqual(doc.materialize(), u"Ala ma kota") + + def test_chained_commits(self): + doc = Document.objects.create(name=u"Sample Document", creator=self.user) + c1 = doc.commit(description="Commit #1", text=u""" + Line #1 + Line #2 is cool + """, author=self.user) + c2 = doc.commit(description="Commit #2", text=u""" + Line #1 + Line #2 is hot + """, author=self.user) + c3 = doc.commit(description="Commit #3", text=u""" + Line #1 + ... is hot + Line #3 ate Line #2 + """, author=self.user) + self.assert_(doc.head is not None) + self.assertEqual(doc.change_set.count(), 4) + + self.assertEqual(doc.materialize(), u""" + Line #1 + ... is hot + Line #3 ate Line #2 + """) + self.assertEqual(doc.materialize(version=c3), u""" + Line #1 + ... is hot + Line #3 ate Line #2 + """) + self.assertEqual(doc.materialize(version=c2), u""" + Line #1 + Line #2 is hot + """) + self.assertEqual(doc.materialize(version=c1), """ + Line #1 + Line #2 is cool + """) + + + def test_parallel_commit_noconflict(self): + doc = Document.objects.create(name=u"Sample Document", creator=self.user) + self.assert_(doc.head is not None) + base = doc.head + base = doc.commit(description="Commit #1", text=u""" + Line #1 + Line #2 +""", author=self.user) + + c1 = doc.commit(description="Commit #2", text=u""" + Line #1 is hot + Line #2 +""", parent=base, author=self.user) + self.assertTextEqual(c1.materialize(), u""" + Line #1 is hot + Line #2 +""") + c2 = doc.commit(description="Commit #3", text=u""" + Line #1 + Line #2 + Line #3 +""", parent=base, author=self.user) + self.assertEqual(doc.change_set.count(), 5) + self.assertTextEqual(doc.materialize(), u""" + Line #1 is hot + Line #2 + Line #3 +""") + + def test_parallel_commit_conflict(self): + doc = Document.objects.create(name=u"Sample Document", creator=self.user) + self.assert_(doc.head is not None) + base = doc.head + base = doc.commit(description="Commit #1", text=u""" +Line #1 +Line #2 +Line #3 +""", author=self.user) + + c1 = doc.commit(description="Commit #2", text=u""" +Line #1 +Line #2 is hot +Line #3 +""", parent=base, author=self.user) + c2 = doc.commit(description="Commit #3", text=u""" +Line #1 +Line #2 is cool +Line #3 +""", parent=base, author=self.user) + self.assertEqual(doc.change_set.count(), 5) + self.assertTextEqual(doc.materialize(), u""" +Line #1 +<<<<<<< +Line #2 is hot +======= +Line #2 is cool +>>>>>>> +Line #3 +""") + + def test_multiply_parallel_commits(self): + doc = Document.objects.create(name=u"Sample Document", creator=self.user) + self.assert_(doc.head is not None) + c1 = doc.commit(description="Commit A1", text=u""" +Line #1 + +Line #2 + +Line #3 +""", author=self.user) + c2 = doc.commit(description="Commit A2", text=u""" +Line #1 * + +Line #2 + +Line #3 +""", author=self.user) + c3 = doc.commit(description="Commit B1", text=u""" +Line #1 + +Line #2 ** + +Line #3 +""", parent=c1, author=self.user) + c4 = doc.commit(description="Commit C1", text=u""" +Line #1 * + +Line #2 + +Line #3 *** +""", parent=c2, author=self.user) + self.assertEqual(doc.change_set.count(), 7) + self.assertTextEqual(doc.materialize(), u""" +Line #1 * + +Line #2 ** + +Line #3 *** +""") + diff --git a/apps/dvcs/urls.py b/apps/dvcs/urls.py new file mode 100644 index 00000000..d1e1e296 --- /dev/null +++ b/apps/dvcs/urls.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 +from django.conf.urls.defaults import * + +urlpatterns = patterns('dvcs.views', + url(r'^data/(?P[^/]+)/(?P.*)$', 'document_data', name='storage_document_data'), +) diff --git a/apps/dvcs/views.py b/apps/dvcs/views.py new file mode 100644 index 00000000..7918e96c --- /dev/null +++ b/apps/dvcs/views.py @@ -0,0 +1,21 @@ +# Create your views here. +from django.views.generic.simple import direct_to_template +from django import http +from dvcs.models import Document + +def document_list(request, template_name="dvcs/document_list.html"): + return direct_to_template(request, template_name, { + "documents": Document.objects.all(), + }) + +def document_data(request, document_id, version=None): + doc = Document.objects.get(pk=document_id) + return http.HttpResponse(doc.materialize(version or None), content_type="text/plain") + +def document_history(request, docid, template_name="dvcs/document_history.html"): + document = Document.objects.get(pk=docid) + return direct_to_template(request, template_name, { + "document": document, + "changes": document.history(), + }) + diff --git a/apps/toolbar/admin.py b/apps/toolbar/admin.py index 283ab782..ea87936a 100644 --- a/apps/toolbar/admin.py +++ b/apps/toolbar/admin.py @@ -25,6 +25,6 @@ class ButtonAdmin(admin.ModelAdmin): list_editable = ('label', 'tooltip', 'accesskey') prepopulated_fields = {'slug': ('label',)} -admin.site.register(models.Button, ButtonAdmin) -admin.site.register(models.ButtonGroup) -admin.site.register(models.Scriptlet) +#admin.site.register(models.Button, ButtonAdmin) +#admin.site.register(models.ButtonGroup) +#admin.site.register(models.Scriptlet) diff --git a/apps/wiki/admin.py b/apps/wiki/admin.py index 1a61b660..9c32b434 100644 --- a/apps/wiki/admin.py +++ b/apps/wiki/admin.py @@ -2,4 +2,4 @@ from django.contrib import admin from wiki import models -admin.site.register(models.Theme) +#admin.site.register(models.Theme) diff --git a/apps/wiki_img/__init__.py b/apps/wiki_img/__init__.py new file mode 100644 index 00000000..c53f0e73 --- /dev/null +++ b/apps/wiki_img/__init__.py @@ -0,0 +1 @@ + # pragma: no cover diff --git a/apps/wiki_img/admin.py b/apps/wiki_img/admin.py new file mode 100644 index 00000000..80f7b36b --- /dev/null +++ b/apps/wiki_img/admin.py @@ -0,0 +1,4 @@ +from django.contrib.admin import site +from wiki_img.models import ImageDocument + +site.register(ImageDocument) diff --git a/apps/wiki_img/constants.py b/apps/wiki_img/constants.py new file mode 100644 index 00000000..6781a48e --- /dev/null +++ b/apps/wiki_img/constants.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +from django.utils.translation import ugettext_lazy as _ + +DOCUMENT_STAGES = ( + ("", u"-----"), + ("first_correction", _(u"First correction")), + ("tagging", _(u"Tagging")), + ("proofreading", _(u"Initial Proofreading")), + ("annotation-proofreading", _(u"Annotation Proofreading")), + ("modernisation", _(u"Modernisation")), + ("annotations", _(u"Annotations")), + ("themes", _(u"Themes")), + ("editor-proofreading", _(u"Editor's Proofreading")), + ("technical-editor-proofreading", _(u"Technical Editor's Proofreading")), +) + +DOCUMENT_TAGS = DOCUMENT_STAGES + \ + (("ready-to-publish", _(u"Ready to publish")),) + +DOCUMENT_TAGS_DICT = dict(DOCUMENT_TAGS) +DOCUMENT_STAGES_DICT = dict(DOCUMENT_STAGES) diff --git a/apps/wiki_img/forms.py b/apps/wiki_img/forms.py new file mode 100644 index 00000000..25f94138 --- /dev/null +++ b/apps/wiki_img/forms.py @@ -0,0 +1,97 @@ +# -*- 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 wiki.constants import DOCUMENT_TAGS, DOCUMENT_STAGES +from django.utils.translation import ugettext_lazy as _ + + +class DocumentTagForm(forms.Form): + """ + Form for tagging revisions. + """ + + id = forms.CharField(widget=forms.HiddenInput) + tag = forms.ChoiceField(choices=DOCUMENT_TAGS) + revision = forms.IntegerField(widget=forms.HiddenInput) + + +class DocumentCreateForm(forms.Form): + """ + Form used for creating new documents. + """ + title = forms.CharField() + id = forms.RegexField(regex=ur"^[-\wąćęłńóśźżĄĆĘŁŃÓŚŹŻ]+$") + file = forms.FileField(required=False) + text = forms.CharField(required=False, widget=forms.Textarea) + + def clean(self): + file = self.cleaned_data['file'] + + if file is not None: + try: + self.cleaned_data['text'] = file.read().decode('utf-8') + except UnicodeDecodeError: + raise forms.ValidationError("Text file must be UTF-8 encoded.") + + if not self.cleaned_data["text"]: + raise forms.ValidationError("You must either enter text or upload a file") + + return self.cleaned_data + + +class DocumentsUploadForm(forms.Form): + """ + Form used for uploading new documents. + """ + file = forms.FileField(required=True, label=_('ZIP file')) + + def clean(self): + file = self.cleaned_data['file'] + + import zipfile + try: + z = self.cleaned_data['zip'] = zipfile.ZipFile(file) + except zipfile.BadZipfile: + raise forms.ValidationError("Should be a ZIP file.") + if z.testzip(): + raise forms.ValidationError("ZIP file corrupt.") + + return self.cleaned_data + + +class DocumentTextSaveForm(forms.Form): + """ + Form for saving document's text: + + * name - document's storage identifier. + * parent_revision - revision which the modified text originated from. + * comment - user's verbose comment; will be used in commit. + * stage_completed - mark this change as end of given stage. + + """ + + id = forms.CharField(widget=forms.HiddenInput) + parent_revision = forms.IntegerField(widget=forms.HiddenInput) + text = forms.CharField(widget=forms.HiddenInput) + + author_name = forms.CharField( + required=False, + label=_(u"Author"), + help_text=_(u"Your name"), + ) + + author_email = forms.EmailField( + required=False, + label=_(u"Author's email"), + help_text=_(u"Your email address, so we can show a gravatar :)"), + ) + + comment = forms.CharField( + required=True, + widget=forms.Textarea, + label=_(u"Your comments"), + help_text=_(u"Describe changes you made."), + ) diff --git a/apps/wiki_img/helpers.py b/apps/wiki_img/helpers.py new file mode 100644 index 00000000..f072ef91 --- /dev/null +++ b/apps/wiki_img/helpers.py @@ -0,0 +1,131 @@ +from django import http +from django.utils import simplejson as json +from django.utils.functional import Promise +from datetime import datetime +from functools import wraps + + +class ExtendedEncoder(json.JSONEncoder): + + def default(self, obj): + if isinstance(obj, Promise): + return unicode(obj) + + if isinstance(obj, datetime): + return datetime.ctime(obj) + " " + (datetime.tzname(obj) or 'GMT') + + return json.JSONEncoder.default(self, obj) + + +# shortcut for JSON reponses +class JSONResponse(http.HttpResponse): + + def __init__(self, data={}, **kwargs): + # get rid of mimetype + kwargs.pop('mimetype', None) + + data = json.dumps(data, cls=ExtendedEncoder) + super(JSONResponse, self).__init__(data, mimetype="application/json", **kwargs) + + +# return errors +class JSONFormInvalid(JSONResponse): + def __init__(self, form): + super(JSONFormInvalid, self).__init__(form.errors, status=400) + + +class JSONServerError(JSONResponse): + def __init__(self, *args, **kwargs): + kwargs['status'] = 500 + super(JSONServerError, self).__init__(*args, **kwargs) + + +def ajax_login_required(view): + @wraps(view) + def authenticated_view(request, *args, **kwargs): + if not request.user.is_authenticated(): + return http.HttpResponse("Login required.", status=401, mimetype="text/plain") + return view(request, *args, **kwargs) + return authenticated_view + + +def ajax_require_permission(permission): + def decorator(view): + @wraps(view) + def authorized_view(request, *args, **kwargs): + if not request.user.has_perm(permission): + return http.HttpResponse("Access Forbidden.", status=403, mimetype="text/plain") + return view(request, *args, **kwargs) + return authorized_view + return decorator + +import collections + +def recursive_groupby(iterable): + """ +# >>> recursive_groupby([1,2,3,4,5]) +# [1, 2, 3, 4, 5] + + >>> recursive_groupby([[1]]) + [1] + + >>> recursive_groupby([('a', 1),('a', 2), 3, ('b', 4), 5]) + ['a', [1, 2], 3, 'b', [4], 5] + + >>> recursive_groupby([('a', 'x', 1),('a', 'x', 2), ('a', 'x', 3)]) + ['a', ['x', [1, 2, 3]]] + + """ + + def _generator(iterator): + group = None + grouper = None + + for item in iterator: + if not isinstance(item, collections.Sequence): + if grouper is not None: + yield grouper + if len(group): + yield recursive_groupby(group) + group = None + grouper = None + yield item + continue + elif len(item) == 1: + if grouper is not None: + yield grouper + if len(group): + yield recursive_groupby(group) + group = None + grouper = None + yield item[0] + continue + elif not len(item): + continue + + if grouper is None: + group = [item[1:]] + grouper = item[0] + continue + + if grouper != item[0]: + if grouper is not None: + yield grouper + if len(group): + yield recursive_groupby(group) + group = None + grouper = None + group = [item[1:]] + grouper = item[0] + continue + + group.append(item[1:]) + + if grouper is not None: + yield grouper + if len(group): + yield recursive_groupby(group) + group = None + grouper = None + + return list(_generator(iterable)) 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 index 00000000..c334eadc 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 index 00000000..568e8441 --- /dev/null +++ b/apps/wiki_img/locale/pl/LC_MESSAGES/django.po @@ -0,0 +1,346 @@ +# 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 , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: Platforma Redakcyjna\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2010-09-29 15:34+0200\n" +"PO-Revision-Date: 2010-09-29 15:36+0100\n" +"Last-Translator: Radek Czajka \n" +"Language-Team: Fundacja Nowoczesna Polska \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: constants.py:6 +msgid "First correction" +msgstr "Autokorekta" + +#: constants.py:7 +msgid "Tagging" +msgstr "Tagowanie" + +#: constants.py:8 +msgid "Initial Proofreading" +msgstr "Korekta" + +#: constants.py:9 +msgid "Annotation Proofreading" +msgstr "Sprawdzenie przypisów źródła" + +#: constants.py:10 +msgid "Modernisation" +msgstr "Uwspółcześnienie" + +#: constants.py:11 +#: templates/wiki/tabs/annotations_view_item.html:3 +msgid "Annotations" +msgstr "Przypisy" + +#: constants.py:12 +msgid "Themes" +msgstr "Motywy" + +#: constants.py:13 +msgid "Editor's Proofreading" +msgstr "Ostateczna redakcja literacka" + +#: constants.py:14 +msgid "Technical Editor's Proofreading" +msgstr "Ostateczna redakcja techniczna" + +#: constants.py:18 +msgid "Ready to publish" +msgstr "Gotowe do publikacji" + +#: forms.py:49 +msgid "ZIP file" +msgstr "Plik ZIP" + +#: forms.py:82 +msgid "Author" +msgstr "Autor" + +#: forms.py:83 +msgid "Your name" +msgstr "Imię i nazwisko" + +#: forms.py:88 +msgid "Author's email" +msgstr "E-mail autora" + +#: forms.py:89 +msgid "Your email address, so we can show a gravatar :)" +msgstr "Adres e-mail, żebyśmy mogli pokazać gravatar :)" + +#: forms.py:95 +msgid "Your comments" +msgstr "Twój komentarz" + +#: forms.py:96 +msgid "Describe changes you made." +msgstr "Opisz swoje zmiany" + +#: forms.py:102 +msgid "Completed" +msgstr "Ukończono" + +#: forms.py:103 +msgid "If you completed a life cycle stage, select it." +msgstr "Jeśli został ukończony etap prac, wskaż go." + +#: models.py:93 +#, python-format +msgid "Finished stage: %s" +msgstr "Ukończony etap: %s" + +#: models.py:152 +msgid "name" +msgstr "nazwa" + +#: models.py:156 +msgid "theme" +msgstr "motyw" + +#: models.py:157 +msgid "themes" +msgstr "motywy" + +#: views.py:167 +#, python-format +msgid "Title already used for %s" +msgstr "Nazwa taka sama jak dla pliku %s" + +#: views.py:169 +msgid "Title already used in repository." +msgstr "Plik o tej nazwie już istnieje w repozytorium." + +#: views.py:175 +msgid "File should be UTF-8 encoded." +msgstr "Plik powinien mieć kodowanie UTF-8." + +#: views.py:358 +msgid "Tag added" +msgstr "Dodano tag" + +#: templates/wiki/base.html:15 +msgid "Platforma Redakcyjna" +msgstr "" + +#: templates/wiki/diff_table.html:5 +msgid "Old version" +msgstr "Stara wersja" + +#: templates/wiki/diff_table.html:6 +msgid "New version" +msgstr "Nowa wersja" + +#: templates/wiki/document_create_missing.html:8 +msgid "Create document" +msgstr "Utwórz dokument" + +#: templates/wiki/document_details.html:32 +msgid "Click to open/close gallery" +msgstr "Kliknij, aby (ro)zwinąć galerię" + +#: templates/wiki/document_details_base.html:36 +msgid "Help" +msgstr "Pomoc" + +#: templates/wiki/document_details_base.html:38 +msgid "Version" +msgstr "Wersja" + +#: templates/wiki/document_details_base.html:38 +msgid "Unknown" +msgstr "nieznana" + +#: templates/wiki/document_details_base.html:40 +#: templates/wiki/tag_dialog.html:15 +msgid "Save" +msgstr "Zapisz" + +#: templates/wiki/document_details_base.html:41 +msgid "Save attempt in progress" +msgstr "Trwa zapisywanie" + +#: templates/wiki/document_details_base.html:42 +msgid "There is a newer version of this document!" +msgstr "Istnieje nowsza wersja tego dokumentu!" + +#: templates/wiki/document_list.html:30 +msgid "Clear filter" +msgstr "Wyczyść filtr" + +#: templates/wiki/document_list.html:48 +msgid "Your last edited documents" +msgstr "Twoje ostatnie edycje" + +#: templates/wiki/document_upload.html:9 +msgid "Bulk documents upload" +msgstr "Hurtowe dodawanie dokumentów" + +#: templates/wiki/document_upload.html:12 +msgid "Please submit a ZIP with UTF-8 encoded XML files. Files not ending with .xml will be ignored." +msgstr "Proszę wskazać archiwum ZIP z plikami XML w kodowaniu UTF-8. Pliki nie kończące się na .xml zostaną zignorowane." + +#: templates/wiki/document_upload.html:17 +msgid "Upload" +msgstr "Dodaj" + +#: templates/wiki/document_upload.html:24 +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/wiki/document_upload.html:25 +msgid "Offending files" +msgstr "Błędne pliki" + +#: templates/wiki/document_upload.html:33 +msgid "Correct files" +msgstr "Poprawne pliki" + +#: templates/wiki/document_upload.html:44 +msgid "Files have been successfully uploaded to the repository." +msgstr "Pliki zostały dodane do repozytorium." + +#: templates/wiki/document_upload.html:45 +msgid "Uploaded files" +msgstr "Dodane pliki" + +#: templates/wiki/document_upload.html:55 +msgid "Skipped files" +msgstr "Pominięte pliki" + +#: templates/wiki/document_upload.html:56 +msgid "Files skipped due to no .xml extension" +msgstr "Pliki pominięte z powodu braku rozszerzenia .xml." + +#: templates/wiki/tag_dialog.html:16 +msgid "Cancel" +msgstr "Anuluj" + +#: templates/wiki/tabs/annotations_view.html:5 +msgid "Refresh" +msgstr "Odśwież" + +#: templates/wiki/tabs/gallery_view.html:7 +msgid "Previous" +msgstr "Poprzednie" + +#: templates/wiki/tabs/gallery_view.html:13 +msgid "Next" +msgstr "Następne" + +#: templates/wiki/tabs/gallery_view.html:15 +msgid "Zoom in" +msgstr "Powiększ" + +#: templates/wiki/tabs/gallery_view.html:16 +msgid "Zoom out" +msgstr "Zmniejsz" + +#: templates/wiki/tabs/gallery_view_item.html:3 +msgid "Gallery" +msgstr "Galeria" + +#: templates/wiki/tabs/history_view.html:5 +msgid "Compare versions" +msgstr "Porównaj wersje" + +#: templates/wiki/tabs/history_view.html:7 +msgid "Mark version" +msgstr "Oznacz wersję" + +#: templates/wiki/tabs/history_view.html:9 +msgid "Revert document" +msgstr "Przywróć wersję" + +#: templates/wiki/tabs/history_view.html:12 +msgid "View version" +msgstr "Zobacz wersję" + +#: templates/wiki/tabs/history_view_item.html:3 +msgid "History" +msgstr "Historia" + +#: templates/wiki/tabs/search_view.html:3 +#: templates/wiki/tabs/search_view.html:5 +msgid "Search" +msgstr "Szukaj" + +#: templates/wiki/tabs/search_view.html:8 +msgid "Replace with" +msgstr "Zamień na" + +#: templates/wiki/tabs/search_view.html:10 +msgid "Replace" +msgstr "Zamień" + +#: templates/wiki/tabs/search_view.html:13 +msgid "Options" +msgstr "Opcje" + +#: templates/wiki/tabs/search_view.html:15 +msgid "Case sensitive" +msgstr "Rozróżniaj wielkość liter" + +#: templates/wiki/tabs/search_view.html:17 +msgid "From cursor" +msgstr "Zacznij od kursora" + +#: templates/wiki/tabs/search_view_item.html:3 +msgid "Search and replace" +msgstr "Znajdź i zamień" + +#: templates/wiki/tabs/source_editor_item.html:5 +msgid "Source code" +msgstr "Kod źródłowy" + +#: templates/wiki/tabs/summary_view.html:10 +msgid "Title" +msgstr "Tytuł" + +#: templates/wiki/tabs/summary_view.html:15 +msgid "Document ID" +msgstr "ID dokumentu" + +#: templates/wiki/tabs/summary_view.html:19 +msgid "Current version" +msgstr "Aktualna wersja" + +#: templates/wiki/tabs/summary_view.html:22 +msgid "Last edited by" +msgstr "Ostatnio edytowane przez" + +#: templates/wiki/tabs/summary_view.html:26 +msgid "Link to gallery" +msgstr "Link do galerii" + +#: templates/wiki/tabs/summary_view.html:31 +msgid "Publish" +msgstr "Opublikuj" + +#: templates/wiki/tabs/summary_view_item.html:4 +msgid "Summary" +msgstr "Podsumowanie" + +#: templates/wiki/tabs/wysiwyg_editor.html:9 +msgid "Insert theme" +msgstr "Wstaw motyw" + +#: templates/wiki/tabs/wysiwyg_editor.html:12 +msgid "Insert annotation" +msgstr "Wstaw przypis" + +#: templates/wiki/tabs/wysiwyg_editor.html:15 +msgid "Insert special character" +msgstr "Wstaw znak specjalny" + +#: templates/wiki/tabs/wysiwyg_editor_item.html:3 +msgid "Visual editor" +msgstr "Edytor wizualny" + diff --git a/apps/wiki_img/models.py b/apps/wiki_img/models.py new file mode 100644 index 00000000..dd16a87d --- /dev/null +++ b/apps/wiki_img/models.py @@ -0,0 +1,29 @@ +# -*- 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.db import models +from django.contrib.auth.models import User +from django.utils.translation import ugettext_lazy as _ +from dvcs.models import Document + + +class ImageDocument(models.Model): + slug = models.SlugField(_('slug'), max_length=120) + name = models.CharField(_('name'), max_length=120) + image = models.ImageField(_('image'), upload_to='wiki_img') + doc = models.OneToOneField(Document, null=True, blank=True) + creator = models.ForeignKey(User, null=True, blank=True) + + @staticmethod + def listener_initial_commit(sender, instance, created, **kwargs): + if created: + instance.doc = Document.objects.create(creator=instance.creator) + instance.save() + + def __unicode__(self): + return self.name + + +models.signals.post_save.connect(ImageDocument.listener_initial_commit, sender=ImageDocument) diff --git a/apps/wiki_img/nice_diff.py b/apps/wiki_img/nice_diff.py new file mode 100644 index 00000000..b228fad9 --- /dev/null +++ b/apps/wiki_img/nice_diff.py @@ -0,0 +1,61 @@ +# -*- 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. +# +import difflib +import re +from collections import deque + +from django.template.loader import render_to_string +from django.utils.html import escape as html_escape + +DIFF_RE = re.compile(r"""\x00([+^-])""", re.UNICODE) +NAMES = {'+': 'added', '-': 'removed', '^': 'changed'} + + +def diff_replace(match): + return """""" % NAMES[match.group(1)] + + +def filter_line(line): + return DIFF_RE.sub(diff_replace, html_escape(line)).replace('\x01', '') + + +def format_changeset(a, b, change): + return (a[0], filter_line(a[1]), b[0], filter_line(b[1]), change) + + +def html_diff_table(la, lb, context=None): + all_changes = difflib._mdiff(la, lb) + + if context is None: + changes = (format_changeset(*c) for c in all_changes) + else: + changes = [] + q = deque() + after_change = False + + for changeset in all_changes: + q.append(changeset) + + if changeset[2]: + after_change = True + if not after_change: + changes.append((0, '-----', 0, '-----', False)) + changes.extend(format_changeset(*c) for c in q) + q.clear() + else: + if len(q) == context and after_change: + changes.extend(format_changeset(*c) for c in q) + q.clear() + after_change = False + elif len(q) > context: + q.popleft() + + return render_to_string("wiki/diff_table.html", { + "changes": changes, + }) + + +__all__ = ['html_diff_table'] diff --git a/apps/wiki_img/templates/wiki_img/base.html b/apps/wiki_img/templates/wiki_img/base.html new file mode 100644 index 00000000..f88fac31 --- /dev/null +++ b/apps/wiki_img/templates/wiki_img/base.html @@ -0,0 +1,24 @@ +{% extends "base.html" %} +{% load compressed i18n %} + +{% block title %}{{ document_name }} - {{ block.super }}{% endblock %} + +{% block extrahead %} +{% compressed_css 'listing' %} +{% endblock %} + +{% block extrabody %} +{% compressed_js 'listing' %} +{% endblock %} + +{% block maincontent %} +

{% trans "Platforma Redakcyjna" %}

+
+ {% block leftcolumn %} + {% endblock leftcolumn %} +
+
+ {% block rightcolumn %} + {% endblock rightcolumn %} +
+{% endblock maincontent %} \ No newline at end of file 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 index 00000000..818c38cc --- /dev/null +++ b/apps/wiki_img/templates/wiki_img/diff_table.html @@ -0,0 +1,21 @@ +{% load i18n %} + + + + + + + + +{% for an, a, bn, b, has_change in changes %} + + + + + + + + +{% endfor %} + +
{% trans "Old version" %}{% trans "New version" %}
{{an}}{{ a|safe }} {{bn}}{{ b|safe }} 
\ No newline at end of file diff --git a/apps/wiki_img/templates/wiki_img/document_create_missing.html b/apps/wiki_img/templates/wiki_img/document_create_missing.html new file mode 100644 index 00000000..351e87a2 --- /dev/null +++ b/apps/wiki_img/templates/wiki_img/document_create_missing.html @@ -0,0 +1,13 @@ +{% extends "wiki/base.html" %} +{% load i18n %} + +{% block leftcolumn %} +
+ {{ form.as_p }} + +

+
+{% endblock leftcolumn %} + +{% block rightcolumn %} +{% endblock rightcolumn %} \ 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 index 00000000..d03a0bfa --- /dev/null +++ b/apps/wiki_img/templates/wiki_img/document_details.html @@ -0,0 +1,32 @@ +{% extends "wiki_img/document_details_base.html" %} +{% load i18n %} + +{% block extrabody %} +{{ block.super }} + + +{% 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" %} +{% 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" %} +{% endblock %} + +{% block dialogs %} + {% include "wiki_img/save_dialog.html" %} +{% 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 index 00000000..30accf26 --- /dev/null +++ b/apps/wiki_img/templates/wiki_img/document_details_base.html @@ -0,0 +1,57 @@ +{% extends "base.html" %} +{% load toolbar_tags i18n %} + +{% block title %}{{ document.name }} - {{ block.super }}{% endblock %} +{% block extrahead %} +{% load compressed %} +{% compressed_css 'detail' %} +{% endblock %} + +{% block extrabody %} + +{% compressed_js 'wiki_img' %} +{% endblock %} + +{% block maincontent %} + + + +
+
+ {% block tabs-content %} {% endblock %} +
+
+ +{% 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 index 00000000..71556a19 --- /dev/null +++ b/apps/wiki_img/templates/wiki_img/document_details_readonly.html @@ -0,0 +1,27 @@ +{% extends "wiki/document_details_base.html" %} +{% load i18n %} + +{% block editor-class %}readonly{% endblock %} + +{% block extrabody %} +{{ block.super }} + + +{% endblock %} + +{% block tabs-menu %} + {% include "wiki/tabs/wysiwyg_editor_item.html" %} + {% include "wiki/tabs/source_editor_item.html" %} +{% endblock %} + +{% block tabs-content %} + {% include "wiki/tabs/wysiwyg_editor.html" %} + {% include "wiki/tabs/source_editor.html" %} +{% endblock %} + +{% block splitter-extra %} +{% endblock %} + +{% block dialogs %} +{% endblock %} \ No newline at end of file diff --git a/apps/wiki_img/templates/wiki_img/document_list.html b/apps/wiki_img/templates/wiki_img/document_list.html new file mode 100644 index 00000000..cf10cde3 --- /dev/null +++ b/apps/wiki_img/templates/wiki_img/document_list.html @@ -0,0 +1,56 @@ +{% extends "wiki/base.html" %} + +{% load i18n %} +{% load wiki %} + +{% block extrabody %} +{{ block.super }} + +{% endblock %} + +{% block leftcolumn %} +
+ + + + + + + + + {% for doc in object_list %} + + + + + {% endfor %} + +
Filtr:
{{ doc.name }}
+
+{% endblock leftcolumn %} + +{% block rightcolumn %} +
+

{% trans "Your last edited documents" %}

+
    + {% for name, date in last_docs %} +
  1. {{ name|wiki_title }}
    ({{ date|date:"H:i:s, d/m/Y" }})
  2. + {% endfor %} +
+
+{% endblock rightcolumn %} diff --git a/apps/wiki_img/templates/wiki_img/save_dialog.html b/apps/wiki_img/templates/wiki_img/save_dialog.html new file mode 100644 index 00000000..fc239d22 --- /dev/null +++ b/apps/wiki_img/templates/wiki_img/save_dialog.html @@ -0,0 +1,24 @@ +{% load i18n %} +
+
+

{{ forms.text_save.comment.label }}

+

+ {{ forms.text_save.comment.help_text}} + +

+ {{forms.text_save.comment }} + + + + {% for f in forms.text_save.hidden_fields %} + {{ f }} + {% endfor %} + +

+ +

+ + +

+
+
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 index 00000000..b5b65882 --- /dev/null +++ b/apps/wiki_img/templates/wiki_img/tabs/motifs_editor.html @@ -0,0 +1,17 @@ +{% load i18n %} + 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 index 00000000..33ad25b5 --- /dev/null +++ b/apps/wiki_img/templates/wiki_img/tabs/motifs_editor_item.html @@ -0,0 +1,4 @@ +{% load i18n %} +
  • + {% trans "Motifs" %} +
  • 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 index 00000000..686cfa23 --- /dev/null +++ b/apps/wiki_img/templates/wiki_img/tabs/objects_editor.html @@ -0,0 +1,17 @@ +{% load i18n %} + 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 index 00000000..d5b0832f --- /dev/null +++ b/apps/wiki_img/templates/wiki_img/tabs/objects_editor_item.html @@ -0,0 +1,4 @@ +{% load i18n %} +
  • + {% trans "Objects" %} +
  • 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 index 00000000..a1316a7e --- /dev/null +++ b/apps/wiki_img/templates/wiki_img/tabs/source_editor.html @@ -0,0 +1,4 @@ +{% load toolbar_tags i18n %} +
    + +
    \ 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 index 00000000..89e0fae7 --- /dev/null +++ b/apps/wiki_img/templates/wiki_img/tabs/source_editor_item.html @@ -0,0 +1,6 @@ +{% load i18n %} +
  • + {% trans "Source code" %} +
  • \ 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 index 00000000..a908f553 --- /dev/null +++ b/apps/wiki_img/templates/wiki_img/tabs/summary_view.html @@ -0,0 +1,24 @@ +{% load i18n %} +{% load wiki %} + 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 index 00000000..2b4daeb3 --- /dev/null +++ b/apps/wiki_img/templates/wiki_img/tabs/summary_view_item.html @@ -0,0 +1,5 @@ +{% load i18n %} +{% load wiki %} +
  • + {% trans "Summary" %} +
  • diff --git a/apps/wiki_img/templates/wiki_img/tag_dialog.html b/apps/wiki_img/templates/wiki_img/tag_dialog.html new file mode 100644 index 00000000..bc601cb9 --- /dev/null +++ b/apps/wiki_img/templates/wiki_img/tag_dialog.html @@ -0,0 +1,19 @@ +{% load i18n %} +
    +
    + {% for field in forms.add_tag.visible_fields %} +

    {{ field.label_tag }} {{ field }}

    +

    {{ field.help_text }}

    + {% endfor %} + + {% for f in forms.add_tag.hidden_fields %} + {{ f }} + {% endfor %} +

    + +

    + + +

    +
    +
    diff --git a/apps/wiki_img/tests.py b/apps/wiki_img/tests.py new file mode 100644 index 00000000..65777379 --- /dev/null +++ b/apps/wiki_img/tests.py @@ -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 index 00000000..075e5adf --- /dev/null +++ b/apps/wiki_img/urls.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 +from django.conf.urls.defaults import * +from django.conf import settings +from django.views.generic.list_detail import object_list + +from wiki_img.models import ImageDocument + + +PART = ur"""[ ĄĆĘŁŃÓŚŻŹąćęłńóśżź0-9\w_.-]+""" + +urlpatterns = patterns('wiki_img.views', + url(r'^$', object_list, {'queryset': ImageDocument.objects.all(), "template_name": "wiki_img/document_list.html"}), + + url(r'^edit/(?P%s)$' % PART, + 'editor', name="wiki_img_editor"), + + url(r'^(?P[^/]+)/text$', + 'text', name="wiki_img_text"), + +) diff --git a/apps/wiki_img/views.py b/apps/wiki_img/views.py new file mode 100644 index 00000000..c11549ab --- /dev/null +++ b/apps/wiki_img/views.py @@ -0,0 +1,69 @@ +import os +import functools +import logging +logger = logging.getLogger("fnp.wiki") + +from django.views.generic.simple import direct_to_template +from django.core.urlresolvers import reverse +from wiki.helpers import JSONResponse +from django import http +from django.shortcuts import get_object_or_404 +from django.conf import settings + +from wiki_img.models import ImageDocument +from wiki_img.forms import DocumentTextSaveForm + +# +# 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(ImageDocument, slug=slug) + + return direct_to_template(request, template_name, extra_context={ + 'document': doc, + 'forms': { + "text_save": DocumentTextSaveForm(prefix="textsave"), + }, + 'REDMINE_URL': settings.REDMINE_URL, + }) + + +@never_cache +def text(request, slug): + if request.method == 'POST': + form = DocumentTextSaveForm(request.POST, prefix="textsave") + if form.is_valid(): + document = get_object_or_404(ImageDocument, slug=slug) + revision = form.cleaned_data['parent_revision'] + + comment = form.cleaned_data['comment'] + + if request.user.is_authenticated(): + user = request.user + else: + user = None + + document.doc.commit( + parent=revision, + text=form.cleaned_data['text'], + author=user, + description=comment + ) + + return JSONResponse({ + 'text': document.doc.materialize(), + 'revision': document.doc.change_set.count(), + }) + else: + return JSONFormInvalid(form) + else: + doc = get_object_or_404(ImageDocument, slug=slug) + return JSONResponse({ + 'text': doc.doc.materialize(), + 'revision': doc.doc.change_set.count() + }) + diff --git a/redakcja/settings/common.py b/redakcja/settings/common.py index 8d032ef6..5acf2658 100644 --- a/redakcja/settings/common.py +++ b/redakcja/settings/common.py @@ -118,7 +118,9 @@ INSTALLED_APPS = ( 'sorl.thumbnail', 'filebrowser', + 'dvcs', 'wiki', + 'wiki_img', 'toolbar', ) diff --git a/redakcja/settings/compress.py b/redakcja/settings/compress.py index 2ce6ac91..714e4c2a 100644 --- a/redakcja/settings/compress.py +++ b/redakcja/settings/compress.py @@ -9,6 +9,7 @@ COMPRESS_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', @@ -57,6 +58,36 @@ COMPRESS_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_img/toolbar.js', + + # dialogs + 'js/wiki_img/dialog_save.js', + 'js/wiki_img/dialog_addtag.js', + + # views + 'js/wiki_img/view_summary.js', + 'js/wiki_img/view_editor_objects.js', + 'js/wiki_img/view_editor_motifs.js', + 'js/wiki/view_editor_source.js', + ), + 'output_filename': 'compressed/detail_img_scripts_?.js', + }, 'listing': { 'source_filenames': ( 'js/lib/jquery-1.4.2.min.js', diff --git a/redakcja/static/css/imgareaselect-default.css b/redakcja/static/css/imgareaselect-default.css new file mode 100644 index 00000000..e9c8592f --- /dev/null +++ b/redakcja/static/css/imgareaselect-default.css @@ -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 { +} diff --git a/redakcja/static/css/master.css b/redakcja/static/css/master.css index 84c1ca53..1c56e546 100644 --- a/redakcja/static/css/master.css +++ b/redakcja/static/css/master.css @@ -57,6 +57,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; } @@ -99,6 +122,8 @@ body { font: 11px Helvetica, Verdana, sans-serif; font-weight: bold; + + z-index: 100; } @@ -345,3 +370,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 index 00000000..ec9f5da7 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 index 00000000..331cc90b 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 index 00000000..a2aa5b0d 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 index 00000000..4bfd5556 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 index 00000000..d981fdc5 --- /dev/null +++ b/redakcja/static/js/lib/jquery/jquery.imgareaselect.js @@ -0,0 +1,716 @@ +/* + * imgAreaSelect jQuery plugin + * version 0.9.3 + * + * Copyright (c) 2008-2010 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 $('
    '); +} + +$.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, + + imgWidth, imgHeight, + + $parent, + + parOfs, + + zIndex = 0, + + position = 'absolute', + + startX, startY, + + scaleX, scaleY, + + resizeMargin = 10, + + 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, + + $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), + y1: round(y1 / sy), + x2: round(x2 / sx), + y2: round(y2 / sy) + }; + + selection.width = selection.x2 - selection.x1; + selection.height = selection.y2 - selection.y1; + } + + function adjust() { + if (!$img.width()) + return; + + imgOfs = { left: round($img.offset().left), top: round($img.offset().top) }; + + imgWidth = $img.width(); + imgHeight = $img.height(); + + minWidth = options.minWidth || 0; + minHeight = options.minHeight || 0; + maxWidth = min(options.maxWidth || 1<<24, imgWidth); + maxHeight = min(options.maxHeight || 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 = $.inArray($parent.css('position'), ['absolute', 'relative']) + 1 ? + { 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 / 2 }); + $($handles[5]).css({ left: w, top: h / 2 }); + $($handles[6]).css({ left: w / 2, top: h }); + $($handles[7]).css({ top: h / 2 }); + case 4: + $handles.slice(1,3).css({ left: w }); + $handles.slice(2,4).css({ top: h }); + } + + if (resetKeyPress !== false) { + if ($.imgAreaSelect.keyPress != docKeyPress) + $(document).unbind($.imgAreaSelect.keyPress, + $.imgAreaSelect.onKeyPress); + + if (options.keys) + $(document)[$.imgAreaSelect.keyPress]( + $.imgAreaSelect.onKeyPress = docKeyPress); + } + + if ($.browser.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 <= resizeMargin) + resize = 'n'; + else if (y >= selection.height - resizeMargin) + resize = 's'; + if (x <= resizeMargin) + resize += 'w'; + else if (x >= selection.width - 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(); }); + + options.onSelectEnd(img, getSelection()); + + $(document).unbind('mousemove', selectingMouseMove); + $box.mousemove(areaMouseMove); + } + + 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 = resize == '' || /w|e/.test(resize) || aspectRatio ? evX(event) : viewX(selection.x2); + y2 = resize == '' || /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() { + adjust(); + + x2 = x1; + y2 = y1; + + doResize(); + + resize = ''; + + if ($outer.is(':not(: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); + hide($box.add($outer)); + + selection = { x1: selX(x1), y1: selY(y1), x2: selX(x1), y2: selY(y1), + width: 0, height: 0 }; + + 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).one('mousemove', startSelection) + .one('mouseup', cancelSelection); + + return false; + } + + function windowResize() { + doUpdate(false); + } + + function imgLoad() { + imgLoaded = true; + + setOptions(options = $.extend({ + classPrefix: 'imgareaselect', + movable: true, + resizable: true, + parent: 'body', + 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 (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($handles).add($areaOpera)); + + if ($.browser.msie) { + if (o = $outer.css('filter').match(/opacity=([0-9]+)/)) + $outer.css('opacity', o[1]/100); + if (o = $border.css('filter').match(/opacity=([0-9]+)/)) + $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 () { + $img.unbind('mousedown', imgMouseDown); + $box.add($outer).remove(); + }; + + this.getOptions = function () { return options; }; + + this.setOptions = setOptions; + + this.getSelection = getSelection; + + this.setSelection = setSelection; + + this.update = doUpdate; + + $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 ($.browser.msie) + $img.attr('unselectable', 'on'); + + $.imgAreaSelect.keyPress = $.browser.msie || + $.browser.safari ? 'keydown' : 'keypress'; + + if ($.browser.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); +}; + +$.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); diff --git a/redakcja/static/js/wiki_img/base.js b/redakcja/static/js/wiki_img/base.js new file mode 100644 index 00000000..ffe5a01d --- /dev/null +++ b/redakcja/static/js/wiki_img/base.js @@ -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 = $('
  • ' + + title + '
  • '); + var $view = $('
    '); + + 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/dialog_addtag.js b/redakcja/static/js/wiki_img/dialog_addtag.js new file mode 100644 index 00000000..1a90ccf6 --- /dev/null +++ b/redakcja/static/js/wiki_img/dialog_addtag.js @@ -0,0 +1,61 @@ +/* + * Dialog for saving document to the server + * + */ +(function($){ + + function AddTagDialog(element, options){ + if (!options.revision && options.revision != 0) + throw "AddTagDialog needs a revision number."; + + this.ctx = $.wiki.exitContext(); + this.clearForm(); + + /* fill out hidden fields */ + this.$form = $('form', element); + + $("input[name='addtag-id']", this.$form).val(CurrentDocument.id); + $("input[name='addtag-revision']", this.$form).val(options.revision); + + $.wiki.cls.GenericDialog.call(this, element); + }; + + AddTagDialog.prototype = $.extend(new $.wiki.cls.GenericDialog(), { + cancelAction: function(){ + $.wiki.enterContext(this.ctx); + this.hide(); + }, + + saveAction: function(){ + var self = this; + + self.$elem.block({ + message: "Dodawanie tagu", + fadeIn: 0, + }); + + CurrentDocument.setTag({ + form: self.$form, + success: function(doc, changed, info){ + self.$elem.block({ + message: info, + timeout: 2000, + fadeOut: 0, + onUnblock: function(){ + self.hide(); + $.wiki.enterContext(self.ctx); + } + }); + }, + failure: function(doc, info){ + console.log("Failure", info); + self.reportErrors(info); + self.$elem.unblock(); + } + }); + } + }); + + /* make it global */ + $.wiki.cls.AddTagDialog = AddTagDialog; +})(jQuery); diff --git a/redakcja/static/js/wiki_img/dialog_save.js b/redakcja/static/js/wiki_img/dialog_save.js new file mode 100644 index 00000000..aa9258d5 --- /dev/null +++ b/redakcja/static/js/wiki_img/dialog_save.js @@ -0,0 +1,65 @@ +/* + * Dialog for saving document to the server + * + */ +(function($) { + + function SaveDialog(element) { + this.ctx = $.wiki.exitContext(); + this.clearForm(); + + /* fill out hidden fields */ + this.$form = $('form', element); + + $("input[name='textsave-id']", this.$form).val(CurrentDocument.id); + $("input[name='textsave-parent_revision']", this.$form).val(CurrentDocument.revision); + + $.wiki.cls.GenericDialog.call(this, element); + }; + + SaveDialog.prototype = new $.wiki.cls.GenericDialog(); + + SaveDialog.prototype.cancelAction = function() { + $.wiki.enterContext(this.ctx); + this.hide(); + }; + + SaveDialog.prototype.saveAction = function() { + var self = this; + + self.$elem.block({ + message: "Zapisywanie...
    ", + fadeIn: 0, + }); + $.wiki.blocking = self.$elem; + + try { + + CurrentDocument.save({ + form: self.$form, + success: function(doc, changed, info){ + self.$elem.block({ + message: info, + timeout: 2000, + fadeOut: 0, + onUnblock: function() { + self.hide(); + $.wiki.enterContext(self.ctx); + } + }); + }, + failure: function(doc, info) { + console.log("Failure", info); + self.reportErrors(info); + self.$elem.unblock(); + } + }); + } catch(e) { + console.log('Exception:', e) + self.$elem.unblock(); + } + }; /* end of save dialog */ + + /* make it global */ + $.wiki.cls.SaveDialog = SaveDialog; +})(jQuery); diff --git a/redakcja/static/js/wiki_img/loader.js b/redakcja/static/js/wiki_img/loader.js new file mode 100644 index 00000000..41a029d7 --- /dev/null +++ b/redakcja/static/js/wiki_img/loader.js @@ -0,0 +1,159 @@ +if (!window.console) { + window.console = { + log: function(){ + } + } +} + +DEFAULT_PERSPECTIVE = "#SummaryPerspective"; + +$(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) { + $.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("↑ " + active_right.vsplitbar + " ↑"); + $('#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); +}); + + +// Wykonuje block z załadowanymi kanonicznymi motywami +function withThemes(code_block, onError) +{ + if (typeof withThemes.canon == 'undefined') { + $.ajax({ + url: '/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); + } +} + + diff --git a/redakcja/static/js/wiki_img/toolbar.js b/redakcja/static/js/wiki_img/toolbar.js new file mode 100644 index 00000000..3eafdae6 --- /dev/null +++ b/redakcja/static/js/wiki_img/toolbar.js @@ -0,0 +1,60 @@ +/* + * Toolbar plugin. + */ +(function($) { + + $.fn.toolbarize = function(options) { + var $toolbar = $(this); + var $container = $('.button_group_container', $toolbar); + + $('.button_group button', $toolbar).click(function(event){ + event.preventDefault(); + + var params = eval("(" + $(this).attr('data-ui-action-params') + ")"); + + scriptletCenter.callInteractive({ + action: $(this).attr('data-ui-action'), + context: options.actionContext, + extra: params + }); + }); + + $toolbar.children().filter('select').change(function(event){ + var slug = $(this).val(); + $container.scrollLeft(0); + $('*[data-group]').hide().filter('[data-group=' + slug + ']').show(); + }).change(); + + $('button.next', $toolbar).click(function() { + var $current_group = $('.button_group:visible', $toolbar); + var scroll = $container.scrollLeft(); + + var $hidden = $("li", $current_group).filter(function() { + return ($(this).position().left + $(this).outerWidth()) > $container.width(); + }).first(); + + if($hidden.length > 0) { + scroll = $hidden.position().left + scroll + $hidden.outerWidth() - $container.width() + 1; + $container.scrollLeft(scroll); + }; + }); + + $('button.prev', $toolbar).click(function() { + var $current_group = $('.button_group:visible', $toolbar); + var scroll = $container.scrollLeft(); + + /* first not visible element is: */ + var $hidden = $("li", $current_group).filter(function() { + return $(this).position().left < 0; + }).last(); + + if( $hidden.length > 0) + { + scroll = scroll + $hidden.position().left; + $container.scrollLeft(scroll); + }; + }); + + }; + +})(jQuery); \ No newline at end of file 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 index 00000000..7ce96650 --- /dev/null +++ b/redakcja/static/js/wiki_img/view_editor_motifs.js @@ -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 = $('') + $e.text(name); + if (x1 !== null) + $e.data('coords', [x1, y1, x2, y2]); + this.$objects_list.append($e); + this.$objects_list.append('(x)'); + } + + + 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('motyw'), 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('motyw', 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 index 00000000..9e6a3cb8 --- /dev/null +++ b/redakcja/static/js/wiki_img/view_editor_objects.js @@ -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 = $('') + $e.text(name); + if (x1 !== null) + $e.data('coords', [x1, y1, x2, y2]); + this.$objects_list.append($e); + this.$objects_list.append('(x)'); + } + + + 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('obiekt'), 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('obiekt', objects); + + this.ias.setOptions({disable: true, hide: true}); + + }; + + $.wiki.ObjectsPerspective = ObjectsPerspective; + +})(jQuery); diff --git a/redakcja/static/js/wiki_img/view_editor_source.js b/redakcja/static/js/wiki_img/view_editor_source.js new file mode 100644 index 00000000..8fb93589 --- /dev/null +++ b/redakcja/static/js/wiki_img/view_editor_source.js @@ -0,0 +1,109 @@ +/* COMMENT */ +(function($) { + + function CodeMirrorPerspective(options) + { + var old_callback = options.callback; + options.callback = function(){ + var self = this; + + this.codemirror = CodeMirror.fromTextArea('codemirror_placeholder', { + parserfile: 'parsexml.js', + path: STATIC_URL + "js/lib/codemirror-0.8/", + stylesheet: STATIC_URL + "css/xmlcolors_20100906.css", + parserConfig: { + useHTMLKludges: false + }, + iframeClass: 'xml-iframe', + textWrapping: true, + lineNumbers: true, + width: "100%", + height: "100%", + tabMode: 'spaces', + indentUnit: 0, + readOnly: CurrentDocument.readonly || false, + initCallback: function(){ + + self.codemirror.grabKeys(function(event) { + if (event.button) { + $(event.button).trigger('click'); + event.button = null; + } + }, function(keycode, event) { + if(!event.altKey) + return false; + + var c = String.fromCharCode(keycode).toLowerCase(); + var button = $("#source-editor button[data-ui-accesskey='"+c+"']"); + if(button.length == 0) + return false; + + /* it doesn't matter which button we pick - all do the same */ + event.button = button[0]; + return true; + }); + + $('#source-editor .toolbar').toolbarize({ + actionContext: self.codemirror + }); + + console.log("Initialized CodeMirror"); + + // textarea is no longer needed + $('codemirror_placeholder').remove(); + + old_callback.call(self); + } + }); + }; + + $.wiki.Perspective.call(this, options); + }; + + + CodeMirrorPerspective.prototype = new $.wiki.Perspective(); + + CodeMirrorPerspective.prototype.freezeState = function() { + this.config().position = this.codemirror.win.scrollY || 0; + }; + + CodeMirrorPerspective.prototype.unfreezeState = function () { + this.codemirror.win.scroll(0, this.config().position || 0); + }; + + CodeMirrorPerspective.prototype.onEnter = function(success, failure) { + $.wiki.Perspective.prototype.onEnter.call(this); + + console.log('Entering', this.doc); + this.codemirror.setCode(this.doc.text); + + /* fix line numbers bar */ + var $nums = $('.CodeMirror-line-numbers'); + var barWidth = $nums.width(); + + $(this.codemirror.frame.contentDocument.body).css('padding-left', barWidth); + // $nums.css('left', -barWidth); + + $(window).resize(); + this.unfreezeState(this._uistate); + + if(success) success(); + } + + CodeMirrorPerspective.prototype.onExit = function(success, failure) { + this.freezeState(); + + $.wiki.Perspective.prototype.onExit.call(this); + console.log('Exiting', this.doc); + this.doc.setText(this.codemirror.getCode()); + + if ($('.vsplitbar').hasClass('active') && $('#SearchPerspective').hasClass('active')) { + $.wiki.switchToTab('#ScanGalleryPerspective'); + } + + if(success) success(); + } + + $.wiki.CodeMirrorPerspective = CodeMirrorPerspective; + +})(jQuery); diff --git a/redakcja/static/js/wiki_img/view_summary.js b/redakcja/static/js/wiki_img/view_summary.js new file mode 100644 index 00000000..5d066470 --- /dev/null +++ b/redakcja/static/js/wiki_img/view_summary.js @@ -0,0 +1,25 @@ +(function($){ + + function SummaryPerspective(options) { + var old_callback = options.callback; + var self = this; + + $.wiki.Perspective.call(this, options); + }; + + SummaryPerspective.prototype = new $.wiki.Perspective(); + + SummaryPerspective.prototype.freezeState = function(){ + // must + }; + + SummaryPerspective.prototype.onEnter = function(success, failure){ + $.wiki.Perspective.prototype.onEnter.call(this); + + console.log("Entered summery view"); + }; + + $.wiki.SummaryPerspective = SummaryPerspective; + +})(jQuery); + diff --git a/redakcja/static/js/wiki_img/wikiapi.js b/redakcja/static/js/wiki_img/wikiapi.js new file mode 100644 index 00000000..2e4682c4 --- /dev/null +++ b/redakcja/static/js/wiki_img/wikiapi.js @@ -0,0 +1,307 @@ +(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") { + var path = "/" + arguments[1] + "/text"; + + if (arguments[2] !== undefined) + path += "/" + arguments[2]; + + return base_path + path; + } + + /*if (vname == "ajax_document_history") { + + return base_path + "/" + arguments[1] + "/history"; + } +*/ + if (vname == "ajax_document_gallery") { + + return base_path + "/" + arguments[1] + "/gallery"; + } +/* + if (vname == "ajax_document_diff") + return base_path + "/" + arguments[1] + "/diff"; + + if (vname == "ajax_document_rev") + return base_path + "/" + arguments[1] + "/rev"; + + if (vname == "ajax_document_addtag") + return base_path + "/" + arguments[1] + "/tags"; + + if (vname == "ajax_publish") + return base_path + "/" + arguments[1] + "/publish";*/ + + 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-document-name'); + + this.revision = $("*[data-key='revision']", meta).text(); + this.readonly = !!$("*[data-key='readonly']", meta).text(); + + this.galleryLink = $("*[data-key='gallery']", meta).text(); + this.galleryImages = []; + 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: {"revision": self.revision}, + dataType: 'json', + success: function(data) { + var changed = false; + + if (self.text === null || self.revision !== data.revision) { + self.text = data.text; + if (self.text === '') { + self.text = ''; + } + self.revision = data.revision; + 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."); + } + }); + }; + + /* + * 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; + 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: "

    Nie udało się zapisać zmian.

    " + }) + } + else { + try { + params['failure'](self, $.parseJSON(xhr.responseText)); + } + catch (e) { + params['failure'](self, { + "__message": "

    Nie udało się zapisać - błąd serwera.

    " + }); + }; + } + + } + }); + + $('#save-hide').click(function(){ + $('#header').addClass('saving'); + $.unblockUI(); + $.wiki.blocking.unblock(); + }); + }; /* end of save() */ + + WikiDocument.prototype.publish = function(params) { + params = $.extend({}, noops, params); + var self = this; + $.ajax({ + url: reverse("ajax_publish", self.id), + type: "POST", + dataType: "json", + success: function(data) { + params.success(self, data); + }, + error: function(xhr) { + if (xhr.status == 403 || xhr.status == 401) { + params.failure(self, "Nie masz uprawnień lub nie jesteś zalogowany."); + } + else { + try { + params.failure(self, xhr.responseText); + } + catch (e) { + params.failure(self, "Nie udało się - błąd serwera."); + }; + }; + + } + }); + }; + WikiDocument.prototype.setTag = function(params) { + params = $.extend({}, noops, params); + var self = this; + var data = { + "addtag-id": self.id, + }; + + /* unpack form */ + $.each(params.form.serializeArray(), function() { + data[this.name] = this.value; + }); + + $.ajax({ + url: reverse("ajax_document_addtag", 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 = []; + $(tag, doc).each(function(i, e) { + var $e = $(e); + a.push([ + $e.text(), + $e.attr('x1'), + $e.attr('y1'), + $e.attr('x2'), + $e.attr('y2') + ]); + }); + + 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; + } + + $(tag, doc).remove(); + $root = $(doc.firstChild); + $.each(items, function(i, e) { + var el = $(doc.createElement(tag)); + el.text(e[0]); + if (e[1] !== null) { + el.attr('x1', e[1]); + el.attr('y1', e[2]); + el.attr('x2', e[3]); + el.attr('y2', e[4]); + } + $root.append(el); + }); + self.setText(serializer.serializeToString(doc)); + } + + + $.wikiapi.WikiDocument = WikiDocument; +})(jQuery); diff --git a/redakcja/urls.py b/redakcja/urls.py index dd7f884d..08073a41 100644 --- a/redakcja/urls.py +++ b/redakcja/urls.py @@ -18,8 +18,9 @@ urlpatterns = patterns('', url(r'^admin/doc/', include('django.contrib.admindocs.urls')), (r'^admin/', include(admin.site.urls)), - url(r'^$', 'django.views.generic.simple.redirect_to', {'url': '/documents/'}), + url(r'^$', 'django.views.generic.simple.redirect_to', {'url': '/images/'}), url(r'^documents/', include('wiki.urls')), + url(r'^images/', include('wiki_img.urls')), # Static files (should be served by Apache) url(r'^%s(?P.+)$' % settings.MEDIA_URL[1:], 'django.views.static.serve',