From: zuber <marek@stepniowski.com>
Date: Thu, 8 Oct 2009 12:48:15 +0000 (+0200)
Subject: Merge branch 'master' of stigma.nowoczesnapolska.org.pl:platforma
X-Git-Url: https://git.mdrn.pl/redakcja.git/commitdiff_plain/4dae2117bd399c1076973d325c6425e79fda1220?hp=f53e407d37ef0c786b280e6bc1b2f3dcace714a7

Merge branch 'master' of stigma.nowoczesnapolska.org.pl:platforma
---

diff --git a/apps/api/handlers/library_handlers.py b/apps/api/handlers/library_handlers.py
index b7260674..4435fe96 100644
--- a/apps/api/handlers/library_handlers.py
+++ b/apps/api/handlers/library_handlers.py
@@ -13,10 +13,11 @@ from datetime import date
 
 from django.core.urlresolvers import reverse
 from django.utils import simplejson as json
+from django.db import IntegrityError
 
 import librarian
 import librarian.html
-from librarian import dcparser
+from librarian import dcparser, parser
 
 from wlrepo import *
 from explorer.models import PullRequest, GalleryForDocument
@@ -229,7 +230,12 @@ class DocumentHTMLHandler(BaseHandler):
                 return response.BadRequest().django_response({'reason': 'name-mismatch',
                     'message': 'Provided revision refers, to document "%s", but provided "%s"' % (document.id, docid) })
 
-            return librarian.html.transform(document.data('xml'), is_file=False, parse_dublincore=False)
+            return librarian.html.transform(document.data('xml'), is_file=False, \
+                parse_dublincore=False, stylesheet="partial",\
+                options={
+                    "with-paths": 'boolean(1)',                    
+                })
+                
         except (EntryNotFound, RevisionNotFound), e:
             return response.EntityNotFound().django_response({
                 'reason': 'not-found', 'message': e.message})
@@ -287,6 +293,7 @@ XINCLUDE_REGEXP = r"""<(?:\w+:)?include\s+[^>]*?href=("|')wlrepo://(?P<link>[^\1
 #
 #
 #
+
 class DocumentTextHandler(BaseHandler):
     allowed_methods = ('GET', 'POST')
 
@@ -294,6 +301,8 @@ class DocumentTextHandler(BaseHandler):
     def read(self, request, docid, lib):
         """Read document as raw text"""
         revision = request.GET.get('revision', 'latest')
+        part = request.GET.get('part', False)
+        
         try:
             if revision == 'latest':
                 document = lib.document(docid)
@@ -305,22 +314,35 @@ class DocumentTextHandler(BaseHandler):
                     'message': 'Provided revision is not valid for this document'})
             
             # TODO: some finer-grained access control
-            return document.data('xml')
+            if part is False:
+                # we're done :)
+                return document.data('xml')
+            else:
+                xdoc = parser.WLDocument.from_string(document.data('xml'))
+                ptext = xdoc.part_as_text(part)
+
+                if ptext is None:
+                    return response.EntityNotFound().django_response({
+                      'reason': 'no-part-in-document'                     
+                    })
+
+                return ptext
+        except librarian.ParseError:
+            return response.EntityNotFound().django_response({
+                'reason': 'invalid-document-state',
+                'exception': type(e), 'message': e.message
+            })
         except (EntryNotFound, RevisionNotFound), e:
             return response.EntityNotFound().django_response({
-                'exception': type(e), 'message': e.message})
+                'reason': 'not-found',
+                'exception': type(e), 'message': e.message
+            })   
 
     @hglibrary
     def create(self, request, docid, lib):
         try:
-            data = request.POST['contents']
             revision = request.POST['revision']
 
-            if request.POST.has_key('message'):
-                msg = u"$USER$ " + request.POST['message']
-            else:
-                msg = u"$AUTO$ XML content update."
-
             current = lib.document(docid, request.user.username)
             orig = lib.document_for_rev(revision)
 
@@ -330,6 +352,33 @@ class DocumentTextHandler(BaseHandler):
                         "provided_revision": orig.revision,
                         "latest_revision": current.revision })
 
+            if request.POST.has_key('message'):
+                msg = u"$USER$ " + request.POST['message']
+            else:
+                msg = u"$AUTO$ XML content update."
+
+            if request.POST.has_key('contents'):
+                data = request.POST['contents']
+            else:
+                if not request.POST.has_key('chunks'):
+                    # bad request
+                    return response.BadRequest().django_response({'reason': 'invalid-arguments',
+                        'message': 'No contents nor chunks specified.'})
+
+                    # TODO: validate
+                parts = json.loads(request.POST['chunks'])                    
+                xdoc = parser.WLDocument.from_string(current.data('xml'))
+                   
+                errors = xdoc.merge_chunks(parts)
+
+                if len(errors):
+                    return response.EntityConflict().django_response({
+                            "reason": "invalid-chunks",
+                            "message": "Unable to merge following parts into the document: %s " % ",".join(errors)
+                    })
+
+                data = xdoc.serialize()
+
             # try to find any Xinclude tags
             includes = [m.groupdict()['link'] for m in (re.finditer(\
                 XINCLUDE_REGEXP, data, flags=re.UNICODE) or []) ]
@@ -498,21 +547,25 @@ class MergeHandler(BaseHandler):
                     "provided": target_rev,
                     "latest": udoc.revision })
 
-        if not request.user.has_perm('explorer.book.can_share'):
+        if not request.user.has_perm('explorer.document.can_share'):
             # User is not permitted to make a merge, right away
             # So we instead create a pull request in the database
-            prq = PullRequest(
-                comitter=request.user,
-                document=docid,
-                source_revision = str(udoc.revision),
-                status="N",
-                comment = form.cleaned_data['message'] or '$AUTO$ Document shared.'
-            )
-
-            prq.save()
-            return response.RequestAccepted().django_response(\
-                ticket_status=prq.status, \
-                ticket_uri=reverse("pullrequest_view", args=[prq.id]) )
+            try:
+                prq, created = PullRequest.get_or_create(                                        
+                    source_revision = str(udoc.revision),
+                    defaults = {
+                        'comitter': request.user,
+                        'document': docid,
+                        'status': "N",
+                        'comment': form.cleaned_data['message'] or '$AUTO$ Document shared.',
+                    }
+                )
+
+                return response.RequestAccepted().django_response(\
+                    ticket_status=prq.status, \
+                    ticket_uri=reverse("pullrequest_view", args=[prq.id]) )
+            except IntegrityError, e:
+                return response.InternalError().django_response()
 
         if form.cleaned_data['type'] == 'update':
             # update is always performed from the file branch
diff --git a/apps/api/urls.py b/apps/api/urls.py
index 0ae14203..deac319a 100644
--- a/apps/api/urls.py
+++ b/apps/api/urls.py
@@ -16,6 +16,9 @@ urlpatterns = patterns('',
 #    url(r'^hello$', hello_resource, {'emitter_format': 'json'}),
 #    url(r'^hello\.(?P<emitter_format>.+)$', hello_resource),
 
+    # HTML Renderer service
+    url(r'^render$', 'api.views.render'),
+    
     # Toolbar
     url(r'^toolbar/buttons$', toolbar_buttons, {'emitter_format': 'json'},
         name="toolbar_buttons"
@@ -52,12 +55,12 @@ urlpatterns = patterns('',
 
     # XML    
     url(urlpath(r'documents', DOC, 'text', format=False),
-        document_text_resource, {'emitter_format': 'rawxml'},
+        document_text_resource, {'emitter_format': 'raw'},
         name="doctext_view"),
 
     # HTML
     url(urlpath(r'documents', DOC, 'html', format=False),
-        document_html_resource, {'emitter_format': 'rawhtml'},
+        document_html_resource, {'emitter_format': 'raw'},
         name="dochtml_view"),
 
     # DC
diff --git a/apps/api/views.py b/apps/api/views.py
index 60f00ef0..b96fc470 100644
--- a/apps/api/views.py
+++ b/apps/api/views.py
@@ -1 +1,41 @@
 # Create your views here.
+
+from django.http import HttpResponse
+from librarian import html
+from lxml import etree
+from StringIO import StringIO
+import re
+
+LINE_SWAP_EXPR = re.compile(r'/\s', re.MULTILINE | re.UNICODE);
+
+def render(request):    
+    style_filename = html.get_stylesheet('partial')
+
+    data = request.POST['fragment']
+    path = request.POST['part']
+
+    base, me = path.rsplit('/', 1)
+    match = re.match(r'([^\[]+)\[(\d+)\]', me)
+    tag, pos = match.groups()
+
+    print "Redner:", path, base, tag, pos
+
+    style = etree.parse(style_filename)
+
+    data = LINE_SWAP_EXPR.sub(u'<br />\n', data)
+    doc = etree.parse( StringIO(data) )
+
+    opts = {
+        'with-paths': 'boolean(1)',
+        'base-path': "'%s'" % base,
+        'base-offset': pos,
+    }
+
+    print opts
+    
+    result = doc.xslt(style, **opts)
+
+    print result
+    
+    return HttpResponse(
+        etree.tostring(result, encoding=unicode, pretty_print=True) )
\ No newline at end of file
diff --git a/apps/explorer/models.py b/apps/explorer/models.py
index 7ab6b095..23acd7f1 100644
--- a/apps/explorer/models.py
+++ b/apps/explorer/models.py
@@ -55,12 +55,12 @@ class EditorPanel(models.Model):
     def __unicode__(self):
         return self.display_name
     
-class Book(models.Model):
+class Document(models.Model):
     class Meta:
         permissions = (            
             ("can_share", "Can share documents without pull requests."),
         )
-        abstract=True
+        
     pass
 
 class PullRequest(models.Model):    
diff --git a/apps/wysiwyg/tests.py b/apps/wysiwyg/tests.py
deleted file mode 100644
index 2247054b..00000000
--- a/apps/wysiwyg/tests.py
+++ /dev/null
@@ -1,23 +0,0 @@
-"""
-This file demonstrates two different styles of tests (one doctest and one
-unittest). These will both pass when you run "manage.py test".
-
-Replace these with more appropriate tests for your application.
-"""
-
-from django.test import TestCase
-
-class SimpleTest(TestCase):
-    def test_basic_addition(self):
-        """
-        Tests that 1 + 1 always equals 2.
-        """
-        self.failUnlessEqual(1 + 1, 2)
-
-__test__ = {"doctest": """
-Another way to test that 1 + 1 is equal to 2.
-
->>> 1 + 1 == 2
-True
-"""}
-
diff --git a/lib/wlrepo/__init__.py b/lib/wlrepo/__init__.py
index ab6f319f..430e59f9 100644
--- a/lib/wlrepo/__init__.py
+++ b/lib/wlrepo/__init__.py
@@ -51,7 +51,7 @@ class Document(object):
         Should be called on the user version of document. If not, it doesn nothing."""
        
     def data(self, entry):
-        """Returns the specified entry as a file-like object."""
+        """Returns the specified entry as a unicode data."""
         pass
 
     @property
diff --git a/lib/wlrepo/mercurial_backend/document.py b/lib/wlrepo/mercurial_backend/document.py
index a579fb75..e7145616 100644
--- a/lib/wlrepo/mercurial_backend/document.py
+++ b/lib/wlrepo/mercurial_backend/document.py
@@ -189,7 +189,7 @@ class MercurialDocument(wlrepo.Document):
                     return False
 
                 if changed:
-                    local = local.latest()
+                    local = self.latest()._revision
                     
                 success, changed = main.merge_with(local, user=user,\
                     message=message)
diff --git a/project/static/css/html.css b/project/static/css/html.css
index 4904a0d6..336a365e 100644
--- a/project/static/css/html.css
+++ b/project/static/css/html.css
@@ -3,7 +3,7 @@
     font-size: 16px;
     font: Georgia, "Times New Roman", serif;
     line-height: 1.5em;
-    padding: 3em;
+    padding: 3em;    
 }
 
 .htmlview div {
@@ -100,14 +100,14 @@
 /* = Numbering = */
 /* ============= */
 .htmlview .anchor {
-    position: absolute;
-    margin: -0.25em -0.5em;
-    left: 1em;
+    position: relative;
+    margin: 0em;
+    left: -2.2em;
     color: #777;
     font-size: 12px;
     width: 2em;
     text-align: center;
-    padding: 0.25em 0.5em;
+    padding: 0.25em 0.7em;
     line-height: 1.5em;
 }
 
@@ -119,45 +119,45 @@
 /* =================== */
 /* = Custom elements = */
 /* =================== */
-.htmlview span.author {
+.htmlview .autor_utwor {
     font-size: 0.5em;
     display: block;
     line-height: 1.5em;
     margin-bottom: 0.25em;
 }
 
-.htmlview span.collection {
+.htmlview .dzielo_nadrzedne {
     font-size: 0.375em;
     display: block;
     line-height: 1.5em;
     margin-bottom: -0.25em;
 }
 
-.htmlview span.subtitle {
+.htmlview .podtytul {
     font-size: 0.5em;
     display: block;
     line-height: 1.5em;
     margin-top: -0.25em;
 }
 
-.htmlview div.didaskalia {
+.htmlview .didaskalia {
     font-style: italic;
     margin: 0.5em 0 0 1.5em;
 }
 
-.htmlview div.kwestia {
+.htmlview .kwestia {
     margin: 0.5em 0 0;
 }
 
-.htmlview div.stanza {
+.htmlview .strofa {
     margin: 1.5em 0 0;
 }
 
-.htmlview div.kwestia div.stanza {
+.htmlview .kwestia .strofa {
     margin: 0;
 }
 
-.htmlview p.paragraph {
+.htmlview .akap, .htmlview .akap_cd, .htmlview .akap_dialog {
     text-align: justify;
     margin: 1.5em 0 0;
 }
@@ -178,48 +178,50 @@
     padding-bottom: 1.5em;
 }
 
-.htmlview div.note p, .htmlview div.dedication p,
-.htmlview div.note p.paragraph, .htmlview div.dedication p.paragraph {
+.htmlview div.nota p,
+.htmlview div.dedykacja p {
     text-align: right;
     font-style: italic;
 }
 
-.htmlview hr.spacer {
+.htmlview br.sekcja_swiatlo {
     height: 3em;
-    visibility: hidden;
+    /* visibility: hidden; */
 }
 
-.htmlview hr.spacer-line {
+.htmlview hr.separator_linia {
     margin: 1.5em 0;
     border: none;
     border-bottom: 0.1em solid #000;
 }
 
-.htmlview p.spacer-asterisk {
+.htmlview p.sekcja_asterysk {
     padding: 0;
     margin: 1.5em 0;
     text-align: center;
 }
 
-.htmlview div.person-list ol {
+.htmlview div.lista_osob ol {
     list-style: none;
     padding: 0 0 0 1.5em;
 }
 
-.htmlview p.place-and-time {
+.htmlview p.miejsce_czas {
     font-style: italic;
 }
 
-.htmlview em.math, .htmlview em.foreign-word,
-.htmlview em.book-title, .htmlview em.didaskalia {
+.htmlview .mat,
+.htmlview .slowo_obce,
+.htmlview .tytul_dziela,
+.htmlview .didaskalia {
     font-style: italic;
 }
 
-.htmlview em.author-emphasis {
+.htmlview .wyroznienie {
     letter-spacing: 0.1em;
 }
 
-.htmlview em.person {
+.htmlview .osoba {
     font-style: normal;
     font-variant: small-caps;
 }
diff --git a/project/static/css/master.css b/project/static/css/master.css
index 7c794e5d..ae51e475 100644
--- a/project/static/css/master.css
+++ b/project/static/css/master.css
@@ -343,7 +343,7 @@ body#base button {
     border-width: 1px;
     padding: 0px 0.5em;    
     font-family: Sans-Serif;
-    color: #000;
+    /* color: #000; */
     margin: 2px 4px;
 }
 
@@ -351,6 +351,49 @@ body#base button:hover {
     background-color: #EEE;
 }
 
+/* HTML editor interactive elements */
+
+.html-editarea {
+    border: 2px solid black;
+    background-color: gray;
+    padding: 1px;
+
+    z-index: 2000;
+}
+
+.html-editarea textarea
+{   
+   
+    border: 0px;
+    margin: 0px;    
+    padding: 0px;
+    
+    width: 100%;
+    height: 100%;
+    
+    z-index: 0;
+    font-size: 10pt;
+    background-color: ivory;
+}
+
+.html-editarea p.html-editarea-toolbar {
+    position: absolute;
+    background: gray;
+
+    bottom: -26px;
+    height: 24px;
+    
+    left: 0px;
+    right: 0px;    
+        
+    border: 2px solid black;
+
+    margin: 0px;
+    padding: 0px;
+
+    z-index: 100;
+}
+
 /* ================= */
 /* = Message boxes = */
 /* ================= */
@@ -369,3 +412,4 @@ body#base button:hover {
 .critical {
     background-color: red;
 }
+
diff --git a/project/static/js/editor.js b/project/static/js/editor.js
index 6918f9e5..f52950ff 100644
--- a/project/static/js/editor.js
+++ b/project/static/js/editor.js
@@ -574,5 +574,5 @@ $(function() {
 
     // do the layout
     editor.loadConfig();
-    editor.setupUI();
+   editor.setupUI();
 });
diff --git a/project/static/js/editor.ui.js b/project/static/js/editor.ui.js
deleted file mode 100755
index c8d47f66..00000000
--- a/project/static/js/editor.ui.js
+++ /dev/null
@@ -1,359 +0,0 @@
-/*
- * UI related Editor methods
- */
-Editor.prototype.setupUI = function() {
-//     // set up the UI visually and attach callbacks
-    var self = this;
-// 
-//     var resize_start = function(event, mydata) {
-//         $(document).bind('mousemove', mydata, resize_changed).
-//         bind('mouseup', mydata, resize_stop);
-// 
-//         $('.panel-overlay', mydata.root).css('display', 'block');
-//         return false;
-//     }
-//     var resize_changed =  function(event) {
-//         var old_width = parseInt(event.data.overlay.css('width'));
-//         var delta = event.pageX + event.data.hotspot_x - old_width;
-// 
-//         if(old_width + delta < 12) delta = 12 - old_width;
-//         if(old_width + delta > $(window).width()) 
-//             delta = $(window).width() - old_width;
-//         
-//         event.data.overlay.css({
-//             'width': old_width + delta
-//         });
-// 
-//         if(event.data.overlay.next) {
-//             var left = parseInt(event.data.overlay.next.css('left'));
-//             event.data.overlay.next.css('left', left+delta);
-//         }
-// 
-//         return false;
-//     };
-// 
-//     var resize_stop = function(event) {
-//         $(document).unbind('mousemove', resize_changed).unbind('mouseup', resize_stop);
-//         // $('.panel-content', event.data.root).css('display', 'block');
-//         var overlays = $('.panel-content-overlay', event.data.root);
-//         $('.panel-content-overlay', event.data.root).each(function(i) {
-//             if( $(this).data('panel').hasClass('last-panel') )
-//                 $(this).data('panel').css({
-//                     'left': $(this).css('left'),
-//                     'right': $(this).css('right')
-//                 });
-//             else
-//                 $(this).data('panel').css({
-//                     'left': $(this).css('left'),
-//                     'width': $(this).css('width')
-//                 });
-//         });
-//         $('.panel-overlay', event.data.root).css('display', 'none');
-//         $(event.data.root).trigger('stopResize');
-//     };
-// 
-//     /*
-//      * Prepare panels (overlays & stuff)
-//      */
-//     /* create an overlay */
-//     var panel_root = self.rootDiv;
-//     var overlay_root = $("<div class='panel-overlay'></div>");
-//     panel_root.append(overlay_root);
-// 
-//     var prev = null;
-// 
-//     $('*.panel-wrap', panel_root).each( function()
-//     {
-//         var panel = $(this);
-//         var handle = $('.panel-slider', panel);
-//         var overlay = $("<div class='panel-content-overlay panel-wrap'>&nbsp;</div>");
-//         overlay_root.append(overlay);
-//         overlay.data('panel', panel);
-//         overlay.data('next', null);
-// 
-//         if (prev) prev.next = overlay;
-// 
-//         if( panel.hasClass('last-panel') )
-//         {
-//             overlay.css({
-//                 'left': panel.css('left'),
-//                 'right': panel.css('right')
-//             });
-//         }
-//         else {
-//             overlay.css({
-//                 'left': panel.css('left'),
-//                 'width': panel.css('width')
-//             });
-//             // $.log('Has handle: ' + panel.attr('id'));
-//             overlay.append(handle.clone());
-//             /* attach the trigger */
-//             handle.mousedown(function(event) {
-//                 var touch_data = {
-//                     root: panel_root,
-//                     overlay: overlay,
-//                     hotspot_x: event.pageX - handle.position().left
-//                 };
-// 
-//                 $(this).trigger('hpanel:panel-resize-start', touch_data);
-//                 return false;
-//             });
-//             $('.panel-content', panel).css('right',
-//                 (handle.outerWidth() || 10) + 'px');
-//             $('.panel-content-overlay', panel).css('right',
-//                 (handle.outerWidth() || 10) + 'px');
-//         };
-// 
-//         prev = overlay;
-//     });
-// 
-//     panel_root.bind('hpanel:panel-resize-start', resize_start);
-//     self.rootDiv.bind('stopResize', function() {
-//         self.savePanelOptions();      
-//     });
-//     
-    /*
-     * Connect panel actions
-     */
-    $('#panels > *.panel-wrap').each(function() {
-        var panelWrap = $(this);
-        // $.log('wrap: ', panelWrap);
-        var panel = new Panel(panelWrap);
-        panelWrap.data('ctrl', panel); // attach controllers to wraps
-        panel.load($('.panel-toolbar select', panelWrap).val());
-
-        $('.panel-toolbar select', panelWrap).change(function() {
-            var url = $(this).val();
-            panelWrap.data('ctrl').load(url);
-            self.savePanelOptions();
-        });
-
-        $('.panel-toolbar button.refresh-button', panelWrap).click(
-            function() {
-                panel.refresh();
-            } );
-
-        self.rootDiv.bind('stopResize', function() {
-            panel.callHook('toolbarResized');
-        });
-    });
-
-    $(document).bind('panel:contentChanged', function() {
-        self.onContentChanged.apply(self, arguments)
-    });  
-
-    /*
-     * Connect various buttons
-     */
-
-    $('#toolbar-button-quick-save').click( function (event, data) {
-        self.saveToBranch();
-    } );
-
-    $('#toolbar-button-save').click( function (event, data) {
-        $('#commit-dialog').jqmShow( {callback: $.fbind(self, self.saveToBranch)} );
-    } );
-
-    $('#toolbar-button-update').click( function (event, data) {
-        if (self.updateUserBranch()) {
-            // commit/update can be called only after proper, save
-            // this means all panels are clean, and will get refreshed
-            // do this only, when there are any changes to local branch
-            self.refreshPanels();
-        }
-    } );
-
-    /* COMMIT DIALOG */
-    $('#commit-dialog').
-    jqm({
-        modal: true,
-        onShow: $.fbind(self, self.loadRelatedIssues)        
-    });
-
-    $('#toolbar-button-commit').click( function (event, data) {
-        $('#commit-dialog').jqmShow( {callback: $.fbind(self, self.sendMergeRequest)} );
-    } );
-    
-    /* STATIC BINDS */
-    $('#commit-dialog-cancel-button').click(function() {
-        $('#commit-dialog-error-empty-message').hide();
-        $('#commit-dialog').jqmHide();
-    });   
-    
-
-    /* SPLIT DIALOG */
-    $('#split-dialog').jqm({
-        modal: true,
-        onShow: $.fbind(self, self.loadSplitDialog)
-    }).
-    jqmAddClose('button.dialog-close-button');
-}
-
-Editor.prototype.loadRelatedIssues = function(hash)
-{
-    var self = this;
-    var c = $('#commit-dialog-related-issues');
-
-    $('#commit-dialog-save-button').click( function (event, data)
-    {
-        if( $('#commit-dialog-message').val().match(/^\s*$/)) {
-            $('#commit-dialog-error-empty-message').fadeIn();
-        }
-        else {
-            $('#commit-dialog-error-empty-message').hide();
-            $('#commit-dialog').jqmHide();
-
-            var message = $('#commit-dialog-message').val();
-            $('#commit-dialog-related-issues input:checked').
-                each(function() { message += ' refs #' + $(this).val(); });
-            $.log("COMMIT APROVED", hash.t);
-            hash.t.callback(message);
-        }
-
-        return false;
-    });
-
-    $("div.loading-box", c).show();
-    $("div.fatal-error-box", c).hide();
-    $("div.container-box", c).hide();
-    
-    $.getJSON( c.attr('ui:ajax-src') + '?callback=?',
-    function(data, status)
-    {
-        var fmt = '';
-        $(data).each( function() {
-            fmt += '<label><input type="checkbox" checked="checked"'
-            fmt += ' value="' + this.id + '" />' + this.subject +'</label>\n'
-        });
-        $("div.container-box", c).html(fmt);
-        $("div.loading-box", c).hide();
-        $("div.container-box", c).show();        
-    });   
-    
-    hash.w.show();
-}
-
-Editor.prototype.loadSplitDialog = function(hash)
-{
-    var self = this;    
-    
-    $("div.loading-box", hash.w).show();
-    $("div.fatal-error-box", hash.w).hide();
-    $('div.container-box', hash.w).hide();
-    hash.w.show();
-
-    function onFailure(rq, tstat, err) {
-        $('div.container-box', hash.w).html('');
-        $("div.loading-box", hash.w).hide();
-        $("div.fatal-error-box", hash.w).show();
-        hash.t.failure();
-    };
-
-    function onSuccess(data, status) {
-        // put the form into the window
-        $('div.container-box', hash.w).html(data);
-        $("div.loading-box", hash.w).hide();
-        $('form input[name=splitform-splittext]', hash.w).val(hash.t.selection);
-        $('form input[name=splitform-fulltext]', hash.w).val(hash.t.fulltext);
-        $('div.container-box', hash.w).show();
-
-        // connect buttons
-        $('#split-dialog-button-accept').click(function() {
-            self.postSplitRequest(onSuccess, onFailure);
-            return false;
-        });
-
-        $('#split-dialog-button-close').click(function() {
-            hash.w.jqmHide();
-            $('div.container-box', hash.w).html('');
-            hash.t.failure();
-        });
-
-        $('#split-dialog-button-dismiss').click(function() {
-            hash.w.jqmHide();
-            $('div.container-box', hash.w).html('');
-            hash.t.success();
-        });
-
-        /* if($('#id_splitform-autoxml').is(':checked'))
-            $('#split-form-dc-subform').show();
-        else
-            $('#split-form-dc-subform').hide();
-
-        $('#id_splitform-autoxml').change(function() {            
-            if( $(this).is(':checked') )
-                $('#split-form-dc-subform').show();
-            else
-                $('#split-form-dc-subform').hide();
-        }); */
-    };   
-
-    $.ajax({
-        url: 'split',
-        dataType: 'html',
-        success: onSuccess,
-        error: onFailure,
-        type: 'GET',
-        data: {}
-    });
-}
-
-/* Refreshing routine */
-Editor.prototype.refreshPanels = function() {
-    var self = this;
-
-    self.allPanels().each(function() {
-        var panel = $(this).data('ctrl');
-        $.log('Refreshing: ', this, panel);
-        if ( panel.changed() )
-            panel.unmarkChanged();
-        else
-            panel.refresh();
-    });
-
-    $('button.provides-save').attr('disabled', 'disabled');
-    $('button.requires-save').removeAttr('disabled');
-};
-
-/*
- * Pop-up messages
- */
-Editor.prototype.showPopup = function(name, text, timeout)
-{
-    timeout = timeout || 4000;
-    var self = this;
-    self.popupQueue.push( [name, text, timeout] )
-
-    if( self.popupQueue.length > 1)
-        return;
-
-    var box = $('#message-box > #' + name);
-    $('*.data', box).html(text || '');
-    box.fadeIn(100);
-
-    if(timeout > 0)
-        setTimeout( $.fbind(self, self.advancePopupQueue), timeout);
-};
-
-Editor.prototype.advancePopupQueue = function() {
-    var self = this;
-    var elem = this.popupQueue.shift();
-    if(elem) {
-        var box = $('#message-box > #' + elem[0]);
-
-        box.fadeOut(100, function()
-        {
-            $('*.data', box).html('');
-
-            if( self.popupQueue.length > 0) {
-                var ibox = $('#message-box > #' + self.popupQueue[0][0]);
-                $('*.data', ibox).html(self.popupQueue[0][1] || '');
-                ibox.fadeIn(100);
-                if(self.popupQueue[0][2] > 0)
-                    setTimeout( $.fbind(self, self.advancePopupQueue), self.popupQueue[0][2]);
-            }
-        });
-    }
-};
-
-
diff --git a/project/static/js/models.js b/project/static/js/models.js
index 3cd98f67..e2ee8b15 100644
--- a/project/static/js/models.js
+++ b/project/static/js/models.js
@@ -1,31 +1,31 @@
 /*globals Editor fileId SplitView PanelContainerView EditorView FlashView messageCenter*/
 Editor.Model = Editor.Object.extend({
-  synced: false,
-  data: null
+    synced: false,
+    data: null
 });
 
 
 Editor.ToolbarButtonsModel = Editor.Model.extend({
-  className: 'Editor.ToolbarButtonsModel',  
-  buttons: {},
+    className: 'Editor.ToolbarButtonsModel',
+    buttons: {},
   
-  init: function() {
-    this._super();
-  },
+    init: function() {
+        this._super();
+    },
   
-  load: function() {
-    if (!this.get('buttons').length) {
-      $.ajax({
-        url: toolbarUrl,
-        dataType: 'json',
-        success: this.loadSucceeded.bind(this)
-      });
-    }
-  },
+    load: function() {
+        if (!this.get('buttons').length) {
+            $.ajax({
+                url: toolbarUrl,
+                dataType: 'json',
+                success: this.loadSucceeded.bind(this)
+            });
+        }
+    },
   
-  loadSucceeded: function(data) {
-    this.set('buttons', data);
-  }
+    loadSucceeded: function(data) {
+        this.set('buttons', data);
+    }
 });
 
 
@@ -38,382 +38,505 @@ Editor.ToolbarButtonsModel = Editor.Model.extend({
 //                            -> dirty -> updating -> updated -> synced
 //
 Editor.XMLModel = Editor.Model.extend({
-  _className: 'Editor.XMLModel',
-  serverURL: null,
-  data: '',
-  state: 'empty',
+    _className: 'Editor.XMLModel',
+    serverURL: null,
+    data: '',
+    state: 'empty',
   
-  init: function(serverURL, revision) {
-    this._super();
-    this.set('state', 'empty');
-    this.set('revision', revision);
-    this.serverURL = serverURL;
-    this.toolbarButtonsModel = new Editor.ToolbarButtonsModel();
-    this.addObserver(this, 'data', this.dataChanged.bind(this));
-  },
+    init: function(serverURL, revision) {
+        this._super();
+        this.set('state', 'empty');
+        this.set('revision', revision);
+        this.serverURL = serverURL;
+        this.toolbarButtonsModel = new Editor.ToolbarButtonsModel();
+        this.addObserver(this, 'data', this.dataChanged.bind(this));
+    },
   
-  load: function(force) {
-    if (force || this.get('state') == 'empty') {
-      this.set('state', 'loading');
-      messageCenter.addMessage('info', 'Wczytuję XML...');
-      $.ajax({
-        url: this.serverURL,
-        dataType: 'text',
-        data: {revision: this.get('revision')},
-        success: this.loadingSucceeded.bind(this),
-        error: this.loadingFailed.bind(this)
-      });
-      return true;
-    }
-    return false;
-  },
+    load: function(force) {
+        if (force || this.get('state') == 'empty') {
+            this.set('state', 'loading');
+            messageCenter.addMessage('info', 'Wczytuję XML...');
+            $.ajax({
+                url: this.serverURL,
+                dataType: 'text',
+                data: {
+                    revision: this.get('revision')
+                    },
+                success: this.loadingSucceeded.bind(this),
+                error: this.loadingFailed.bind(this)
+            });
+            return true;
+        }
+        return false;
+    },
   
-  loadingSucceeded: function(data) {
-    if (this.get('state') != 'loading') {
-      alert('erroneous state:', this.get('state'));
-    }
-    this.set('data', data);
-    this.set('state', 'synced');
-    messageCenter.addMessage('success', 'Wczytałem XML :-)');
-  },
+    loadingSucceeded: function(data) {
+        if (this.get('state') != 'loading') {
+            alert('erroneous state:', this.get('state'));
+        }
+        this.set('data', data);
+        this.set('state', 'synced');
+        messageCenter.addMessage('success', 'Wczytałem XML :-)');
+    },
   
-  loadingFailed: function() {
-    if (this.get('state') != 'loading') {
-      alert('erroneous state:', this.get('state'));
-    }
-    this.set('error', 'Nie udało się załadować panelu');
-    this.set('state', 'error');    
-    messageCenter.addMessage('error', 'Nie udało mi się wczytać XML. Spróbuj ponownie :-(');
-  },
+    loadingFailed: function() {
+        if (this.get('state') != 'loading') {
+            alert('erroneous state:', this.get('state'));
+        }
+        this.set('error', 'Nie udało się załadować panelu');
+        this.set('state', 'error');
+        messageCenter.addMessage('error', 'Nie udało mi się wczytać XML. Spróbuj ponownie :-(');
+    },
   
-  update: function(message) {
-    if (this.get('state') == 'dirty') {
-      this.set('state', 'updating');
-      messageCenter.addMessage('info', 'Zapisuję XML...');
+    update: function(message) {
+        if (this.get('state') == 'dirty') {
+            this.set('state', 'updating');
+            messageCenter.addMessage('info', 'Zapisuję XML...');
       
-      var payload = {
-        contents: this.get('data'),
-        revision: this.get('revision')
-      };
-      if (message) {
-        payload.message = message;
-      }
+            var payload = {
+                contents: this.get('data'),
+                revision: this.get('revision')
+            };
+            if (message) {
+                payload.message = message;
+            }
       
-      $.ajax({
-        url: this.serverURL,
-        type: 'post',
-        dataType: 'json',
-        data: payload,
-        success: this.updatingSucceeded.bind(this),
-        error: this.updatingFailed.bind(this)
-      });
-      return true;
-    }
-    return false;
-  },
+            $.ajax({
+                url: this.serverURL,
+                type: 'post',
+                dataType: 'json',
+                data: payload,
+                success: this.updatingSucceeded.bind(this),
+                error: this.updatingFailed.bind(this)
+            });
+            return true;
+        }
+        return false;
+    },
   
-  updatingSucceeded: function(data) {
-    if (this.get('state') != 'updating') {
-      alert('erroneous state:', this.get('state'));
-    }
-    this.set('revision', data.revision);
-    this.set('state', 'updated');
-    messageCenter.addMessage('success', 'Zapisałem XML :-)');
-  },
+    updatingSucceeded: function(data) {
+        if (this.get('state') != 'updating') {
+            alert('erroneous state:', this.get('state'));
+        }
+        this.set('revision', data.revision);
+        this.set('state', 'updated');
+        messageCenter.addMessage('success', 'Zapisałem XML :-)');
+    },
   
-  updatingFailed: function() {
-    if (this.get('state') != 'updating') {
-      alert('erroneous state:', this.get('state'));
-    }
-    messageCenter.addMessage('error', 'Nie udało mi się zapisać XML. Spróbuj ponownie :-(');
-    this.set('state', 'dirty');
-  },
+    updatingFailed: function() {
+        if (this.get('state') != 'updating') {
+            alert('erroneous state:', this.get('state'));
+        }
+        messageCenter.addMessage('error', 'Nie udało mi się zapisać XML. Spróbuj ponownie :-(');
+        this.set('state', 'dirty');
+    },
   
-  // For debbuging
-  set: function(property, value) {
-    if (property == 'state') {
-      console.log(this.description(), ':', property, '=', value);
-    }
-    return this._super(property, value);
-  },
+    // For debbuging
+    set: function(property, value) {
+        if (property == 'state') {
+            console.log(this.description(), ':', property, '=', value);
+        }
+        return this._super(property, value);
+    },
   
-  dataChanged: function(property, value) {
-    if (this.get('state') == 'synced') {
-      this.set('state', 'dirty');
-    }
-  },
+    dataChanged: function(property, value) {
+        if (this.get('state') == 'synced') {
+            this.set('state', 'dirty');
+        }
+    },
   
-  dispose: function() {
-    this.removeObserver(this);
-    this._super();
-  }
+    dispose: function() {
+        this.removeObserver(this);
+        this._super();
+    }
 });
 
 
 Editor.HTMLModel = Editor.Model.extend({
-  _className: 'Editor.HTMLModel',
-  serverURL: null,
-  data: '',
-  state: 'empty',
+    _className: 'Editor.HTMLModel',
+    dataURL: null,
+    htmlURL: null,
+    renderURL: null,
+    displaData: '',
+    xmlParts: {},
+    state: 'empty',
   
-  init: function(serverURL, revision) {
-    this._super();
-    this.set('state', 'empty');
-    this.set('revision', revision);
-    this.serverURL = serverURL;
-  },
+    init: function(htmlURL, revision, dataURL) {
+        this._super();
+        this.set('state', 'empty');
+        this.set('revision', revision);
+        this.htmlURL = htmlURL;
+        this.dataURL = dataURL;
+        this.renderURL = "http://localhost:8000/api/render";
+        this.xmlParts = {};
+    },
   
-  load: function(force) {
-    if (force || this.get('state') == 'empty') {
-      this.set('state', 'loading');
-      messageCenter.addMessage('info', 'Wczytuję HTML...');
-      $.ajax({
-        url: this.serverURL,
-        dataType: 'text',
-        data: {revision: this.get('revision')},
-        success: this.loadingSucceeded.bind(this),
-        error: this.loadingFailed.bind(this)
-      });
-    }
-  },
+    load: function(force) {
+        if (force || this.get('state') == 'empty') {
+            this.set('state', 'loading');
+
+            // load the transformed data
+            messageCenter.addMessage('info', 'Wczytuję HTML...');
+
+            $.ajax({
+                url: this.htmlURL,
+                dataType: 'text',
+                data: {
+                    revision: this.get('revision')
+                    },
+                success: this.loadingSucceeded.bind(this),
+                error: this.loadingFailed.bind(this)
+            });
+        }
+    },
   
-  loadingSucceeded: function(data) {
-    if (this.get('state') != 'loading') {
-      alert('erroneous state:', this.get('state'));
-    }
-    this.set('data', data);
-    this.set('state', 'synced');
-    messageCenter.addMessage('success', 'Wczytałem HTML :-)');
-  },
+    loadingSucceeded: function(data) {
+        if (this.get('state') != 'loading') {
+            alert('erroneous state:', this.get('state'));
+        }
+        this.set('data', data);
+        this.set('state', 'synced');
+        messageCenter.addMessage('success', 'Wczytałem HTML :-)');
+    },
   
-  loadingFailed: function() {
-    if (this.get('state') != 'loading') {
-      alert('erroneous state:', this.get('state'));
-    }
-    this.set('error', 'Nie udało się załadować panelu');
-    this.set('state', 'error');    
-    messageCenter.addMessage('error', 'Nie udało mi się wczytać HTML. Spróbuj ponownie :-(');
-  },
-
-  // For debbuging
-  set: function(property, value) {
-    if (property == 'state') {
-      console.log(this.description(), ':', property, '=', value);
+    loadingFailed: function() {
+        if (this.get('state') != 'loading') {
+            alert('erroneous state:', this.get('state'));
+        }
+        this.set('error', 'Nie udało się załadować panelu');
+        this.set('state', 'error');
+        messageCenter.addMessage('error', 'Nie udało mi się wczytać HTML. Spróbuj ponownie :-(');
+    },
+
+    getXMLPart: function(elem, callback)
+    {
+        var path = elem.attr('wl2o:path');
+        if(!this.xmlParts[path])
+            this.loadXMLPart(elem, callback);
+        else
+            callback(path, this.xmlParts[path]);
+    },
+
+    loadXMLPart: function(elem, callback)
+    {
+        var path = elem.attr('wl2o:path');
+        var self = this;
+
+        $.ajax({
+            url: this.dataURL,
+            dataType: 'text; charset=utf-8',
+            data: {
+                revision: this.get('revision'),
+                part: path
+            },
+            success: function(data) {
+                self.xmlParts[path] = data;
+                callback(path, data);
+            },
+            // TODO: error handling
+            error: function(data) {
+                console.log('Failed to load fragment');
+                callback(undefined, undefined);
+            }
+        });
+    },
+
+    putXMLPart: function(elem, data) {
+        var self = this;
+      
+        var path = elem.attr('wl2o:path');
+        this.xmlParts[path] = data;
+
+        this.set('state', 'unsynced');
+
+        /* re-render the changed fragment */
+        $.ajax({
+            url: this.renderURL,
+            type: "POST",
+            dataType: 'text; charset=utf-8',
+            data: {
+                fragment: data,
+                part: path
+            },
+            success: function(htmldata) {
+                elem.replaceWith(htmldata);
+                self.set('state', 'dirty');
+            }
+        });
+    },
+
+    update: function(message) {
+        if (this.get('state') == 'dirty') {
+            this.set('state', 'updating');
+
+            var payload = {
+                chunks: $.toJSON(this.xmlParts),
+                revision: this.get('revision')
+            };
+
+            if (message) {
+                payload.message = message;
+            }
+
+            console.log(payload)
+
+            $.ajax({
+                url: this.dataURL,
+                type: 'post',
+                dataType: 'json',
+                data: payload,
+                success: this.updatingSucceeded.bind(this),
+                error: this.updatingFailed.bind(this)
+            });
+            return true;
+        }
+        return false;
+      
+    },
+
+    updatingSucceeded: function(data) {
+        if (this.get('state') != 'updating') {
+            alert('erroneous state:', this.get('state'));
+        }
+
+        // flush the cache
+        this.xmlParts = {};
+    
+        this.set('revision', data.revision);
+        this.set('state', 'updated');
+    },
+
+    updatingFailed: function() {
+        if (this.get('state') != 'updating') {
+            alert('erroneous state:', this.get('state'));
+        }
+        messageCenter.addMessage('error', 'Uaktualnienie nie powiodło się', 'Uaktualnienie nie powiodło się');
+        this.set('state', 'dirty');
+    },
+
+    // For debbuging
+    set: function(property, value) {
+        if (property == 'state') {
+            console.log(this.description(), ':', property, '=', value);
+        }
+        return this._super(property, value);
     }
-    return this._super(property, value);
-  }
 });
 
 
 Editor.ImageGalleryModel = Editor.Model.extend({
-  _className: 'Editor.ImageGalleryModel',
-  serverURL: null,
-  data: [],
-  state: 'empty',
-
-  init: function(serverURL) {
-    this._super();
-    this.set('state', 'empty');
-    this.serverURL = serverURL;
-    // olewać data    
-    this.pages = [];
-  },
-
-  load: function(force) {
-    if (force || this.get('state') == 'empty') {
-      this.set('state', 'loading');
-      $.ajax({
-        url: this.serverURL,
-        dataType: 'json',
-        success: this.loadingSucceeded.bind(this)
-      });
-    }
-  },  
+    _className: 'Editor.ImageGalleryModel',
+    serverURL: null,
+    data: [],
+    state: 'empty',
 
-  loadingSucceeded: function(data) {
-    if (this.get('state') != 'loading') {
-      alert('erroneous state:', this.get('state'));
-    }
+    init: function(serverURL) {
+        this._super();
+        this.set('state', 'empty');
+        this.serverURL = serverURL;
+        // olewać data
+        this.pages = [];
+    },
 
-    console.log('galleries:', data);
+    load: function(force) {
+        if (force || this.get('state') == 'empty') {
+            this.set('state', 'loading');
+            $.ajax({
+                url: this.serverURL,
+                dataType: 'json',
+                success: this.loadingSucceeded.bind(this)
+            });
+        }
+    },
 
-    if (data.length === 0) {
-        this.set('data', []);
-    } else {
-        console.log('dupa');
-        this.set('data', data[0].pages);
-    }  
+    loadingSucceeded: function(data) {
+        if (this.get('state') != 'loading') {
+            alert('erroneous state:', this.get('state'));
+        }
 
-    this.set('state', 'synced');
-  },
+        console.log('galleries:', data);
 
-  set: function(property, value) {
-    if (property == 'state') {
-      console.log(this.description(), ':', property, '=', value);
+        if (data.length === 0) {
+            this.set('data', []);
+        } else {
+            console.log('dupa');
+            this.set('data', data[0].pages);
+        }
+
+        this.set('state', 'synced');
+    },
+
+    set: function(property, value) {
+        if (property == 'state') {
+            console.log(this.description(), ':', property, '=', value);
+        }
+        return this._super(property, value);
     }
-    return this._super(property, value);
-  }
 });
 
 
 Editor.DocumentModel = Editor.Model.extend({
-  _className: 'Editor.DocumentModel',
-  data: null, // name, text_url, user_revision, latest_shared_rev, parts_url, dc_url, size, merge_url
-  contentModels: {},
-  state: 'empty',
+    _className: 'Editor.DocumentModel',
+    data: null, // name, text_url, user_revision, latest_shared_rev, parts_url, dc_url, size, merge_url
+    contentModels: {},
+    state: 'empty',
   
-  init: function() {
-    this._super();
-    this.set('state', 'empty');
-    this.load();
-  },
+    init: function() {
+        this._super();
+        this.set('state', 'empty');
+        this.load();
+    },
   
-  load: function() {
-    if (this.get('state') == 'empty') {
-      this.set('state', 'loading');
-      messageCenter.addMessage('info', 'Ładuję dane dokumentu...');
-      $.ajax({
-        cache: false,
-        url: documentsUrl + fileId,
-        dataType: 'json',
-        success: this.successfulLoad.bind(this)
-      });
-    }
-  },
-  
-  successfulLoad: function(data) {
-    this.set('data', data);
-    this.set('state', 'synced');
-    this.contentModels = {
-      'xml': new Editor.XMLModel(data.text_url, data.user_revision),
-      'html': new Editor.HTMLModel(data.html_url, data.user_revision),
-      'gallery': new Editor.ImageGalleryModel(data.gallery_url)
-    };
-    for (var key in this.contentModels) {
-      this.contentModels[key].addObserver(this, 'state', this.contentModelStateChanged.bind(this));
-    }
-    messageCenter.addMessage('success', 'Dane dokumentu zostały załadowane :-)');
-  },
-  
-  contentModelStateChanged: function(property, value, contentModel) {
-    if (value == 'dirty') {
-      this.set('state', 'dirty');
-      for (var key in this.contentModels) {
-        if (this.contentModels[key].guid() != contentModel.guid()) {
-          this.contentModels[key].set('state', 'unsynced');
+    load: function() {
+        if (this.get('state') == 'empty') {
+            this.set('state', 'loading');
+            messageCenter.addMessage('info', 'Ładuję dane dokumentu...');
+            $.ajax({
+                cache: false,
+                url: documentsUrl + fileId,
+                dataType: 'json',
+                success: this.successfulLoad.bind(this)
+            });
         }
-      }
-    } else if (value == 'updated') {
-      this.set('state', 'synced');
-      for (key in this.contentModels) {
-        if (this.contentModels[key].guid() == contentModel.guid()) {
-          this.contentModels[key].set('state', 'synced');
-          this.data.user_revision = this.contentModels[key].get('revision');
+    },
+  
+    successfulLoad: function(data) {
+        this.set('data', data);
+        this.set('state', 'synced');
+        this.contentModels = {
+            'xml': new Editor.XMLModel(data.text_url, data.user_revision),
+            'html': new Editor.HTMLModel(data.html_url, data.user_revision, data.text_url),
+            'gallery': new Editor.ImageGalleryModel(data.gallery_url)
+        };
+        for (var key in this.contentModels) {
+            this.contentModels[key].addObserver(this, 'state', this.contentModelStateChanged.bind(this));
         }
-      }
-      for (key in this.contentModels) {
-        if (this.contentModels[key].guid() != contentModel.guid()) {
-          this.contentModels[key].set('revision', this.data.user_revision);
-          this.contentModels[key].set('state', 'empty');
+        messageCenter.addMessage('success', 'Dane dokumentu zostały załadowane :-)');
+    },
+  
+    contentModelStateChanged: function(property, value, contentModel) {
+        if (value == 'dirty') {
+            this.set('state', 'dirty');
+            for (var key in this.contentModels) {
+                if (this.contentModels[key].guid() != contentModel.guid()) {
+                    this.contentModels[key].set('state', 'unsynced');
+                }
+            }
+        } else if (value == 'updated') {
+            this.set('state', 'synced');
+            for (key in this.contentModels) {
+                if (this.contentModels[key].guid() == contentModel.guid()) {
+                    this.contentModels[key].set('state', 'synced');
+                    this.data.user_revision = this.contentModels[key].get('revision');
+                }
+            }
+            for (key in this.contentModels) {
+                if (this.contentModels[key].guid() != contentModel.guid()) {
+                    this.contentModels[key].set('revision', this.data.user_revision);
+                    this.contentModels[key].set('state', 'empty');
+                }
+            }
         }
-      }
-    }
-  },
+    },
   
-  saveDirtyContentModel: function(message) {
-    for (var key in this.contentModels) {
-      if (this.contentModels[key].get('state') == 'dirty') {
-        this.contentModels[key].update(message);
-        break;
-      }
-    }
-  },
+    saveDirtyContentModel: function(message) {
+        for (var key in this.contentModels) {
+            if (this.contentModels[key].get('state') == 'dirty') {
+                this.contentModels[key].update(message);
+                break;
+            }
+        }
+    },
   
-  update: function() {
-    this.set('state', 'loading');
-    messageCenter.addMessage('info', 'Uaktualniam dokument...');
-    $.ajax({
-      url: this.data.merge_url,
-      dataType: 'json',
-      type: 'post',
-      data: {
-        type: 'update',
-        target_revision: this.data.user_revision
-      },
-      complete: this.updateCompleted.bind(this),
-      success: function(data) { this.set('updateData', data); }.bind(this)
-    });
-  },
+    update: function() {
+        this.set('state', 'loading');
+        messageCenter.addMessage('info', 'Uaktualniam dokument...');
+        $.ajax({
+            url: this.data.merge_url,
+            dataType: 'json',
+            type: 'post',
+            data: {
+                type: 'update',
+                target_revision: this.data.user_revision
+            },
+            complete: this.updateCompleted.bind(this),
+            success: function(data) {
+                this.set('updateData', data);
+            }.bind(this)
+        });
+    },
   
-  updateCompleted: function(xhr, textStatus) {
-    console.log(xhr.status, textStatus);
-    if (xhr.status == 200) { // Sukces
-      this.data.user_revision = this.get('updateData').revision;
-      messageCenter.addMessage('info', 'Uaktualnienie dokumentu do wersji ' + this.get('updateData').revision,
-        'Uaktualnienie dokumentu do wersji ' + this.get('updateData').revision);
-      for (var key in this.contentModels) {
-        this.contentModels[key].set('revision', this.data.user_revision);
-        this.contentModels[key].set('state', 'empty');
-      }
-      messageCenter.addMessage('success', 'Uaktualniłem dokument do najnowszej wersji :-)');
-    } else if (xhr.status == 202) { // Wygenerowano PullRequest (tutaj?)
-    } else if (xhr.status == 204) { // Nic nie zmieniono
-      messageCenter.addMessage('info', 'Nic się nie zmieniło od ostatniej aktualizacji. Po co mam uaktualniać?');
-    } else if (xhr.status == 409) { // Konflikt podczas operacji
-      messageCenter.addMessage('error', 'Wystąpił konflikt podczas aktualizacji. Pędź po programistów! :-(');
-    } else if (xhr.status == 500) {
-      messageCenter.addMessage('critical', 'Błąd serwera. Pędź po programistów! :-(');
-    } 
-    this.set('state', 'synced');
-    this.set('updateData', null);
-  },
+    updateCompleted: function(xhr, textStatus) {
+        console.log(xhr.status, textStatus);
+        if (xhr.status == 200) { // Sukces
+            this.data.user_revision = this.get('updateData').revision;
+            messageCenter.addMessage('info', 'Uaktualnienie dokumentu do wersji ' + this.get('updateData').revision,
+                'Uaktualnienie dokumentu do wersji ' + this.get('updateData').revision);
+            for (var key in this.contentModels) {
+                this.contentModels[key].set('revision', this.data.user_revision);
+                this.contentModels[key].set('state', 'empty');
+            }
+            messageCenter.addMessage('success', 'Uaktualniłem dokument do najnowszej wersji :-)');
+        } else if (xhr.status == 202) { // Wygenerowano PullRequest (tutaj?)
+        } else if (xhr.status == 204) { // Nic nie zmieniono
+            messageCenter.addMessage('info', 'Nic się nie zmieniło od ostatniej aktualizacji. Po co mam uaktualniać?');
+        } else if (xhr.status == 409) { // Konflikt podczas operacji
+            messageCenter.addMessage('error', 'Wystąpił konflikt podczas aktualizacji. Pędź po programistów! :-(');
+        } else if (xhr.status == 500) {
+            messageCenter.addMessage('critical', 'Błąd serwera. Pędź po programistów! :-(');
+        }
+        this.set('state', 'synced');
+        this.set('updateData', null);
+    },
   
-  merge: function(message) {
-    this.set('state', 'loading');
-    messageCenter.addMessage('info', 'Scalam dokument z głównym repozytorium...');
-    $.ajax({
-      url: this.data.merge_url,
-      type: 'post',
-      dataType: 'json',
-      data: {
-        type: 'share',
-        target_revision: this.data.user_revision,
-        message: message
-      },
-      complete: this.mergeCompleted.bind(this),
-      success: function(data) { this.set('mergeData', data); }.bind(this)
-    });
-  },
+    merge: function(message) {
+        this.set('state', 'loading');
+        messageCenter.addMessage('info', 'Scalam dokument z głównym repozytorium...');
+        $.ajax({
+            url: this.data.merge_url,
+            type: 'post',
+            dataType: 'json',
+            data: {
+                type: 'share',
+                target_revision: this.data.user_revision,
+                message: message
+            },
+            complete: this.mergeCompleted.bind(this),
+            success: function(data) {
+                this.set('mergeData', data);
+            }.bind(this)
+        });
+    },
   
-  mergeCompleted: function(xhr, textStatus) {
-    console.log(xhr.status, textStatus);
-    if (xhr.status == 200) { // Sukces
-      this.data.user_revision = this.get('mergeData').revision;
-      for (var key in this.contentModels) {
-        this.contentModels[key].set('revision', this.data.user_revision);
-        this.contentModels[key].set('state', 'empty');
-      }
-      messageCenter.addMessage('success', 'Scaliłem dokument z głównym repozytorium :-)');
-    } else if (xhr.status == 202) { // Wygenerowano PullRequest
-      messageCenter.addMessage('success', 'Wysłałem prośbę o scalenie dokumentu z głównym repozytorium.');
-    } else if (xhr.status == 204) { // Nic nie zmieniono
-      messageCenter.addMessage('info', 'Nic się nie zmieniło od ostatniego scalenia. Po co mam scalać?');
-    } else if (xhr.status == 409) { // Konflikt podczas operacji
-      messageCenter.addMessage('error', 'Wystąpił konflikt podczas scalania. Pędź po programistów! :-(');
-    } else if (xhr.status == 500) {
-      messageCenter.addMessage('critical', 'Błąd serwera. Pędź po programistów! :-(');
-    }
-    this.set('state', 'synced');
-    this.set('mergeData', null);
-  },
+    mergeCompleted: function(xhr, textStatus) {
+        console.log(xhr.status, textStatus);
+        if (xhr.status == 200) { // Sukces
+            this.data.user_revision = this.get('mergeData').revision;
+            for (var key in this.contentModels) {
+                this.contentModels[key].set('revision', this.data.user_revision);
+                this.contentModels[key].set('state', 'empty');
+            }
+            messageCenter.addMessage('success', 'Scaliłem dokument z głównym repozytorium :-)');
+        } else if (xhr.status == 202) { // Wygenerowano PullRequest
+            messageCenter.addMessage('success', 'Wysłałem prośbę o scalenie dokumentu z głównym repozytorium.');
+        } else if (xhr.status == 204) { // Nic nie zmieniono
+            messageCenter.addMessage('info', 'Nic się nie zmieniło od ostatniego scalenia. Po co mam scalać?');
+        } else if (xhr.status == 409) { // Konflikt podczas operacji
+            messageCenter.addMessage('error', 'Wystąpił konflikt podczas scalania. Pędź po programistów! :-(');
+        } else if (xhr.status == 500) {
+            messageCenter.addMessage('critical', 'Błąd serwera. Pędź po programistów! :-(');
+        }
+        this.set('state', 'synced');
+        this.set('mergeData', null);
+    },
   
-  // For debbuging
-  set: function(property, value) {
-    if (property == 'state') {
-      console.log(this.description(), ':', property, '=', value);
+    // For debbuging
+    set: function(property, value) {
+        if (property == 'state') {
+            console.log(this.description(), ':', property, '=', value);
+        }
+        return this._super(property, value);
     }
-    return this._super(property, value);
-  }
 });
 
 
@@ -421,16 +544,16 @@ var leftPanelView, rightPanelContainer, doc;
 
 $(function()
 {
-  documentsUrl = $('#api-base-url').text() + '/';
-  toolbarUrl = $('#api-toolbar-url').text();
+    documentsUrl = $('#api-base-url').text() + '/';
+    toolbarUrl = $('#api-toolbar-url').text();
 
-  doc = new Editor.DocumentModel();
-  var editor = new EditorView('#body-wrap', doc);  
-  editor.freeze();
+    doc = new Editor.DocumentModel();
+    var editor = new EditorView('#body-wrap', doc);
+    editor.freeze();
 
-  var flashView = new FlashView('#flashview', messageCenter);
-  var splitView = new SplitView('#splitview', doc);
+    var flashView = new FlashView('#flashview', messageCenter);
+    var splitView = new SplitView('#splitview', doc);
 
-  leftPanelView = new PanelContainerView('#left-panel-container', doc);
-  rightPanelContainer = new PanelContainerView('#right-panel-container', doc); 
+    leftPanelView = new PanelContainerView('#left-panel-container', doc);
+    rightPanelContainer = new PanelContainerView('#right-panel-container', doc);
 });
diff --git a/project/static/js/views/html.js b/project/static/js/views/html.js
index a8eb4d01..ebdf90af 100644
--- a/project/static/js/views/html.js
+++ b/project/static/js/views/html.js
@@ -1,59 +1,130 @@
 /*global View render_template panels */
 var HTMLView = View.extend({
-  _className: 'HTMLView',
-  element: null,
-  model: null,
-  template: 'html-view-template',
+    _className: 'HTMLView',
+    element: null,
+    model: null,
+    template: 'html-view-template',
   
-  init: function(element, model, parent, template) {
-    this._super(element, model, template);
-    this.parent = parent;
+    init: function(element, model, parent, template) {
+        this._super(element, model, template);
+        this.parent = parent;
     
-    this.model
-      .addObserver(this, 'data', this.modelDataChanged.bind(this))
-      .addObserver(this, 'state', this.modelStateChanged.bind(this));
+        this.model
+        .addObserver(this, 'data', this.modelDataChanged.bind(this))        
+        .addObserver(this, 'state', this.modelStateChanged.bind(this));
       
-    $('.htmlview', this.element).html(this.model.get('data'));
-    this.modelStateChanged('state', this.model.get('state'));
-    this.model.load();
-  },
-  
-  modelDataChanged: function(property, value) {
-    $('.htmlview', this.element).html(value);
+        $('.htmlview', this.element).html(this.model.get('data'));
+        this.modelStateChanged('state', this.model.get('state'));
+        this.model.load();
+    },
+
+    modelDataChanged: function(property, value) {
+        $('.htmlview', this.element).html(value);
 
-    var base = this.$printLink.attr('ui:baseref');
-    this.$printLink.attr('href', base + "?revision=" + this.model.get('revision'));
-  },
+        var base = this.$printLink.attr('ui:baseref');
+        this.$printLink.attr('href', base + "?revision=" + this.model.get('revision'));
+    },
   
-  modelStateChanged: function(property, value) {
-    if (value == 'synced' || value == 'dirty') {
-      this.unfreeze();
-    } else if (value == 'unsynced') {
-      this.freeze('Niezsynchronizowany...');
-    } else if (value == 'loading') {
-      this.freeze('Ładowanie...');
-    } else if (value == 'saving') {
-      this.freeze('Zapisywanie...');
-    } else if (value == 'error') {
-      this.freeze(this.model.get('error'));
-    }
-  },
+    modelStateChanged: function(property, value) {
+        if (value == 'synced' || value == 'dirty') {
+            this.unfreeze();
+        } else if (value == 'unsynced') {
+            this.freeze('Niezsynchronizowany...');
+        } else if (value == 'loading') {
+            this.freeze('Ładowanie...');
+        } else if (value == 'saving') {
+            this.freeze('Zapisywanie...');
+        } else if (value == 'error') {
+            this.freeze(this.model.get('error'));
+        }
+    },
 
+    render: function() {
+        this.element.unbind('click');
 
-  render: function() {
-      if(this.$printLink) this.$printLink.unbind();
-      this._super();
-      this.$printLink = $('.html-print-link', this.element);
-  },
+        if(this.$printLink) this.$printLink.unbind();
+        this._super();
+        this.$printLink = $('.html-print-link', this.element);
+
+        this.element.bind('click', this.itemClicked.bind(this));
+    },
+  
+    reload: function() {
+        this.model.load(true);
+    },
   
-  reload: function() {
-    this.model.load(true);
-  },
+    dispose: function() {
+        this.model.removeObserver(this);
+        this._super();
+    },
+
+    itemClicked: function(event) 
+    {
+        var self = this;
+        
+        console.log('click:', event, event.ctrlKey, event.target);
+        var editableContent = null;
+        var $e = $(event.target);
+
+        var n = 0;
+
+        while( ($e[0] != this.element[0]) && !($e.attr('wl2o:editable'))
+            && n < 50)
+        {
+            // console.log($e, $e.parent(), this.element);
+            $e = $e.parent();
+            n += 1;
+        }
+      
+        if(!$e.attr('wl2o:editable'))
+            return true;
+    
+        // start edition on this node
+        
+
+        var $overlay = $(
+        '<div class="html-editarea">\n\
+            <p class="html-editarea-toolbar">\n\
+                <button class="html-editarea-save-button" type="button">Zapisz</button>\n\
+                <button class="html-editarea-cancel-button" type="button">Anuluj</button>\n\
+            </p>\n\
+            <textarea></textarea>\n\
+        </div>');
+
+        var x = $e[0].offsetLeft;
+        var y = $e[0].offsetTop;
+        var w = $e.outerWidth();
+        var h = $e.innerHeight();
+        $overlay.css({position: 'absolute', height: h, left: "5%", top: y, width: "90%"});
+        $e.offsetParent().append($overlay);
+
+        // load the original XML content
+        console.log($e, $e.offsetParent(), $overlay);
+                        
+        $('.html-editarea-cancel-button', $overlay).click(function() {
+            $overlay.remove();
+        });
+
+        $('.html-editarea-save-button', $overlay).click(function() {
+            $overlay.remove();
+
+            // put the part back to the model
+            self.model.putXMLPart($e, $('textarea', $overlay).val());
+        });
+
+        $('textarea', $overlay).focus(function() {
+            $overlay.css('z-index', 3000);
+        }).blur(function() {
+            $overlay.css('z-index', 2000);
+        });
+
+        this.model.getXMLPart($e, function(path, data) {
+            $('textarea', $overlay).val(data);
+        });
+        
+        return false;
+    }
   
-  dispose: function() {
-    this.model.removeObserver(this);
-    this._super();
-  }
 });
 
 // Register view
diff --git a/project/static/js/views/xml.js b/project/static/js/views/xml.js
index 02154723..00547d19 100644
--- a/project/static/js/views/xml.js
+++ b/project/static/js/views/xml.js
@@ -102,7 +102,7 @@ var XMLView = View.extend({
            || (code >= 65 && code <= 90)) ) return null;
 
         var ch = String.fromCharCode(code & 0xff).toLowerCase();
-        console.log(ch.charCodeAt(0), '#', buttons);
+        /* # console.log(ch.charCodeAt(0), '#', buttons); */
 
         var buttons = $('.buttontoolbarview-button[title='+ch+']', this.element);
         var mod = 0;
@@ -129,8 +129,7 @@ var XMLView = View.extend({
     },
 
     isHotkey: function() {
-        console.log(arguments);
-        
+        /* console.log(arguments); */
         if(this.getHotkey.apply(this, arguments))
             return true;
         else
diff --git a/project/templates/explorer/editor.html b/project/templates/explorer/editor.html
index 620d8536..b338e6d3 100644
--- a/project/templates/explorer/editor.html
+++ b/project/templates/explorer/editor.html
@@ -13,6 +13,7 @@
 	{# Libraries #}
     <script src="{{STATIC_URL}}js/lib/codemirror/codemirror.js" type="text/javascript" charset="utf-8"></script>
 	<script src="{{STATIC_URL}}js/lib/jquery.modal.js" type="text/javascript" charset="utf-8"></script>
+        <script src="{{STATIC_URL}}js/lib/jquery.json.js" type="text/javascript" charset="utf-8"></script>
 	{# Scriptlets #}
 	<script src="{{STATIC_URL}}js/button_scripts.js" type="text/javascript" charset="utf-8"></script>
 	
@@ -58,7 +59,7 @@
                 
 		<div class="htmlview">
 		</div>
-	</script>
+	</script>	
 
 	<script type="text/html" charset="utf-8" id="flash-view-template">
 		<div class="flashview">
diff --git a/project/templates/wysiwyg.html b/project/templates/wysiwyg.html
index ec6eaed3..a592bf11 100644
--- a/project/templates/wysiwyg.html
+++ b/project/templates/wysiwyg.html
@@ -4,12 +4,7 @@
 <h1>Wysiwyg editor</h1>
 <div>This part is not editable!</div>
 <div id="loremIpsum">
-
- {% for a,b in listA,listB %}
-
- {{ a }} / {{ b }}
- {% endfor %}
-
+ 
 <p>
 <p>Lorem <b>ipsum</b> dolor sit amet, consectetur adipiscing elit.
     Suspendisse a urna eu enim rutrum elementum nec sed nibh. Quisque sed tortor