From: Radek Czajka Date: Wed, 14 Dec 2011 13:44:29 +0000 (+0100) Subject: Merge master into img-playground. Image support with new management features. Missing... X-Git-Url: https://git.mdrn.pl/redakcja.git/commitdiff_plain/73ef2b8442dc95f8b7279de812c30ac8626d5f39?hp=c89776c783212b4e1ed572854bcf4308462d8b82 Merge master into img-playground. Image support with new management features. Missing history-related stuff, needs refactoring (doubled code). --- diff --git a/apps/catalogue/admin.py b/apps/catalogue/admin.py index a3faa98e..8ba803e7 100644 --- a/apps/catalogue/admin.py +++ b/apps/catalogue/admin.py @@ -9,5 +9,7 @@ class BookAdmin(admin.ModelAdmin): admin.site.register(models.Book, BookAdmin) admin.site.register(models.Chunk) - admin.site.register(models.Chunk.tag_model) + +admin.site.register(models.Image) +admin.site.register(models.Image.tag_model) diff --git a/apps/catalogue/migrations/0009_auto__add_imagechange__add_unique_imagechange_tree_revision__add_image.py b/apps/catalogue/migrations/0009_auto__add_imagechange__add_unique_imagechange_tree_revision__add_image.py new file mode 100644 index 00000000..4de5212e --- /dev/null +++ b/apps/catalogue/migrations/0009_auto__add_imagechange__add_unique_imagechange_tree_revision__add_image.py @@ -0,0 +1,251 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Adding model 'ImageChange' + db.create_table('catalogue_imagechange', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('author', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, blank=True)), + ('author_name', self.gf('django.db.models.fields.CharField')(max_length=128, null=True, blank=True)), + ('author_email', self.gf('django.db.models.fields.CharField')(max_length=128, null=True, blank=True)), + ('revision', self.gf('django.db.models.fields.IntegerField')(db_index=True)), + ('parent', self.gf('django.db.models.fields.related.ForeignKey')(default=None, related_name='children', null=True, blank=True, to=orm['catalogue.ImageChange'])), + ('merge_parent', self.gf('django.db.models.fields.related.ForeignKey')(default=None, related_name='merge_children', null=True, blank=True, to=orm['catalogue.ImageChange'])), + ('description', self.gf('django.db.models.fields.TextField')(default='', blank=True)), + ('created_at', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now, db_index=True)), + ('publishable', self.gf('django.db.models.fields.BooleanField')(default=False)), + ('tree', self.gf('django.db.models.fields.related.ForeignKey')(related_name='change_set', to=orm['catalogue.Image'])), + ('data', self.gf('django.db.models.fields.files.FileField')(max_length=100)), + )) + db.send_create_signal('catalogue', ['ImageChange']) + + # Adding M2M table for field tags on 'ImageChange' + db.create_table('catalogue_imagechange_tags', ( + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), + ('imagechange', models.ForeignKey(orm['catalogue.imagechange'], null=False)), + ('imagetag', models.ForeignKey(orm['catalogue.imagetag'], null=False)) + )) + db.create_unique('catalogue_imagechange_tags', ['imagechange_id', 'imagetag_id']) + + # Adding unique constraint on 'ImageChange', fields ['tree', 'revision'] + db.create_unique('catalogue_imagechange', ['tree_id', 'revision']) + + # Adding model 'ImagePublishRecord' + db.create_table('catalogue_imagepublishrecord', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('image', self.gf('django.db.models.fields.related.ForeignKey')(related_name='publish_log', to=orm['catalogue.Image'])), + ('timestamp', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), + ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])), + ('change', self.gf('django.db.models.fields.related.ForeignKey')(related_name='publish_log', to=orm['catalogue.ImageChange'])), + )) + db.send_create_signal('catalogue', ['ImagePublishRecord']) + + # Adding model 'ImageTag' + db.create_table('catalogue_imagetag', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField')(max_length=64)), + ('slug', self.gf('django.db.models.fields.SlugField')(db_index=True, max_length=64, unique=True, null=True, blank=True)), + ('ordering', self.gf('django.db.models.fields.IntegerField')()), + )) + db.send_create_signal('catalogue', ['ImageTag']) + + # Adding model 'Image' + db.create_table('catalogue_image', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, blank=True)), + ('image', self.gf('django.db.models.fields.files.FileField')(max_length=100)), + ('title', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)), + ('slug', self.gf('django.db.models.fields.SlugField')(unique=True, max_length=50, db_index=True)), + ('public', self.gf('django.db.models.fields.BooleanField')(default=True, db_index=True)), + ('_short_html', self.gf('django.db.models.fields.TextField')(null=True, blank=True)), + ('_new_publishable', self.gf('django.db.models.fields.NullBooleanField')(null=True, blank=True)), + ('_published', self.gf('django.db.models.fields.NullBooleanField')(null=True, blank=True)), + ('_changed', self.gf('django.db.models.fields.NullBooleanField')(null=True, blank=True)), + ('stage', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.ImageTag'], null=True, blank=True)), + ('head', self.gf('django.db.models.fields.related.ForeignKey')(default=None, to=orm['catalogue.ImageChange'], null=True, blank=True)), + ('creator', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='created_image', null=True, to=orm['auth.User'])), + )) + db.send_create_signal('catalogue', ['Image']) + + + def backwards(self, orm): + + # Removing unique constraint on 'ImageChange', fields ['tree', 'revision'] + db.delete_unique('catalogue_imagechange', ['tree_id', 'revision']) + + # Deleting model 'ImageChange' + db.delete_table('catalogue_imagechange') + + # Removing M2M table for field tags on 'ImageChange' + db.delete_table('catalogue_imagechange_tags') + + # Deleting model 'ImagePublishRecord' + db.delete_table('catalogue_imagepublishrecord') + + # Deleting model 'ImageTag' + db.delete_table('catalogue_imagetag') + + # Deleting model 'Image' + db.delete_table('catalogue_image') + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'catalogue.book': { + 'Meta': {'ordering': "['title', 'slug']", 'object_name': 'Book'}, + '_new_publishable': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_published': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + '_single': ('django.db.models.fields.NullBooleanField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + 'dc_slug': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '128', 'null': 'True', 'blank': 'True'}), + 'gallery': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['catalogue.Book']"}), + 'parent_number': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + 'public': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '128', 'db_index': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}) + }, + 'catalogue.bookpublishrecord': { + 'Meta': {'ordering': "['-timestamp']", 'object_name': 'BookPublishRecord'}, + 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.Book']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'catalogue.chunk': { + 'Meta': {'ordering': "['number']", 'unique_together': "[['book', 'number'], ['book', 'slug']]", 'object_name': 'Chunk'}, + '_changed': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_hidden': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'created_chunk'", 'null': 'True', 'to': "orm['auth.User']"}), + 'gallery_start': ('django.db.models.fields.IntegerField', [], {'default': '1', 'null': 'True', 'blank': 'True'}), + 'head': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['catalogue.ChunkChange']", 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'number': ('django.db.models.fields.IntegerField', [], {}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}), + 'stage': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ChunkTag']", 'null': 'True', 'blank': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}) + }, + 'catalogue.chunkchange': { + 'Meta': {'ordering': "('created_at',)", 'unique_together': "(['tree', 'revision'],)", 'object_name': 'ChunkChange'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'author_email': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), + 'author_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}), + 'data': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), + 'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'merge_parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'merge_children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}), + 'publishable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'revision': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'change_set'", 'symmetrical': 'False', 'to': "orm['catalogue.ChunkTag']"}), + 'tree': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'change_set'", 'to': "orm['catalogue.Chunk']"}) + }, + 'catalogue.chunkpublishrecord': { + 'Meta': {'object_name': 'ChunkPublishRecord'}, + 'book_record': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.BookPublishRecord']"}), + 'change': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.ChunkChange']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + 'catalogue.chunktag': { + 'Meta': {'ordering': "['ordering']", 'object_name': 'ChunkTag'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'ordering': ('django.db.models.fields.IntegerField', [], {}), + 'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'}) + }, + 'catalogue.image': { + 'Meta': {'ordering': "['title']", 'object_name': 'Image'}, + '_changed': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_new_publishable': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_published': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'created_image'", 'null': 'True', 'to': "orm['auth.User']"}), + 'head': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['catalogue.ImageChange']", 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'image': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), + 'public': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50', 'db_index': 'True'}), + 'stage': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ImageTag']", 'null': 'True', 'blank': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}) + }, + 'catalogue.imagechange': { + 'Meta': {'ordering': "('created_at',)", 'unique_together': "(['tree', 'revision'],)", 'object_name': 'ImageChange'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'author_email': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), + 'author_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}), + 'data': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), + 'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'merge_parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'merge_children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ImageChange']"}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ImageChange']"}), + 'publishable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'revision': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'change_set'", 'symmetrical': 'False', 'to': "orm['catalogue.ImageTag']"}), + 'tree': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'change_set'", 'to': "orm['catalogue.Image']"}) + }, + 'catalogue.imagepublishrecord': { + 'Meta': {'ordering': "['-timestamp']", 'object_name': 'ImagePublishRecord'}, + 'change': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.ImageChange']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'image': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.Image']"}), + 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'catalogue.imagetag': { + 'Meta': {'ordering': "['ordering']", 'object_name': 'ImageTag'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'ordering': ('django.db.models.fields.IntegerField', [], {}), + 'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + } + } + + complete_apps = ['catalogue'] diff --git a/apps/catalogue/models/__init__.py b/apps/catalogue/models/__init__.py index 6161807b..82e1c116 100755 --- a/apps/catalogue/models/__init__.py +++ b/apps/catalogue/models/__init__.py @@ -4,6 +4,7 @@ # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. # from catalogue.models.chunk import Chunk +from catalogue.models.image import Image from catalogue.models.publish_log import BookPublishRecord, ChunkPublishRecord from catalogue.models.book import Book from catalogue.models.listeners import * diff --git a/apps/catalogue/models/image.py b/apps/catalogue/models/image.py new file mode 100755 index 00000000..53f8830d --- /dev/null +++ b/apps/catalogue/models/image.py @@ -0,0 +1,95 @@ +# -*- coding: utf-8 -*- +# +# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# +from django.conf import settings +from django.db import models +from django.template.loader import render_to_string +from django.utils.translation import ugettext_lazy as _ +from catalogue.helpers import cached_in_field +from catalogue.tasks import refresh_instance +from dvcs import models as dvcs_models + + +class Image(dvcs_models.Document): + """ An editable chunk of text. Every Book text is divided into chunks. """ + REPO_PATH = settings.CATALOGUE_IMAGE_REPO_PATH + + image = models.FileField(_('image'), upload_to='catalogue/images') + title = models.CharField(_('title'), max_length=255, blank=True) + slug = models.SlugField(_('slug'), unique=True) + public = models.BooleanField(_('public'), default=True, db_index=True) + + # cache + _short_html = models.TextField(null=True, blank=True, editable=False) + _new_publishable = models.NullBooleanField(editable=False) + _published = models.NullBooleanField(editable=False) + _changed = models.NullBooleanField(editable=False) + + class Meta: + app_label = 'catalogue' + ordering = ['title'] + verbose_name = _('image') + verbose_name_plural = _('images') + permissions = [('can_pubmark_image', 'Can mark images for publishing')] + + # Representing + # ============ + + def __unicode__(self): + return self.title + + @models.permalink + def get_absolute_url(self): + return ("wiki_img_editor", [self.slug]) + + # State & cache + # ============= + + def accessible(self, request): + return self.public or request.user.is_authenticated() + + def is_new_publishable(self): + change = self.publishable() + if not change: + return False + return change.publish_log.exists() + new_publishable = cached_in_field('_new_publishable')(is_new_publishable) + + def is_published(self): + return self.publish_log.exists() + published = cached_in_field('_published')(is_published) + + def is_changed(self): + if self.head is None: + return False + return not self.head.publishable + changed = cached_in_field('_changed')(is_changed) + + @cached_in_field('_short_html') + def short_html(self): + return render_to_string( + 'catalogue/image_short.html', {'image': self}) + + def refresh(self): + """This should be done offline.""" + self.short_html + self.single + self.new_publishable + self.published + + def touch(self): + update = { + "_changed": self.is_changed(), + "_short_html": None, + "_new_publishable": self.is_new_publishable(), + "_published": self.is_published(), + } + Image.objects.filter(pk=self.pk).update(**update) + refresh_instance(self) + + def refresh(self): + """This should be done offline.""" + self.changed + self.short_html diff --git a/apps/catalogue/models/listeners.py b/apps/catalogue/models/listeners.py index 532f1e79..de1387ee 100755 --- a/apps/catalogue/models/listeners.py +++ b/apps/catalogue/models/listeners.py @@ -5,7 +5,7 @@ # from django.contrib.auth.models import User from django.db import models -from catalogue.models import Book, Chunk +from catalogue.models import Book, Chunk, Image from catalogue.signals import post_publish from dvcs.signals import post_publishable @@ -23,6 +23,11 @@ def chunk_changed(sender, instance, created, **kwargs): models.signals.post_save.connect(chunk_changed, sender=Chunk) +def image_changed(sender, instance, created, **kwargs): + instance.touch() +models.signals.post_save.connect(image_changed, sender=Image) + + def user_changed(sender, instance, *args, **kwargs): books = set() for c in instance.chunk_set.all(): diff --git a/apps/catalogue/models/publish_log.py b/apps/catalogue/models/publish_log.py index f422e377..6cc86d08 100755 --- a/apps/catalogue/models/publish_log.py +++ b/apps/catalogue/models/publish_log.py @@ -6,7 +6,7 @@ from django.contrib.auth.models import User from django.db import models from django.utils.translation import ugettext_lazy as _ -from catalogue.models import Chunk +from catalogue.models import Chunk, Image class BookPublishRecord(models.Model): @@ -37,3 +37,18 @@ class ChunkPublishRecord(models.Model): app_label = 'catalogue' verbose_name = _('chunk publish record') verbose_name = _('chunk publish records') + + +class ImagePublishRecord(models.Model): + """A record left after publishing an Image.""" + + image = models.ForeignKey(Image, verbose_name=_('image'), related_name='publish_log') + timestamp = models.DateTimeField(_('time'), auto_now_add=True) + user = models.ForeignKey(User, verbose_name=_('user')) + change = models.ForeignKey(Image.change_model, related_name='publish_log', verbose_name=_('change')) + + class Meta: + app_label = 'catalogue' + ordering = ['-timestamp'] + verbose_name = _('image publish record') + verbose_name = _('image publish records') diff --git a/apps/catalogue/templates/catalogue/book_html.html b/apps/catalogue/templates/catalogue/book_html.html new file mode 100755 index 00000000..af4cfa79 --- /dev/null +++ b/apps/catalogue/templates/catalogue/book_html.html @@ -0,0 +1,30 @@ +{% load i18n %} +{% load compressed %} + + + + + {{ book.title }} + + + +
+ {#% book_info book %#} +
+ + + {{ html|safe }} + + + diff --git a/apps/catalogue/templates/catalogue/image_list.html b/apps/catalogue/templates/catalogue/image_list.html new file mode 100755 index 00000000..3ff75bc0 --- /dev/null +++ b/apps/catalogue/templates/catalogue/image_list.html @@ -0,0 +1,9 @@ +{% extends "catalogue/base.html" %} + +{% load i18n %} +{% load catalogue book_list %} + + +{% block content %} + {% image_list %} +{% endblock content %} diff --git a/apps/catalogue/templates/catalogue/image_short.html b/apps/catalogue/templates/catalogue/image_short.html new file mode 100755 index 00000000..2e2b386c --- /dev/null +++ b/apps/catalogue/templates/catalogue/image_short.html @@ -0,0 +1,18 @@ +{% load i18n %} + + + [B] + + {{ image.title }} + {% if image.stage %} + {{ image.stage }} + {% else %}– + {% endif %} + {% if image.user %}{{ image.user.first_name }} {{ image.user.last_name }}{% endif %} + + {% if image.published %}P{% endif %} + {% if image.new_publishable %}p{% endif %} + {% if image.changed %}+{% endif %} + + diff --git a/apps/catalogue/templates/catalogue/image_table.html b/apps/catalogue/templates/catalogue/image_table.html new file mode 100755 index 00000000..68293e77 --- /dev/null +++ b/apps/catalogue/templates/catalogue/image_table.html @@ -0,0 +1,69 @@ +{% load i18n %} +{% load pagination_tags %} + + +
+ + +{% if not viewed_user %} + +{% endif %} + +
+ + + + + + + + {% if not viewed_user %} + + {% endif %} + + + + + + {% with cnt=objects|length %} + {% autopaginate objects 100 %} + + {% for item in objects %} + {{ item.short_html|safe }} + {% endfor %} + + + {% endwith %} +
+
+ +
+
+ {% paginate %} + {% blocktrans count c=cnt %}{{c}} image{% plural %}{{c}} images{% endblocktrans %}
+{% if not objects %} +

{% trans "No images found." %}

+{% endif %} diff --git a/apps/catalogue/templates/catalogue/upload_pdf.html b/apps/catalogue/templates/catalogue/upload_pdf.html new file mode 100755 index 00000000..a9670e47 --- /dev/null +++ b/apps/catalogue/templates/catalogue/upload_pdf.html @@ -0,0 +1,17 @@ +{% extends "catalogue/base.html" %} +{% load i18n %} + + +{% block content %} + + +

{% trans "PDF file upload" %}

+ +
+{% csrf_token %} +{{ form.as_p }} +

+
+ + +{% endblock content %} diff --git a/apps/catalogue/templatetags/book_list.py b/apps/catalogue/templatetags/book_list.py index f7e70474..5e18b7e2 100755 --- a/apps/catalogue/templatetags/book_list.py +++ b/apps/catalogue/templatetags/book_list.py @@ -5,7 +5,7 @@ from django.db.models import Q, Count from django import template from django.utils.translation import ugettext_lazy as _ from django.contrib.auth.models import User -from catalogue.models import Chunk +from catalogue.models import Chunk, Image register = template.Library() @@ -138,3 +138,58 @@ def book_list(context, user=None): }) return new_context + + + +_image_states = [ + ('publishable', _('publishable'), Q(_new_publishable=True)), + ('changed', _('changed'), Q(_changed=True)), + ('published', _('published'), Q(_published=True)), + ('unpublished', _('unpublished'), Q(_published=False)), + ('empty', _('empty'), Q(head=None)), + ] +_image_states_options = [s[:2] for s in _states] +_image_states_dict = dict([(s[0], s[2]) for s in _states]) + +def image_list_filter(request, **kwargs): + + def arg_or_GET(field): + return kwargs.get(field, request.GET.get(field)) + + images = Image.objects.all() + + if not request.user.is_authenticated(): + images = images.filter(public=True) + + state = arg_or_GET('status') + if state in _image_states_dict: + images = images.filter(_image_states_dict[state]) + + images = foreign_filter(images, arg_or_GET('user'), 'user', User, 'username') + images = foreign_filter(images, arg_or_GET('stage'), 'stage', Image.tag_model, 'slug') + images = search_filter(images, arg_or_GET('title'), ['title', 'title']) + return images + + +@register.inclusion_tag('catalogue/image_table.html', takes_context=True) +def image_list(context, user=None): + request = context['request'] + + if user: + filters = {"user": user} + new_context = {"viewed_user": user} + else: + filters = {} + new_context = {"users": User.objects.annotate( + count=Count('chunk')).filter(count__gt=0).order_by( + '-count', 'last_name', 'first_name')} + + new_context.update({ + "filters": True, + "request": request, + "objects": image_list_filter(request, **filters), + "stages": Image.tag_model.objects.all(), + "states": _image_states_options, + }) + + return new_context diff --git a/apps/catalogue/templatetags/catalogue.py b/apps/catalogue/templatetags/catalogue.py index 3cc7210c..0b57b498 100644 --- a/apps/catalogue/templatetags/catalogue.py +++ b/apps/catalogue/templatetags/catalogue.py @@ -28,6 +28,7 @@ def main_tabs(context): tabs.append(Tab('activity', _('Activity'), reverse("catalogue_activity"))) tabs.append(Tab('all', _('All'), reverse("catalogue_document_list"))) + tabs.append(Tab('images', _('Images'), reverse("catalogue_image_list"))) tabs.append(Tab('users', _('Users'), reverse("catalogue_users"))) if user.has_perm('catalogue.add_book'): diff --git a/apps/catalogue/urls.py b/apps/catalogue/urls.py index ab9b5704..621eb12a 100644 --- a/apps/catalogue/urls.py +++ b/apps/catalogue/urls.py @@ -6,6 +6,9 @@ from django.views.generic.simple import redirect_to urlpatterns = patterns('catalogue.views', url(r'^$', redirect_to, {'url': 'catalogue/'}), + url(r'^images/$', 'image_list', name='catalogue_image_list'), + url(r'^image/(?P[^/]+)/$', 'image', name="catalogue_image"), + url(r'^catalogue/$', 'document_list', name='catalogue_document_list'), url(r'^user/$', 'my', name='catalogue_user'), url(r'^user/(?P[^/]+)/$', 'user', name='catalogue_user'), diff --git a/apps/catalogue/views.py b/apps/catalogue/views.py index 3c0e70ff..3c37ee60 100644 --- a/apps/catalogue/views.py +++ b/apps/catalogue/views.py @@ -40,6 +40,12 @@ def document_list(request): return render(request, 'catalogue/document_list.html') +@active_tab('images') +@never_cache +def image_list(request, user=None): + return render(request, 'catalogue/image_list.html') + + @never_cache def user(request, username): user = get_object_or_404(User, username=username) @@ -333,6 +339,37 @@ def book(request, slug): }) +def image(request, slug): + image = get_object_or_404(Image, slug=slug) + if not image.accessible(request): + return HttpResponseForbidden("Not authorized.") + + if request.user.has_perm('catalogue.change_image'): + if request.method == "POST": + form = forms.ImageForm(request.POST, instance=image) + if form.is_valid(): + form.save() + return http.HttpResponseRedirect(image.get_absolute_url()) + else: + form = forms.ImageForm(instance=image) + editable = True + else: + form = forms.ReadonlyImageForm(instance=image) + editable = False + + #publish_error = publishable_error(book) + publish_error = 'Publishing not implemented yet.' + publishable = publish_error is None + + return direct_to_template(request, "catalogue/image_detail.html", extra_context={ + "object": image, + "publishable": publishable, + "publishable_error": publish_error, + "form": form, + "editable": editable, + }) + + @permission_required('catalogue.add_chunk') def chunk_add(request, slug, chunk): try: 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 ae309a9d..90da85e6 100644 --- a/apps/wiki/admin.py +++ b/apps/wiki/admin.py @@ -2,7 +2,6 @@ from django.contrib import admin from wiki import models - class ThemeAdmin(admin.ModelAdmin): search_fields = ['name'] 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/forms.py b/apps/wiki_img/forms.py new file mode 100644 index 00000000..555f2647 --- /dev/null +++ b/apps/wiki_img/forms.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# +# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# +from django import forms +from django.utils.translation import ugettext_lazy as _ +from wiki.forms import DocumentTextSaveForm +from catalogue.models import Image + + +class ImageSaveForm(DocumentTextSaveForm): + """Form for saving document's text.""" + + stage_completed = forms.ModelChoiceField( + queryset=Image.tag_model.objects.all(), + required=False, + label=_(u"Completed"), + help_text=_(u"If you completed a life cycle stage, select it."), + ) diff --git a/apps/wiki_img/locale/pl/LC_MESSAGES/django.mo b/apps/wiki_img/locale/pl/LC_MESSAGES/django.mo new file mode 100644 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..b685324b --- /dev/null +++ b/apps/wiki_img/models.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# +# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# diff --git a/apps/wiki_img/templates/wiki_img/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_details.html b/apps/wiki_img/templates/wiki_img/document_details.html new file mode 100644 index 00000000..fc2e2076 --- /dev/null +++ b/apps/wiki_img/templates/wiki_img/document_details.html @@ -0,0 +1,34 @@ +{% 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" %} + {% include "wiki/tabs/history_view_item.html" %} +{% endblock %} + +{% block tabs-content %} + {% include "wiki_img/tabs/summary_view.html" %} + {% include "wiki_img/tabs/motifs_editor.html" %} + {% include "wiki_img/tabs/objects_editor.html" %} + {% include "wiki_img/tabs/source_editor.html" %} + {% include "wiki_img/tabs/history_view.html" %} +{% endblock %} + +{% block dialogs %} + {% include "wiki_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..8cba7bf3 --- /dev/null +++ b/apps/wiki_img/templates/wiki_img/document_details_base.html @@ -0,0 +1,52 @@ +{% 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..ca38838e --- /dev/null +++ b/apps/wiki_img/templates/wiki_img/document_details_readonly.html @@ -0,0 +1,26 @@ +{% extends "wiki_img/document_details_base.html" %} +{% load i18n %} + +{% block extrabody %} +{{ block.super }} + + +{% endblock %} + +{% block tabs-menu %} + {% include "wiki_img/tabs/motifs_editor_item.html" %} + {% include "wiki_img/tabs/objects_editor_item.html" %} + {% include "wiki_img/tabs/source_editor_item.html" %} +{% endblock %} + +{% block tabs-content %} + {% include "wiki_img/tabs/motifs_editor.html" %} + {% include "wiki_img/tabs/objects_editor.html" %} + {% include "wiki_img/tabs/source_editor.html" %} +{% endblock %} + +{% block editor-class %} + sideless +{% endblock %} + diff --git a/apps/wiki_img/templates/wiki_img/save_dialog.html b/apps/wiki_img/templates/wiki_img/save_dialog.html new file mode 100644 index 00000000..e8f89e6a --- /dev/null +++ b/apps/wiki_img/templates/wiki_img/save_dialog.html @@ -0,0 +1,25 @@ +{% load i18n %} +
+
+ {% csrf_token %} +

{{ 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/history_view.html b/apps/wiki_img/templates/wiki_img/tabs/history_view.html new file mode 100755 index 00000000..db49d648 --- /dev/null +++ b/apps/wiki_img/templates/wiki_img/tabs/history_view.html @@ -0,0 +1,40 @@ +{% load i18n %} + 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..b4396c6c --- /dev/null +++ b/apps/wiki_img/urls.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 +from django.conf.urls.defaults import * + + +urlpatterns = patterns('wiki_img.views', + url(r'^edit/(?P[^/]+)/$', + 'editor', name="wiki_img_editor"), + + url(r'^readonly/(?P[^/]+)/$', + 'editor_readonly', name="wiki_img_editor_readonly"), + + url(r'^text/(?P\d+)/$', + 'text', name="wiki_img_text"), + + url(r'^history/(?P\d+)/$', + 'history', name="wiki_history"), + +) diff --git a/apps/wiki_img/views.py b/apps/wiki_img/views.py new file mode 100644 index 00000000..9e87f660 --- /dev/null +++ b/apps/wiki_img/views.py @@ -0,0 +1,129 @@ +import os +import functools +import logging +logger = logging.getLogger("fnp.wiki_img") + +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.views.decorators.http import require_GET +from django.conf import settings +from django.utils.formats import localize + +from catalogue.models import Image +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(Image, slug=slug) + + return direct_to_template(request, template_name, extra_context={ + 'document': doc, + 'forms': { + "text_save": DocumentTextSaveForm(user=request.user, prefix="textsave"), + }, + 'REDMINE_URL': settings.REDMINE_URL, + }) + + +@require_GET +def editor_readonly(request, slug, template_name='wiki_img/document_details_readonly.html'): + doc = get_object_or_404(Image, slug=slug) + try: + revision = request.GET['revision'] + except (KeyError): + raise Http404 + + + + return direct_to_template(request, template_name, extra_context={ + 'document': doc, + 'revision': revision, + 'readonly': True, + 'REDMINE_URL': settings.REDMINE_URL, + }) + + +@never_cache +def text(request, image_id): + doc = get_object_or_404(Image, pk=image_id) + if request.method == 'POST': + form = DocumentTextSaveForm(request.POST, user=request.user, prefix="textsave") + if form.is_valid(): + if request.user.is_authenticated(): + author = request.user + else: + author = None + text = form.cleaned_data['text'] + parent_revision = form.cleaned_data['parent_revision'] + if parent_revision is not None: + parent = doc.at_revision(parent_revision) + else: + parent = None + stage = form.cleaned_data['stage_completed'] + tags = [stage] if stage else [] + publishable = (form.cleaned_data['publishable'] and + request.user.has_perm('catalogue.can_pubmark')) + doc.commit(author=author, + text=text, + parent=parent, + description=form.cleaned_data['comment'], + tags=tags, + author_name=form.cleaned_data['author_name'], + author_email=form.cleaned_data['author_email'], + publishable=publishable, + ) + revision = doc.revision() + return JSONResponse({ + 'text': doc.materialize() if parent_revision != revision else None, + 'meta': {}, + 'revision': revision, + }) + else: + return JSONFormInvalid(form) + else: + revision = request.GET.get("revision", None) + + try: + revision = int(revision) + except (ValueError, TypeError): + revision = doc.revision() + + if revision is not None: + text = doc.at_revision(revision).materialize() + else: + text = '' + + return JSONResponse({ + 'text': text, + 'meta': {}, + 'revision': revision, + }) + + +@never_cache +def history(request, chunk_id): + # TODO: pagination + doc = get_object_or_404(Image, pk=chunk_id) + if not doc.accessible(request): + return HttpResponseForbidden("Not authorized.") + + changes = [] + for change in doc.history().reverse(): + changes.append({ + "version": change.revision, + "description": change.description, + "author": change.author_str(), + "date": localize(change.created_at), + "publishable": _("Publishable") + "\n" if change.publishable else "", + "tag": ',\n'.join(unicode(tag) for tag in change.tags.all()), + }) + return JSONResponse(changes) diff --git a/redakcja/settings/common.py b/redakcja/settings/common.py index 1e16bb1e..cc7765e6 100644 --- a/redakcja/settings/common.py +++ b/redakcja/settings/common.py @@ -120,6 +120,7 @@ INSTALLED_APPS = ( 'catalogue', 'dvcs', 'wiki', + 'wiki_img', 'toolbar', 'apiclient', 'email_mangler', diff --git a/redakcja/settings/compress.py b/redakcja/settings/compress.py index d4425c21..34cfa2ff 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', @@ -58,6 +59,37 @@ 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', + 'js/wiki/view_history.js', + ), + 'output_filename': 'compressed/detail_img_scripts_?.js', + }, 'catalogue': { 'source_filenames': ( 'js/catalogue/catalogue.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 75bdc638..c1060bcd 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; } @@ -353,3 +378,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..84f012c4 --- /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_commit']", this.$form).val(CurrentDocument.commit); + + $.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..9cfc640d --- /dev/null +++ b/redakcja/static/js/wiki_img/loader.js @@ -0,0 +1,136 @@ +if (!window.console) { + window.console = { + log: function(){ + } + } +} + +DEFAULT_PERSPECTIVE = "#MotifsPerspective"; + +$(function() +{ + var tabs = $('ol#tabs li'); + var gallery = null; + CurrentDocument = new $.wikiapi.WikiDocument("document-meta"); + + $.blockUI.defaults.baseZ = 10000; + + function initialize() + { + $(document).keydown(function(event) { + console.log("Received key:", event); + }); + + /* The save button */ + $('#save-button').click(function(event){ + event.preventDefault(); + $.wiki.showDialog('#save_dialog'); + }); + + $('.editor').hide(); + + /* + * TABS + */ + $('.tabs li').live('click', function(event, callback) { + $.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); +}); + + diff --git a/redakcja/static/js/wiki_img/loader_readonly.js b/redakcja/static/js/wiki_img/loader_readonly.js new file mode 100755 index 00000000..1ce15b77 --- /dev/null +++ b/redakcja/static/js/wiki_img/loader_readonly.js @@ -0,0 +1,92 @@ +if (!window.console) { + window.console = { + log: function(){ + } + } +} + + +DEFAULT_PERSPECTIVE = "#MotifsPerspective"; + +$(function() +{ + var tabs = $('ol#tabs li'); + var gallery = null; + + CurrentDocument = new $.wikiapi.WikiDocument("document-meta"); + $.blockUI.defaults.baseZ = 10000; + + function initialize() + { + $('.editor').hide(); + + /* + * TABS + */ + $('#tabs li').live('click', function(event, callback) { + $.wiki.switchToTab(this); + }); + + $('#tabs li > .tabclose').live('click', function(event, callback) { + var $tab = $(this).parent(); + + if($tab.is('.active')) + $.wiki.switchToTab(DEFAULT_PERSPECTIVE); + + var p = $.wiki.perspectiveForTab($tab); + p.destroy(); + return false; + }); + + $(window).resize(function(){ + $('iframe').height($(window).height() - $('#tabs').outerHeight() - $('#source-editor .toolbar').outerHeight()); + }); + + $(document).bind('wlapi_document_changed', function(event, doc) { + try { + $('#document-revision').text(doc.revision); + } catch(e) { + console.log("Failed handler", e); + } + }); + + CurrentDocument.fetch({ + success: function(){ + console.log("Fetch success"); + $('#loading-overlay').fadeOut(); + var active_tab = document.location.hash || DEFAULT_PERSPECTIVE; + + $(window).resize(); + + console.log("Initial tab is:", active_tab) + $.wiki.switchToTab(active_tab); + }, + failure: function() { + $('#loading-overlay').fadeOut(); + alert("FAILURE"); + } + }); + }; /* end of initialize() */ + + /* Load configuration */ + $.wiki.loadConfig(); + + var initAll = function(a, f) { + if (a.length == 0) return f(); + + $.wiki.initTab({ + tab: a.pop(), + doc: CurrentDocument, + callback: function(){ + initAll(a, f); + } + }); + }; + + + /* + * Initialize all perspectives + */ + initAll( $.makeArray($('ol#tabs li')), initialize); + console.log(location.hash); +}); diff --git a/redakcja/static/js/wiki_img/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..0f56ffe7 --- /dev/null +++ b/redakcja/static/js/wiki_img/wikiapi.js @@ -0,0 +1,350 @@ +(function($) { + $.wikiapi = {}; + var noop = function() { + }; + var noops = { + success: noop, + failure: noop + }; + /* + * Return absolute reverse path of given named view. (at least he have it + * hard-coded in one place) + * + * TODO: think of a way, not to hard-code it here ;) + * + */ + function reverse() { + var vname = arguments[0]; + var base_path = "/images"; + + if (vname == "ajax_document_text") { + return base_path + "/text/" + arguments[1] + "/"; + } + + if (vname == "ajax_document_history") { + + return base_path + "/history/" + arguments[1] + "/"; + } +*/ +/* + 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-object-id'); + + this.revision = $("*[data-key='revision']", meta).text(); + this.readonly = !!$("*[data-key='readonly']", meta).text(); + + this.text = null; + this.has_local_changes = false; + this._lock = -1; + this._context_lock = -1; + this._lock_count = 0; + }; + + WikiDocument.prototype.triggerDocumentChanged = function() { + $(document).trigger('wlapi_document_changed', this); + }; + /* + * Fetch text of this document. + */ + WikiDocument.prototype.fetch = function(params) { + params = $.extend({}, noops, params); + var self = this; + $.ajax({ + method: "GET", + url: reverse("ajax_document_text", self.id), + data: {"commit": self.commit}, + dataType: 'json', + success: function(data) { + var changed = false; + + if (self.text === null || self.commit !== data.commit) { + self.text = data.text; + if (self.text === '') { + self.text = ''; + } + self.revision = data.revision; + self.commit = data.commit; + changed = true; + self.triggerDocumentChanged(); + }; + + self.has_local_changes = false; + params['success'](self, changed); + }, + error: function() { + params['failure'](self, "Nie udało się wczytać treści dokumentu."); + } + }); + }; + /* + * Fetch history of this document. + * + * from - First revision to fetch (default = 0) upto - Last revision to + * fetch (default = tip) + * + */ + WikiDocument.prototype.fetchHistory = function(params) { + /* this doesn't modify anything, so no locks */ + params = $.extend({}, noops, params); + var self = this; + $.ajax({ + method: "GET", + url: reverse("ajax_document_history", self.id), + dataType: 'json', + data: { + "from": params['from'], + "upto": params['upto'] + }, + success: function(data) { + params['success'](self, data); + }, + error: function() { + params['failure'](self, "Nie udało się wczytać historii dokumentu."); + } + }); + }; + + /* + * Set document's text + */ + WikiDocument.prototype.setText = function(text) { + this.text = text; + this.has_local_changes = true; + }; + + /* + * Save text back to the server + */ + WikiDocument.prototype.save = function(params) { + params = $.extend({}, noops, params); + var self = this; + + if (!self.has_local_changes) { + console.log("Abort: no changes."); + return params['success'](self, false, "Nie ma zmian do zapisania."); + }; + + // Serialize form to dictionary + var data = {}; + $.each(params['form'].serializeArray(), function() { + data[this.name] = this.value; + }); + + data['textsave-text'] = self.text; + + $.ajax({ + url: reverse("ajax_document_text", self.id), + type: "POST", + dataType: "json", + data: data, + success: function(data) { + var changed = false; + + $('#header').removeClass('saving'); + + if (data.text) { + self.text = data.text; + self.revision = data.revision; + self.commit = data.commit; + changed = true; + self.triggerDocumentChanged(); + }; + + params['success'](self, changed, ((changed && "Udało się zapisać :)") || "Twoja wersja i serwera jest identyczna")); + }, + error: function(xhr) { + if ($('#header').hasClass('saving')) { + $('#header').removeClass('saving'); + $.blockUI({ + message: "

    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); + + + +// Wykonuje block z załadowanymi kanonicznymi motywami +function withThemes(code_block, onError) +{ + if (typeof withThemes.canon == 'undefined') { + $.ajax({ + url: '/editor/themes', + dataType: 'text', + success: function(data) { + withThemes.canon = data.split('\n'); + code_block(withThemes.canon); + }, + error: function() { + withThemes.canon = null; + code_block(withThemes.canon); + } + }) + } + else { + code_block(withThemes.canon); + } +} + diff --git a/redakcja/urls.py b/redakcja/urls.py index 4aedf58f..2343b05d 100644 --- a/redakcja/urls.py +++ b/redakcja/urls.py @@ -26,6 +26,7 @@ urlpatterns = patterns('', url(r'^documents/', include('catalogue.urls')), url(r'^apiclient/', include('apiclient.urls')), url(r'^editor/', include('wiki.urls')), + url(r'^images/', include('wiki_img.urls')), # Static files (should be served by Apache) url(r'^%s(?P.+)$' % settings.MEDIA_URL[1:], 'django.views.static.serve',