From: Łukasz Rekucki <lrekucki@gmail.com>
Date: Thu, 8 Oct 2009 10:29:40 +0000 (+0200)
Subject: HTML View load&save
X-Git-Url: https://git.mdrn.pl/redakcja.git/commitdiff_plain/1d07e208b0897af64f71755c974762bc7cd19ca0?ds=sidebyside

HTML View load&save
---

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 653b8640..d9309094 100644
--- a/project/static/css/master.css
+++ b/project/static/css/master.css
@@ -343,10 +343,53 @@ body#base button {
     border-width: 1px;
     padding: 0px 0.5em;    
     font-family: Sans-Serif;
-    color: #000;
+    /* color: #000; */
     margin: 2px 4px;
 }
 
 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;
 }
\ No newline at end of file
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 0b00c7ce..d183f992 100644
--- a/project/static/js/models.js
+++ b/project/static/js/models.js
@@ -147,22 +147,30 @@ Editor.XMLModel = Editor.Model.extend({
 
 Editor.HTMLModel = Editor.Model.extend({
   _className: 'Editor.HTMLModel',
-  serverURL: null,
-  data: '',
+  dataURL: null,
+  htmlURL: null,
+  renderURL: null,
+  displaData: '',
+  xmlParts: {},
   state: 'empty',
   
-  init: function(serverURL, revision) {
+  init: function(htmlURL, revision, dataURL) {
     this._super();
     this.set('state', 'empty');
     this.set('revision', revision);
-    this.serverURL = serverURL;
+    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');
+
+      // load the transformed data
       $.ajax({
-        url: this.serverURL,
+        url: this.htmlURL,
         dataType: 'text',
         data: {revision: this.get('revision')},
         success: this.loadingSucceeded.bind(this),
@@ -187,6 +195,109 @@ Editor.HTMLModel = Editor.Model.extend({
     this.set('state', 'error');    
   },
 
+  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') {
@@ -277,7 +388,7 @@ Editor.DocumentModel = Editor.Model.extend({
     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),
+      '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) {
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 edb3bfcd..fb7b2520 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