HTML View load&save
authorŁukasz Rekucki <lrekucki@gmail.com>
Thu, 8 Oct 2009 10:29:40 +0000 (12:29 +0200)
committerŁukasz Rekucki <lrekucki@gmail.com>
Thu, 8 Oct 2009 10:29:40 +0000 (12:29 +0200)
16 files changed:
apps/api/handlers/library_handlers.py
apps/api/urls.py
apps/api/views.py
apps/explorer/models.py
apps/wysiwyg/tests.py [deleted file]
lib/wlrepo/__init__.py
lib/wlrepo/mercurial_backend/document.py
project/static/css/html.css
project/static/css/master.css
project/static/js/editor.js
project/static/js/editor.ui.js [deleted file]
project/static/js/models.js
project/static/js/views/html.js
project/static/js/views/xml.js
project/templates/explorer/editor.html
project/templates/wysiwyg.html

index b726067..4435fe9 100644 (file)
@@ -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
index 0ae1420..deac319 100644 (file)
@@ -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
index 60f00ef..b96fc47 100644 (file)
@@ -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
index 7ab6b09..23acd7f 100644 (file)
@@ -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 (file)
index 2247054..0000000
+++ /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
-"""}
-
index ab6f319..430e59f 100644 (file)
@@ -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
index a579fb7..e714561 100644 (file)
@@ -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)
index 4904a0d..336a365 100644 (file)
@@ -3,7 +3,7 @@
     font-size: 16px;
     font: Georgia, "Times New Roman", serif;
     line-height: 1.5em;
-    padding: 3em;
+    padding: 3em;    
 }
 
 .htmlview div {
 /* = 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;
 }
 
 /* =================== */
 /* = 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;
 }
     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;
 }
index 653b864..d930909 100644 (file)
@@ -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
index 6918f9e..f52950f 100644 (file)
@@ -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 (executable)
index c8d47f6..0000000
+++ /dev/null
@@ -1,359 +0,0 @@
-/*\r
- * UI related Editor methods\r
- */\r
-Editor.prototype.setupUI = function() {\r
-//     // set up the UI visually and attach callbacks\r
-    var self = this;\r
-// \r
-//     var resize_start = function(event, mydata) {\r
-//         $(document).bind('mousemove', mydata, resize_changed).\r
-//         bind('mouseup', mydata, resize_stop);\r
-// \r
-//         $('.panel-overlay', mydata.root).css('display', 'block');\r
-//         return false;\r
-//     }\r
-//     var resize_changed =  function(event) {\r
-//         var old_width = parseInt(event.data.overlay.css('width'));\r
-//         var delta = event.pageX + event.data.hotspot_x - old_width;\r
-// \r
-//         if(old_width + delta < 12) delta = 12 - old_width;\r
-//         if(old_width + delta > $(window).width()) \r
-//             delta = $(window).width() - old_width;\r
-//         \r
-//         event.data.overlay.css({\r
-//             'width': old_width + delta\r
-//         });\r
-// \r
-//         if(event.data.overlay.next) {\r
-//             var left = parseInt(event.data.overlay.next.css('left'));\r
-//             event.data.overlay.next.css('left', left+delta);\r
-//         }\r
-// \r
-//         return false;\r
-//     };\r
-// \r
-//     var resize_stop = function(event) {\r
-//         $(document).unbind('mousemove', resize_changed).unbind('mouseup', resize_stop);\r
-//         // $('.panel-content', event.data.root).css('display', 'block');\r
-//         var overlays = $('.panel-content-overlay', event.data.root);\r
-//         $('.panel-content-overlay', event.data.root).each(function(i) {\r
-//             if( $(this).data('panel').hasClass('last-panel') )\r
-//                 $(this).data('panel').css({\r
-//                     'left': $(this).css('left'),\r
-//                     'right': $(this).css('right')\r
-//                 });\r
-//             else\r
-//                 $(this).data('panel').css({\r
-//                     'left': $(this).css('left'),\r
-//                     'width': $(this).css('width')\r
-//                 });\r
-//         });\r
-//         $('.panel-overlay', event.data.root).css('display', 'none');\r
-//         $(event.data.root).trigger('stopResize');\r
-//     };\r
-// \r
-//     /*\r
-//      * Prepare panels (overlays & stuff)\r
-//      */\r
-//     /* create an overlay */\r
-//     var panel_root = self.rootDiv;\r
-//     var overlay_root = $("<div class='panel-overlay'></div>");\r
-//     panel_root.append(overlay_root);\r
-// \r
-//     var prev = null;\r
-// \r
-//     $('*.panel-wrap', panel_root).each( function()\r
-//     {\r
-//         var panel = $(this);\r
-//         var handle = $('.panel-slider', panel);\r
-//         var overlay = $("<div class='panel-content-overlay panel-wrap'>&nbsp;</div>");\r
-//         overlay_root.append(overlay);\r
-//         overlay.data('panel', panel);\r
-//         overlay.data('next', null);\r
-// \r
-//         if (prev) prev.next = overlay;\r
-// \r
-//         if( panel.hasClass('last-panel') )\r
-//         {\r
-//             overlay.css({\r
-//                 'left': panel.css('left'),\r
-//                 'right': panel.css('right')\r
-//             });\r
-//         }\r
-//         else {\r
-//             overlay.css({\r
-//                 'left': panel.css('left'),\r
-//                 'width': panel.css('width')\r
-//             });\r
-//             // $.log('Has handle: ' + panel.attr('id'));\r
-//             overlay.append(handle.clone());\r
-//             /* attach the trigger */\r
-//             handle.mousedown(function(event) {\r
-//                 var touch_data = {\r
-//                     root: panel_root,\r
-//                     overlay: overlay,\r
-//                     hotspot_x: event.pageX - handle.position().left\r
-//                 };\r
-// \r
-//                 $(this).trigger('hpanel:panel-resize-start', touch_data);\r
-//                 return false;\r
-//             });\r
-//             $('.panel-content', panel).css('right',\r
-//                 (handle.outerWidth() || 10) + 'px');\r
-//             $('.panel-content-overlay', panel).css('right',\r
-//                 (handle.outerWidth() || 10) + 'px');\r
-//         };\r
-// \r
-//         prev = overlay;\r
-//     });\r
-// \r
-//     panel_root.bind('hpanel:panel-resize-start', resize_start);\r
-//     self.rootDiv.bind('stopResize', function() {\r
-//         self.savePanelOptions();      \r
-//     });\r
-//     \r
-    /*\r
-     * Connect panel actions\r
-     */\r
-    $('#panels > *.panel-wrap').each(function() {\r
-        var panelWrap = $(this);\r
-        // $.log('wrap: ', panelWrap);\r
-        var panel = new Panel(panelWrap);\r
-        panelWrap.data('ctrl', panel); // attach controllers to wraps\r
-        panel.load($('.panel-toolbar select', panelWrap).val());\r
-\r
-        $('.panel-toolbar select', panelWrap).change(function() {\r
-            var url = $(this).val();\r
-            panelWrap.data('ctrl').load(url);\r
-            self.savePanelOptions();\r
-        });\r
-\r
-        $('.panel-toolbar button.refresh-button', panelWrap).click(\r
-            function() {\r
-                panel.refresh();\r
-            } );\r
-\r
-        self.rootDiv.bind('stopResize', function() {\r
-            panel.callHook('toolbarResized');\r
-        });\r
-    });\r
-\r
-    $(document).bind('panel:contentChanged', function() {\r
-        self.onContentChanged.apply(self, arguments)\r
-    });  \r
-\r
-    /*\r
-     * Connect various buttons\r
-     */\r
-\r
-    $('#toolbar-button-quick-save').click( function (event, data) {\r
-        self.saveToBranch();\r
-    } );\r
-\r
-    $('#toolbar-button-save').click( function (event, data) {\r
-        $('#commit-dialog').jqmShow( {callback: $.fbind(self, self.saveToBranch)} );\r
-    } );\r
-\r
-    $('#toolbar-button-update').click( function (event, data) {\r
-        if (self.updateUserBranch()) {\r
-            // commit/update can be called only after proper, save\r
-            // this means all panels are clean, and will get refreshed\r
-            // do this only, when there are any changes to local branch\r
-            self.refreshPanels();\r
-        }\r
-    } );\r
-\r
-    /* COMMIT DIALOG */\r
-    $('#commit-dialog').\r
-    jqm({\r
-        modal: true,\r
-        onShow: $.fbind(self, self.loadRelatedIssues)        \r
-    });\r
-\r
-    $('#toolbar-button-commit').click( function (event, data) {\r
-        $('#commit-dialog').jqmShow( {callback: $.fbind(self, self.sendMergeRequest)} );\r
-    } );\r
-    \r
-    /* STATIC BINDS */\r
-    $('#commit-dialog-cancel-button').click(function() {\r
-        $('#commit-dialog-error-empty-message').hide();\r
-        $('#commit-dialog').jqmHide();\r
-    });   \r
-    \r
-\r
-    /* SPLIT DIALOG */\r
-    $('#split-dialog').jqm({\r
-        modal: true,\r
-        onShow: $.fbind(self, self.loadSplitDialog)\r
-    }).\r
-    jqmAddClose('button.dialog-close-button');\r
-}\r
-\r
-Editor.prototype.loadRelatedIssues = function(hash)\r
-{\r
-    var self = this;\r
-    var c = $('#commit-dialog-related-issues');\r
-\r
-    $('#commit-dialog-save-button').click( function (event, data)\r
-    {\r
-        if( $('#commit-dialog-message').val().match(/^\s*$/)) {\r
-            $('#commit-dialog-error-empty-message').fadeIn();\r
-        }\r
-        else {\r
-            $('#commit-dialog-error-empty-message').hide();\r
-            $('#commit-dialog').jqmHide();\r
-\r
-            var message = $('#commit-dialog-message').val();\r
-            $('#commit-dialog-related-issues input:checked').\r
-                each(function() { message += ' refs #' + $(this).val(); });\r
-            $.log("COMMIT APROVED", hash.t);\r
-            hash.t.callback(message);\r
-        }\r
-\r
-        return false;\r
-    });\r
-\r
-    $("div.loading-box", c).show();\r
-    $("div.fatal-error-box", c).hide();\r
-    $("div.container-box", c).hide();\r
-    \r
-    $.getJSON( c.attr('ui:ajax-src') + '?callback=?',\r
-    function(data, status)\r
-    {\r
-        var fmt = '';\r
-        $(data).each( function() {\r
-            fmt += '<label><input type="checkbox" checked="checked"'\r
-            fmt += ' value="' + this.id + '" />' + this.subject +'</label>\n'\r
-        });\r
-        $("div.container-box", c).html(fmt);\r
-        $("div.loading-box", c).hide();\r
-        $("div.container-box", c).show();        \r
-    });   \r
-    \r
-    hash.w.show();\r
-}\r
-\r
-Editor.prototype.loadSplitDialog = function(hash)\r
-{\r
-    var self = this;    \r
-    \r
-    $("div.loading-box", hash.w).show();\r
-    $("div.fatal-error-box", hash.w).hide();\r
-    $('div.container-box', hash.w).hide();\r
-    hash.w.show();\r
-\r
-    function onFailure(rq, tstat, err) {\r
-        $('div.container-box', hash.w).html('');\r
-        $("div.loading-box", hash.w).hide();\r
-        $("div.fatal-error-box", hash.w).show();\r
-        hash.t.failure();\r
-    };\r
-\r
-    function onSuccess(data, status) {\r
-        // put the form into the window\r
-        $('div.container-box', hash.w).html(data);\r
-        $("div.loading-box", hash.w).hide();\r
-        $('form input[name=splitform-splittext]', hash.w).val(hash.t.selection);\r
-        $('form input[name=splitform-fulltext]', hash.w).val(hash.t.fulltext);\r
-        $('div.container-box', hash.w).show();\r
-\r
-        // connect buttons\r
-        $('#split-dialog-button-accept').click(function() {\r
-            self.postSplitRequest(onSuccess, onFailure);\r
-            return false;\r
-        });\r
-\r
-        $('#split-dialog-button-close').click(function() {\r
-            hash.w.jqmHide();\r
-            $('div.container-box', hash.w).html('');\r
-            hash.t.failure();\r
-        });\r
-\r
-        $('#split-dialog-button-dismiss').click(function() {\r
-            hash.w.jqmHide();\r
-            $('div.container-box', hash.w).html('');\r
-            hash.t.success();\r
-        });\r
-\r
-        /* if($('#id_splitform-autoxml').is(':checked'))\r
-            $('#split-form-dc-subform').show();\r
-        else\r
-            $('#split-form-dc-subform').hide();\r
-\r
-        $('#id_splitform-autoxml').change(function() {            \r
-            if( $(this).is(':checked') )\r
-                $('#split-form-dc-subform').show();\r
-            else\r
-                $('#split-form-dc-subform').hide();\r
-        }); */\r
-    };   \r
-\r
-    $.ajax({\r
-        url: 'split',\r
-        dataType: 'html',\r
-        success: onSuccess,\r
-        error: onFailure,\r
-        type: 'GET',\r
-        data: {}\r
-    });\r
-}\r
-\r
-/* Refreshing routine */\r
-Editor.prototype.refreshPanels = function() {\r
-    var self = this;\r
-\r
-    self.allPanels().each(function() {\r
-        var panel = $(this).data('ctrl');\r
-        $.log('Refreshing: ', this, panel);\r
-        if ( panel.changed() )\r
-            panel.unmarkChanged();\r
-        else\r
-            panel.refresh();\r
-    });\r
-\r
-    $('button.provides-save').attr('disabled', 'disabled');\r
-    $('button.requires-save').removeAttr('disabled');\r
-};\r
-\r
-/*\r
- * Pop-up messages\r
- */\r
-Editor.prototype.showPopup = function(name, text, timeout)\r
-{\r
-    timeout = timeout || 4000;\r
-    var self = this;\r
-    self.popupQueue.push( [name, text, timeout] )\r
-\r
-    if( self.popupQueue.length > 1)\r
-        return;\r
-\r
-    var box = $('#message-box > #' + name);\r
-    $('*.data', box).html(text || '');\r
-    box.fadeIn(100);\r
-\r
-    if(timeout > 0)\r
-        setTimeout( $.fbind(self, self.advancePopupQueue), timeout);\r
-};\r
-\r
-Editor.prototype.advancePopupQueue = function() {\r
-    var self = this;\r
-    var elem = this.popupQueue.shift();\r
-    if(elem) {\r
-        var box = $('#message-box > #' + elem[0]);\r
-\r
-        box.fadeOut(100, function()\r
-        {\r
-            $('*.data', box).html('');\r
-\r
-            if( self.popupQueue.length > 0) {\r
-                var ibox = $('#message-box > #' + self.popupQueue[0][0]);\r
-                $('*.data', ibox).html(self.popupQueue[0][1] || '');\r
-                ibox.fadeIn(100);\r
-                if(self.popupQueue[0][2] > 0)\r
-                    setTimeout( $.fbind(self, self.advancePopupQueue), self.popupQueue[0][2]);\r
-            }\r
-        });\r
-    }\r
-};\r
-\r
-\r
index 0b00c7c..d183f99 100644 (file)
@@ -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) {
index a8eb4d0..ebdf90a 100644 (file)
 /*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
index 0215472..00547d1 100644 (file)
@@ -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
index edb3bfc..fb7b252 100644 (file)
@@ -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">
index ec6eaed..a592bf1 100644 (file)
@@ -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