Interaktywne błędy, gdy nie udało się wczytać HTML'a.
authorŁukasz Rekucki <lrekucki@gmail.com>
Fri, 9 Oct 2009 15:46:33 +0000 (17:46 +0200)
committerŁukasz Rekucki <lrekucki@gmail.com>
Fri, 9 Oct 2009 15:46:33 +0000 (17:46 +0200)
Tooltipy w przyciskach.

apps/api/handlers/library_handlers.py
apps/api/handlers/toolbar_handlers.py
apps/toolbar/models.py
project/static/js/models.js
project/static/js/views/button_toolbar.js
project/static/js/views/editor.js
project/static/js/views/html.js
project/static/js/views/split.js
project/static/js/views/xml.js
project/templates/explorer/editor.html

index d7438a1..488c2d4 100644 (file)
@@ -38,6 +38,7 @@ log = logging.getLogger('platforma.api')
 #
 # Document List Handlers
 #
+# TODO: security check
 class BasicLibraryHandler(AnonymousBaseHandler):
     allowed_methods = ('GET',)
 
@@ -51,10 +52,14 @@ class BasicLibraryHandler(AnonymousBaseHandler):
         return {'documents' : document_list}
         
 
+#
+# This handler controlls the document collection
+#
 class LibraryHandler(BaseHandler):
     allowed_methods = ('GET', 'POST')
     anonymous = BasicLibraryHandler
 
+
     @hglibrary
     def read(self, request, lib):
         """Return the list of documents."""
@@ -138,12 +143,15 @@ class LibraryHandler(BaseHandler):
                 lock.release()
         except LibraryException, e:
             import traceback
-            return response.InternalError().django_response(\
-                {'exception': traceback.format_exc()} )
+            return response.InternalError().django_response({
+                "reason": traceback.format_exc()
+            })
         except DocumentAlreadyExists:
             # Document is already there
-            return response.EntityConflict().django_response(\
-                {"reason": "Document %s already exists." % docid})
+            return response.EntityConflict().django_response({
+                "reason": "already-exists",
+                "message": "Document already exists." % docid
+            })
 
 #
 # Document Handlers
@@ -318,7 +326,8 @@ class DocumentTextHandler(BaseHandler):
                 # we're done :)
                 return document.data('xml')
             else:
-                xdoc = parser.WLDocument.from_string(document.data('xml'))
+                xdoc = parser.WLDocument.from_string(document.data('xml'),\
+                    parse_dublincore=False)
                 ptext = xdoc.part_as_text(part)
 
                 if ptext is None:
@@ -547,33 +556,35 @@ class MergeHandler(BaseHandler):
                     "provided": target_rev,
                     "latest": udoc.revision })
 
-        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
-            try:
-                prq, created = PullRequest.objects.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
             # to the user branch
             success, changed = udoc.update(request.user.username)
 
-        if form.cleaned_data['type'] == 'share':
-            success, changed = udoc.share(form.cleaned_data['message'])
+        if form.cleaned_data['type'] == '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
+                try:
+                    prq, created = PullRequest.objects.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:
+                    return response.EntityConflict().django_response({
+                        'reason': 'request-already-exist'
+                    })
+            else:
+                success, changed = udoc.share(form.cleaned_data['message'])
 
         if not success:
             return response.EntityConflict().django_response({
index be77359..5408db3 100644 (file)
@@ -17,9 +17,7 @@ class ToolbarHandler(BaseHandler):
 
     def read(self, request):
         groups = toolbar.models.ButtonGroup.objects.all()
-        return [ {'name': g.name, 'position': g.position,\
-            'buttons': g.button_set.all()} for g in groups ]
-            
+        return [g.to_dict(with_buttons=True) for g in groups]
             
 class ScriptletsHandler(BaseHandler):
     allowed_methods = ('GET',)
index 0414622..1b986c3 100644 (file)
@@ -14,6 +14,13 @@ class ButtonGroup(models.Model):
     def __unicode__(self):
         return self.name
 
+    def to_dict(self, with_buttons=False):
+        d = {'name': self.name, 'position': self.position}
+
+        if with_buttons:
+            d['buttons'] = [ b.to_dict() for b in self.button_set.all() ]
+
+        return d
 
 #class ButtonGroupManager(models.Manager):
 #
@@ -67,8 +74,18 @@ class Button(models.Model):
         if self.key_mod & 0x01: mods.append('Alt')
         if self.key_mod & 0x02: mods.append('Ctrl')
         if self.key_mod & 0x04: mods.append('Shift')
-        mods.append('"'+self.key+'"')
-        return '+'.join(mods)
+        mods.append(str(self.key))
+        return '[' + '+'.join(mods) + ']'
+
+    def to_dict(self):
+        return {
+            'label': self.label,
+            'tooltip': (self.tooltip or '') + self.hotkey_name(),
+            'key': self.key,
+            'key_mod': self.key_mod,
+            'params': self.params,
+            'scriptlet_id': self.scriptlet_id
+        }
     
     def __unicode__(self):
         return self.label
index 9a42bca..c03aa1e 100644 (file)
@@ -176,7 +176,7 @@ Editor.HTMLModel = Editor.Model.extend({
             this.set('state', 'loading');
 
             // load the transformed data
-            messageCenter.addMessage('info', 'Wczytuję HTML...');
+            // messageCenter.addMessage('info', 'Wczytuję HTML...');
 
             $.ajax({
                 url: this.htmlURL,
@@ -196,16 +196,42 @@ Editor.HTMLModel = Editor.Model.extend({
         }
         this.set('data', data);
         this.set('state', 'synced');
-        messageCenter.addMessage('success', 'Wczytałem HTML :-)');
+        // messageCenter.addMessage('success', 'Wczytałem HTML :-)');
     },
   
-    loadingFailed: function() {
+    loadingFailed: function(response) {
         if (this.get('state') != 'loading') {
             alert('erroneous state:', this.get('state'));
         }
-        this.set('error', 'Nie udało się załadować panelu');
+
+        var json_response = null;
+        var message = "";
+
+        try {
+            json_response = $.evalJSON(response.responseText);
+
+            if(json_response.reason == 'xml-parse-error') {
+
+                message = json_response.message.replace(/(line\s+)(\d+)(\s+)/i,
+                    "<a class='xml-editor-ref' href='#xml-$2-1'>$1$2$3</a>");
+
+                message = message.replace(/(line\s+)(\d+)(\,\s*column\s+)(\d+)/i,
+                    "<a class='xml-editor-ref' href='#xml-$2-$4'>$1$2$3$4</a>");
+
+                
+            }
+            else {
+                message = json_response.message || json_response.reason || "nieznany błąd.";
+            }
+        }
+        catch (e) {
+            message = response.statusText;
+        }
+
+        this.set('error', '<p>Nie udało się wczytać widoku HTML: </p>' + message);
+
         this.set('state', 'error');
-        messageCenter.addMessage('error', 'Nie udało mi się wczytać HTML. Spróbuj ponownie :-(');
+        // messageCenter.addMessage('error', 'Nie udało mi się wczytać HTML. Spróbuj ponownie :-(');
     },
 
     getXMLPart: function(elem, callback)
@@ -548,12 +574,12 @@ $(function()
     toolbarUrl = $('#api-toolbar-url').text();
 
     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);
+    EditorView = new EditorView('#body-wrap', doc);
+    EditorView.freeze();
 
     leftPanelView = new PanelContainerView('#left-panel-container', doc);
     rightPanelContainer = new PanelContainerView('#right-panel-container', doc);
+
+    var flashView = new FlashView('#flashview', messageCenter);   
 });
index 7e4b6ea..7f03998 100644 (file)
@@ -47,6 +47,7 @@ var ButtonToolbarView = View.extend({
 
     buttonPressed: function(event)
     {
+        var self = this;
         var target = event.target;
         
         var groupIndex = parseInt($(target).attr('ui:groupindex'), 10);
@@ -57,9 +58,13 @@ var ButtonToolbarView = View.extend({
 
         console.log('Executing', scriptletId, 'with params', params);
         try {
-            scriptletCenter.scriptlets[scriptletId](this.parent, params);
+            self.parent.freeze('Wykonuję akcję...');
+            setTimeout(function() {
+                scriptletCenter.scriptlets[scriptletId](self.parent, params);
+                self.parent.unfreeze();
+            }, 10);
         } catch(e) {
-            console.log("Scriptlet", scriptletId, "failed.");
+            console.log("Scriptlet", scriptletId, "failed.", e);
         }
 
     },
index 10c77f6..2793141 100644 (file)
 /*global View render_template panels */
 var EditorView = View.extend({
-  _className: 'EditorView',
-  element: null,
-  model: null,
-  template: null,
+    _className: 'EditorView',
+    element: null,
+    model: null,
+    template: null,
   
-  init: function(element, model, template) {
-    this._super(element, model, template);
+    init: function(element, model, template) {
+        this._super(element, model, template);
     
-    this.quickSaveButton = $('#action-quick-save', this.element).bind('click.editorview', this.quickSave.bind(this));
-    this.commitButton = $('#action-commit', this.element).bind('click.editorview', this.commit.bind(this));
-    this.updateButton = $('#action-update', this.element).bind('click.editorview', this.update.bind(this));
-    this.mergeButton = $('#action-merge', this.element).bind('click.editorview', this.merge.bind(this));
+        this.quickSaveButton = $('#action-quick-save', this.element).bind('click.editorview', this.quickSave.bind(this));
+        this.commitButton = $('#action-commit', this.element).bind('click.editorview', this.commit.bind(this));
+        this.updateButton = $('#action-update', this.element).bind('click.editorview', this.update.bind(this));
+        this.mergeButton = $('#action-merge', this.element).bind('click.editorview', this.merge.bind(this));
     
-    this.model.addObserver(this, 'state', this.modelStateChanged.bind(this));
-    this.modelStateChanged('state', this.model.get('state'));
-      
-    // Inicjalizacja okien jQuery Modal
-    $('#commit-dialog', this.element).
-    jqm({
-        modal: true,
-        onShow: this.loadRelatedIssues.bind(this)
-    });
+        this.model.addObserver(this, 'state', this.modelStateChanged.bind(this));
+        this.modelStateChanged('state', this.model.get('state'));        
+
+        this.splitView = new SplitView('#splitview', doc);
+        
+        // Inicjalizacja okien jQuery Modal
+        $('#commit-dialog', this.element).
+        jqm({
+            modal: true,
+            onShow: this.loadRelatedIssues.bind(this)
+        });
     
-    $('#commit-dialog-cancel-button', this.element).click(function() {
-        $('#commit-dialog-error-empty-message').hide();
-        $('#commit-dialog').jqmHide();
-    });   
+        $('#commit-dialog-cancel-button', this.element).click(function() {
+            $('#commit-dialog-error-empty-message').hide();
+            $('#commit-dialog').jqmHide();
+        });
     
-    // $('#split-dialog').jqm({
-    //      modal: true,
-    //      onShow: $.fbind(self, self.loadSplitDialog)
-    //  }).
-    //  jqmAddClose('button.dialog-close-button');
+        // $('#split-dialog').jqm({
+        //      modal: true,
+        //      onShow: $.fbind(self, self.loadSplitDialog)
+        //  }).
+        //  jqmAddClose('button.dialog-close-button');
     
-    this.model.load();
-  },
+        this.model.load();
+    },
   
-  quickSave: function(event) {
-    this.model.saveDirtyContentModel();
-  },
+    quickSave: function(event) {
+        this.model.saveDirtyContentModel();
+    },
   
-  commit: function(event) {
-    $('#commit-dialog', this.element).jqmShow({callback: this.doCommit.bind(this)});
-  },
+    commit: function(event) {
+        $('#commit-dialog', this.element).jqmShow({
+            callback: this.doCommit.bind(this)
+            });
+    },
   
-  doCommit: function(message) {
-    this.model.saveDirtyContentModel(message);
-  },
+    doCommit: function(message) {
+        this.model.saveDirtyContentModel(message);
+    },
   
-  update: function(event) {
-    this.model.update();
-  },
+    update: function(event) {
+        this.model.update();
+    },
   
-  merge: function(event) {
-    $('#commit-dialog', this.element).jqmShow({callback: this.doMerge.bind(this)});
-  },
+    merge: function(event) {
+        $('#commit-dialog', this.element).jqmShow({
+            callback: this.doMerge.bind(this)
+            });
+    },
   
-  doMerge: function(message) {
-    this.model.merge(message);
-  },
+    doMerge: function(message) {
+        this.model.merge(message);
+    },
   
-  loadRelatedIssues: function(hash) {
-    var self = this;
-    var c = $('#commit-dialog-related-issues');
+    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();
+        $('#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(); });
-        console.log("COMMIT APROVED", hash.t);
-        hash.t.callback(message);
-      }
-      return false;
-    });
+                var message = $('#commit-dialog-message').val();
+                $('#commit-dialog-related-issues input:checked')
+                .each(function() {
+                    message += ' refs #' + $(this).val();
+                });
+                console.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();
+        $("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();        
-    });   
+        $.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();
-  },
+        hash.w.show();
+    },
   
-  modelStateChanged: function(property, value) {
-    // Uaktualnia stan przycisków
-    if (value == 'dirty') {
-      this.quickSaveButton.attr('disabled', null);
-      this.commitButton.attr('disabled', null);
-      this.updateButton.attr('disabled', 'disabled');
-      this.mergeButton.attr('disabled', 'disabled');
-    } else if (value == 'synced') {
-      this.quickSaveButton.attr('disabled', 'disabled');
-      this.commitButton.attr('disabled', 'disabled');
-      this.updateButton.attr('disabled', null);
-      this.mergeButton.attr('disabled', null);      
-    } else if (value == 'empty') {
-      this.quickSaveButton.attr('disabled', 'disabled');
-      this.commitButton.attr('disabled', 'disabled');
-      this.updateButton.attr('disabled', 'disabled');
-      this.mergeButton.attr('disabled', 'disabled');
-    }
-  },
+    modelStateChanged: function(property, value) {
+        // Uaktualnia stan przycisków
+        if (value == 'dirty') {
+            this.quickSaveButton.attr('disabled', null);
+            this.commitButton.attr('disabled', null);
+            this.updateButton.attr('disabled', 'disabled');
+            this.mergeButton.attr('disabled', 'disabled');
+        } else if (value == 'synced') {
+            this.quickSaveButton.attr('disabled', 'disabled');
+            this.commitButton.attr('disabled', 'disabled');
+            this.updateButton.attr('disabled', null);
+            this.mergeButton.attr('disabled', null);
+        } else if (value == 'empty') {
+            this.quickSaveButton.attr('disabled', 'disabled');
+            this.commitButton.attr('disabled', 'disabled');
+            this.updateButton.attr('disabled', 'disabled');
+            this.mergeButton.attr('disabled', 'disabled');
+        }
+    },
   
-  dispose: function() {
-    $('#action-quick-save', this.element).unbind('click.editorview');
-    $('#action-commit', this.element).unbind('click.editorview');
-    $('#action-update', this.element).unbind('click.editorview');
-    $('#action-merge', this.element).unbind('click.editorview');
+    dispose: function() {
+        $('#action-quick-save', this.element).unbind('click.editorview');
+        $('#action-commit', this.element).unbind('click.editorview');
+        $('#action-update', this.element).unbind('click.editorview');
+        $('#action-merge', this.element).unbind('click.editorview');
 
-    this.model.removeObserver(this);
-    this._super();
-  }
+        this.model.removeObserver(this);
+        this._super();
+    }    
 });
index d9de492..a3db1d2 100644 (file)
@@ -25,7 +25,10 @@ var HTMLView = View.extend({
         this.$printLink.attr('href', base + "?revision=" + this.model.get('revision'));
     },
   
-    modelStateChanged: function(property, value) {
+    modelStateChanged: function(property, value) 
+    {
+        var self = $(this);
+
         if (value == 'synced' || value == 'dirty') {
             this.unfreeze();
         } else if (value == 'unsynced') {
@@ -36,6 +39,21 @@ var HTMLView = View.extend({
             this.freeze('Zapisywanie...');
         } else if (value == 'error') {
             this.freeze(this.model.get('error'));
+            $('.xml-editor-ref', this.overlay).click(
+            function(event) {
+                console.log("Sending scroll rq.", this);
+                try {
+                    var href = $(this).attr('href').split('-');
+                    var line = parseInt(href[1]);
+                    var column = parseInt(href[2]);
+                    
+                    $(document).trigger('xml-scroll-request', {line:line, column:column});
+                } catch(e) {
+                    console.log(e);
+                }
+                
+                return false;
+            });
         }
     },
 
index 48f0de7..30eda4a 100644 (file)
@@ -17,7 +17,7 @@ var SplitView = View.extend({
   init: function(element, model) {
     this._super(element, model, null);
     this.element.css('position', 'relative');
-    this._resizingSubviews = false;
+    this._resizingSubviews = false;    
     
     this.views = $(">*", this.element[0]).css({
        position: 'absolute',                     // positioned inside splitter container
@@ -28,6 +28,7 @@ var SplitView = View.extend({
     
     this.leftView = $(this.views[0]);
     this.rightView = $(this.views[1]);
+    
     this.splitbar = $(this.views[2] || '<div></div>')
       .insertAfter(this.leftView)
       .css({
index 00547d1..1681cae 100644 (file)
@@ -1,4 +1,4 @@
-/*global View CodeMirror ButtonToolbarView render_template panels */
+/*global View CodeMirror ToolbarView render_template panels */
 var XMLView = View.extend({
     _className: 'XMLView',
     element: null,
@@ -18,8 +18,11 @@ var XMLView = View.extend({
         var self = this;
 
         $('.xmlview-toolbar', this.element).bind('resize.xmlview', this.resized.bind(this));
-   
-    
+
+        // scroll to the given position (if availble)
+        this.scrollCallback = this.scrollOnRequest.bind(this);
+        $(document).bind('xml-scroll-request', this.scrollCallback);
+       
         this.parent.freeze('Ładowanie edytora...');
         this.editor = new CodeMirror($('.xmlview', this.element).get(0), {
             parserfile: 'parsexml.js',
@@ -28,7 +31,7 @@ var XMLView = View.extend({
             parserConfig: {
                 useHTMLKludges: false
             },
-            textWrapping: false,
+            textWrapping: true,
             tabMode: 'spaces',
             indentUnit: 0,
             onChange: this.editorDataChanged.bind(this),
@@ -91,6 +94,8 @@ var XMLView = View.extend({
     },
     
     dispose: function() {
+        $(document).unbind('xml-scroll-request', this.scrollCallback);
+        
         this.model.removeObserver(this);
         $(this.editor.frame).remove();
         this._super();
@@ -104,7 +109,7 @@ var XMLView = View.extend({
         var ch = String.fromCharCode(code & 0xff).toLowerCase();
         /* # console.log(ch.charCodeAt(0), '#', buttons); */
 
-        var buttons = $('.buttontoolbarview-button[title='+ch+']', this.element);
+        var buttons = $('.buttontoolbarview-button[hotkey='+ch+']', this.element);
         var mod = 0;
             
         if(event.altKey) mod |= 0x01;
@@ -141,6 +146,16 @@ var XMLView = View.extend({
         this.buttonToolbar.buttonPressed({
             target: button
         });
+    },
+
+    scrollOnRequest: function(event, data) 
+    {
+        try {
+            var line = this.editor.nthLine(data.line);
+            this.editor.selectLines(line, (data.column-1));
+        } catch(e) {
+            console.log('Exception in scrollOnRequest:', e);
+        }
     }
 
 });
index b338e6d..3c775d4 100644 (file)
                                <div class="buttontoolbarview-group toolbar-buttons-container" ui:groupIndex="<%= i %>" style="display: none">
                                        <% for (var j=0; j < buttons[i].buttons.length; j++) { %>
                                                <% if (buttons[i].buttons[j].scriptlet_id) { %>
-                                               <button type="button" class="buttontoolbarview-button" 
-                                                    title="<%= buttons[i].buttons[j].key %>"
+                                               <button type="button" class="buttontoolbarview-button"
+                                                    title="<%= buttons[i].buttons[j].tooltip %>"
+                                                    hotkey="<%= buttons[i].buttons[j].key %>"
                                                     ui:hotkey_mod="<%= buttons[i].buttons[j].key_mod %>"
                                                     ui:groupindex="<%= i %>" ui:buttonindex="<%= j %>">
                                                        <%= buttons[i].buttons[j].label %>