Merge branch 'master' of stigma.nowoczesnapolska.org.pl:platforma into view-refactor
authorzuber <marek@stepniowski.com>
Mon, 28 Sep 2009 14:22:41 +0000 (16:22 +0200)
committerzuber <marek@stepniowski.com>
Mon, 28 Sep 2009 14:22:41 +0000 (16:22 +0200)
20 files changed:
project/static/css/master.css
project/static/js/app.js [new file with mode: 0644]
project/static/js/editor.js
project/static/js/editor.ui.js
project/static/js/lib/codemirror/codemirror.js
project/static/js/lib/codemirror/editor.js
project/static/js/lib/codemirror/select.js
project/static/js/lib/codemirror/stringstream.js
project/static/js/lib/codemirror/undo.js
project/static/js/lib/codemirror/util.js
project/static/js/models.js [new file with mode: 0644]
project/static/js/views/button_toolbar.js [new file with mode: 0644]
project/static/js/views/editor.js [new file with mode: 0644]
project/static/js/views/html.js [new file with mode: 0644]
project/static/js/views/panel_container.js [new file with mode: 0644]
project/static/js/views/split.js [new file with mode: 0644]
project/static/js/views/view.js [new file with mode: 0644]
project/static/js/views/xml.js [new file with mode: 0644]
project/templates/base.html
project/templates/explorer/editor.html

index 44bd62e..a3857e6 100644 (file)
@@ -1,6 +1,6 @@
 body {
     margin: 0;
-    font: 10pt Helvetica, Verdana, sans-serif;
+    font: 12px Helvetica, Verdana, sans-serif;
     overflow: hidden;
     background: #AAA;
 }
@@ -35,7 +35,6 @@ body {
     top: 2.4em; left: 0px; right: 0px; bottom: 0px;
     overflow: auto;    
     background-color: white;
-    padding: 0.2em 1em;
 }
 
 /*
@@ -108,8 +107,8 @@ label {
 /* ========== */
 
 #panels {
-    position: absolute;
-    bottom: 0px; left: 0px; right: 0px; top: 0px;
+    height: 100%;
+    width: 100%;
 }
 
 .panel-wrap {
@@ -344,13 +343,75 @@ text#commit-dialog-message {
     margin: 0.5em;
 }
 
-.CodeMirror-line-numbers
-{
-    text-align: right;
-    padding-top: 0.4em;
-    padding-right: 2px;
-    width: 28px;
-    font-size: 10pt;
-    background: black;
-    color: white;
-}
\ No newline at end of file
+/* ======= */
+/* = New = */
+/* ======= */
+#splitview {
+    width: 100%;
+    height: 100%;
+    padding: 0;
+    margin: 0;
+}
+
+.splitview-splitbar {
+    width: 5px;
+    border-left: 1px solid #999;
+    border-right: 1px solid #999;
+    height: 100%;
+    background-color: #CCC;
+    z-index: 100;
+}
+
+.splitview-overlay {
+    z-index: 90;
+    background: #FFF;
+    opacity: 0.5;
+}
+
+.panel-container {
+    height: 100%;
+    position: relative;
+}
+
+.content-view {
+    position: absolute;
+    top: 20px;
+    right: 0;
+    bottom: 0;
+    left: 0;
+    overflow: none;
+}
+
+.panel-container select {
+    z-index: 1100;
+}
+
+.xmlview {
+    height: 100%;
+}
+
+.view-overlay {
+    z-index: 1000;
+    background: #FFF;
+    opacity: 0.8;
+    text-align: center;
+    text-valign: center;
+}
+
+.view-overlay p {
+    display: block;
+    position: relative;
+    top: auto;
+    bottom: auto;
+    height: 40px;
+}
+
+.buttontoolbarview {
+    display: block;
+    background-color: #CCC;
+}
+
+.buttontoolbarview a {
+    color: #000;
+    text-decoration: none;
+}
diff --git a/project/static/js/app.js b/project/static/js/app.js
new file mode 100644 (file)
index 0000000..e8875c9
--- /dev/null
@@ -0,0 +1,177 @@
+/*global Class*/
+var editor;
+var panel_hooks;
+
+
+(function(){
+  // Classes
+  var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
+  this.Class = function(){};
+  Class.extend = function(prop) {
+    var _super = this.prototype;
+    initializing = true;
+    var prototype = new this();
+    initializing = false;
+    for (var name in prop) {
+      prototype[name] = typeof prop[name] == "function" &&
+        typeof _super[name] == "function" && fnTest.test(prop[name]) ?
+        (function(name, fn){
+          return function() {
+            var tmp = this._super;
+            this._super = _super[name];
+            var ret = fn.apply(this, arguments);       
+            this._super = tmp;           
+            return ret;
+          };
+        })(name, prop[name]) :
+        prop[name];
+    }   
+    function Class() {
+      if ( !initializing && this.init )
+        this.init.apply(this, arguments);
+    }
+    Class.prototype = prototype;
+    Class.constructor = Class;
+    Class.extend = arguments.callee;   
+    return Class;
+  };
+  
+  // Templates
+  var cache = {};
+
+  this.render_template = function render_template(str, data){
+    // Figure out if we're getting a template, or if we need to
+    // load the template - and be sure to cache the result.
+    var fn = !/^[\d\s-_]/.test(str) ?
+      cache[str] = cache[str] ||
+        render_template(document.getElementById(str).innerHTML) :
+
+      // Generate a reusable function that will serve as a template
+      // generator (and which will be cached).
+      new Function("obj",
+        "var p=[],print=function(){p.push.apply(p,arguments);};" +
+
+        // Introduce the data as local variables using with(){}
+        "with(obj){p.push('" +
+
+        // Convert the template into pure JavaScript
+        str
+          .replace(/[\r\t\n]/g, " ")
+          .split("<%").join("\t")
+          .replace(/((^|%>)[^\t]*)'/g, "$1\r")
+          .replace(/\t=(.*?)%>/g, "',$1,'")
+          .split("\t").join("');")
+          .split("%>").join("p.push('")
+          .split("\r").join("\\'")
+      + "');}return p.join('');");
+
+      // Provide some basic currying to the user
+    return data ? fn( data ) : fn;
+  };
+})();
+
+
+(function() {
+  var slice = Array.prototype.slice;
+  
+  function update(array, args) {
+    var arrayLength = array.length, length = args.length;
+    while (length--) array[arrayLength + length] = args[length];
+    return array;
+  };
+  
+  function merge(array, args) {
+    array = slice.call(array, 0);
+    return update(array, args);
+  };
+  
+  Function.prototype.bind = function(context) {
+    if (arguments.length < 2 && typeof arguments[0] === 'undefined') {
+      return this;
+    } 
+    var __method = this;
+    var args = slice.call(arguments, 1);
+    return function() {
+      var a = merge(args, arguments);
+      return __method.apply(context, a);
+    }
+  }
+  
+})();
+
+
+var Editor = Editor || {};
+
+// Obiekt implementujący wzorzec KVC/KVO
+Editor.Object = Class.extend({
+  _className: 'Editor.Object',
+  _observers: {},
+  _guid: null,
+  
+  init: function() {
+    this._observers = {};
+  },
+  
+  description: function() {
+    return this._className + '(guid = ' + this.guid() + ')';
+  },
+  
+  addObserver: function(observer, property, callback) {
+    // console.log('Add observer', observer.description(), 'to', this.description(), '[', property, ']');
+    if (!this._observers[property]) {
+      this._observers[property] = {}
+    }
+    this._observers[property][observer.guid()] = callback;
+    return this;
+  },
+  
+  removeObserver: function(observer, property) {
+    if (!property) {
+      for (var property in this._observers) {
+        this.removeObserver(observer, property)
+      }
+    } else {
+      // console.log('Remove observer', observer.description(), 'from', this.description(), '[', property, ']');
+      delete this._observers[property][observer.guid()];
+    }
+    return this;
+  },
+  
+  notifyObservers: function(property) {
+    var currentValue = this[property];
+    for (var guid in this._observers[property]) {
+      // console.log(this._observers[property][guid]);
+      // console.log('Notifying', guid, 'of', this.description(), '[', property, ']');
+      this._observers[property][guid](property, currentValue, this);
+    }
+    return this;
+  },
+  
+  guid: function() {
+    if (!this._guid) {
+      this._guid = ('editor-' + Editor.Object._lastGuid++);
+    }
+    return this._guid;
+  },
+  
+  get: function(property) {
+    return this[property];
+  },
+  
+  set: function(property, value) {
+    if (this[property] != value) {
+      this[property] = value;
+      this.notifyObservers(property);
+    }
+    return this;
+  },
+  
+  dispose: function() {
+    delete this._observers;
+  }
+});
+
+Editor.Object._lastGuid = 0;
+
+
+var panels = [];
index 79b7fa9..6918f9e 100644 (file)
@@ -320,30 +320,30 @@ Editor.prototype.loadConfig = function() {
 };
 
 Editor.prototype.loadPanelOptions = function() {
-    var self = this;
-    var totalWidth = 0;
-    
-    $('.panel-wrap', self.rootDiv).each(function(index) {
-        var panelWidth = self.fileOptions.panels[index].ratio * self.rootDiv.width();
-        if ($(this).hasClass('last-panel')) {
-            $(this).css({
-                left: totalWidth,
-                right: 0
-            });
-        } else {
-            $(this).css({
-                left: totalWidth,
-                width: panelWidth
-            });
-            totalWidth += panelWidth;               
-        }
-        $.log('panel:', this, $(this).css('left'));
-        $('.panel-toolbar option', this).each(function() {
-            if ($(this).attr('p:panel-name') == self.fileOptions.panels[index].name) {
-                $(this).parent('select').val($(this).attr('value'));
-            }
-        });
-    });   
+    // var self = this;
+    // var totalWidth = 0;
+    // 
+    // $('.panel-wrap', self.rootDiv).each(function(index) {
+    //     var panelWidth = self.fileOptions.panels[index].ratio * self.rootDiv.width();
+    //     if ($(this).hasClass('last-panel')) {
+    //         $(this).css({
+    //             left: totalWidth,
+    //             right: 0
+    //         });
+    //     } else {
+    //         $(this).css({
+    //             left: totalWidth,
+    //             width: panelWidth
+    //         });
+    //         totalWidth += panelWidth;               
+    //     }
+    //     $.log('panel:', this, $(this).css('left'));
+    //     $('.panel-toolbar option', this).each(function() {
+    //         if ($(this).attr('p:panel-name') == self.fileOptions.panels[index].name) {
+    //             $(this).parent('select').val($(this).attr('value'));
+    //         }
+    //     });
+    // });   
 };
 
 Editor.prototype.savePanelOptions = function() {
index f66d977..c8d47f6 100755 (executable)
  * UI related Editor methods\r
  */\r
 Editor.prototype.setupUI = function() {\r
-    // set up the UI visually and attach callbacks\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
+//     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
@@ -187,8 +187,6 @@ Editor.prototype.setupUI = function() {
         onShow: $.fbind(self, self.loadSplitDialog)\r
     }).\r
     jqmAddClose('button.dialog-close-button');\r
-\r
-// $('#split-dialog').   \r
 }\r
 \r
 Editor.prototype.loadRelatedIssues = function(hash)\r
index 25da6f7..f63ed07 100644 (file)
@@ -55,34 +55,20 @@ var CodeMirror = (function(){
 
   function wrapLineNumberDiv(place) {
     return function(node) {
-        
       var container = document.createElement("DIV"),
           nums = document.createElement("DIV"),
           scroller = document.createElement("DIV");
-      
+      container.style.position = "relative";
       nums.style.position = "absolute";
-      nums.style.height = "100%";      
+      nums.style.height = "100%";
       if (nums.style.setExpression) {
         try {nums.style.setExpression("height", "this.previousSibling.offsetHeight + 'px'");}
         catch(e) {} // Seems to throw 'Not Implemented' on some IE8 versions
       }
       nums.style.top = "0px";
-      nums.style.overflow = "hidden";      
-
-      container.style.position = "absolute";
-      container.style.left = "0px";
-      container.style.right = "0px";
-      container.style.bottom = "0px";
-      container.style.top = "0px";
-
-      node.style.position = "absolute";
-      node.style.top = "0px";
-      node.style.right = "0px";
-      node.style.bottom = "0px";
-      node.style.left = "16px"
-
+      nums.style.overflow = "hidden";
       place(container);
-      container.appendChild(node);     
+      container.appendChild(node);
       container.appendChild(nums);
       scroller.className = "CodeMirror-line-numbers";
       nums.appendChild(scroller);
@@ -91,22 +77,19 @@ var CodeMirror = (function(){
 
   function applyLineNumbers(frame) {
     var win = frame.contentWindow, doc = win.document,
-        nums = frame.parentNode.nextSibling, scroller = nums.firstChild;
+        nums = frame.nextSibling, scroller = nums.firstChild;
 
     var nextNum = 1, barWidth = null;
     function sizeBar() {
-      if (!frame.offsetWidth || !win.Editor) {
-        for (var cur = frame; cur.parentNode; cur = cur.parentNode) {
-          if (cur != document) {
-            clearInterval(sizeInterval);
-            return;
-          }
-        }
+      for (var root = frame; root.parentNode; root = root.parentNode);
+      if (root != document || !win.Editor) {
+        clearInterval(sizeInterval);
+        return;
       }
 
       if (nums.offsetWidth != barWidth) {
         barWidth = nums.offsetWidth;
-        // nums.style.left = "-" + (frame.parentNode.style.marginLeft = barWidth + "px");
+        nums.style.left = "-" + (frame.parentNode.style.marginLeft = barWidth + "px");
       }
     }
     function update() {
@@ -139,8 +122,8 @@ var CodeMirror = (function(){
     frame.frameBorder = 0;
     frame.src = "javascript:false;";
     frame.style.border = "0";
-    frame.style.width = "100%";
-    frame.style.height = "100%";
+    frame.style.width = options.width;
+    frame.style.height = options.height;
     // display: block occasionally suppresses some Firefox bugs, so we
     // always add it, redundant as it sounds.
     frame.style.display = "block";
@@ -149,44 +132,9 @@ var CodeMirror = (function(){
       var node = place;
       place = function(n){node.appendChild(n);};
     }
-
-    var iframe_container = document.createElement("DIV");
-    iframe_container.appendChild(frame);
-
-    var content_wrapper = document.createElement("DIV");
-    content_wrapper.appendChild(iframe_container);
-    content_wrapper.style.position = 'relative';
-    content_wrapper.className = 'CodeMirror-content-wrapper';
-    content_wrapper.style.width = options.width;
-    content_wrapper.style.height = options.height;
-    
-    iframe_container.style.position = 'absolute';
-    iframe_container.style.top = '0px';
-    iframe_container.style.right = '0px';
-    iframe_container.style.bottom = '0px';
-    iframe_container.style.left = '0px';
-    
-    if (options.lineNumbers) {
-       iframe_container.style.left = '28px';
-       
-       var nums = document.createElement("DIV"),
-          scroller = document.createElement("DIV");
-
-        nums.style.position = "absolute";
-        nums.style.height = "100%";
-
-        nums.style.top = "0px";
-        nums.style.left = "0px";
-        nums.style.overflow = "hidden";
-
-        scroller.className = "CodeMirror-line-numbers";
-        nums.appendChild(scroller);
-        content_wrapper.appendChild(nums);
-
-        iframe_container.style.right = nums.width;        
-    }   
     
-    place(content_wrapper);
+    if (options.lineNumbers) place = wrapLineNumberDiv(place);
+    place(frame);
 
     // Link back to this object, so that the editor can fetch options
     // and add a reference to itself.
@@ -225,10 +173,14 @@ var CodeMirror = (function(){
 
     getCode: function() {return this.editor.getCode();},
     setCode: function(code) {this.editor.importCode(code);},
-    selection: function() {return this.editor.selectedText();},
+    selection: function() {this.focusIfIE(); return this.editor.selectedText();},
     reindent: function() {this.editor.reindent();},
-    reindentSelection: function() {this.editor.reindentSelection(null);},
+    reindentSelection: function() {this.focusIfIE(); this.editor.reindentSelection(null);},
 
+    focusIfIE: function() {
+      // in IE, a lot of selection-related functionality only works when the frame is focused
+      if (this.win.select.ie_selection) this.focus();
+    },
     focus: function() {
       this.win.focus();
       if (this.editor.selectionSnapshot) // IE hack
@@ -256,10 +208,7 @@ var CodeMirror = (function(){
 
     setParser: function(name) {this.editor.setParser(name);},
 
-    cursorPosition: function(start) {
-      if (this.win.select.ie_selection) this.focus();
-      return this.editor.cursorPosition(start);
-    },
+    cursorPosition: function(start) {this.focusIfIE(); return this.editor.cursorPosition(start);},
     firstLine: function() {return this.editor.firstLine();},
     lastLine: function() {return this.editor.lastLine();},
     nextLine: function(line) {return this.editor.nextLine(line);},
index 757d3ea..b7c53c7 100644 (file)
@@ -80,13 +80,13 @@ var Editor = (function(){
         if (text.length) leaving = false;
         result.push(node);
       }
-      else if (node.nodeName == "BR" && node.childNodes.length == 0) {
+      else if (isBR(node) && node.childNodes.length == 0) {
         leaving = true;
         result.push(node);
       }
       else {
         forEach(node.childNodes, simplifyNode);
-        if (!leaving && newlineElements.hasOwnProperty(node.nodeName)) {
+        if (!leaving && newlineElements.hasOwnProperty(node.nodeName.toUpperCase())) {
           leaving = true;
           if (!atEnd || !top)
             result.push(doc.createElement("BR"));
@@ -175,7 +175,7 @@ var Editor = (function(){
         nodeQueue.push(node);
         return yield(node.currentText, c);
       }
-      else if (node.nodeName == "BR") {
+      else if (isBR(node)) {
         nodeQueue.push(node);
         return yield("\n", c);
       }
@@ -195,47 +195,25 @@ var Editor = (function(){
 
   // Determine the text size of a processed node.
   function nodeSize(node) {
-    if (node.nodeName == "BR")
-      return 1;
-    else
-      return node.currentText.length;
+    return isBR(node) ? 1 : node.currentText.length;
   }
 
   // Search backwards through the top-level nodes until the next BR or
   // the start of the frame.
   function startOfLine(node) {
-    while (node && node.nodeName != "BR") node = node.previousSibling;
+    while (node && !isBR(node)) node = node.previousSibling;
     return node;
   }
   function endOfLine(node, container) {
     if (!node) node = container.firstChild;
-    else if (node.nodeName == "BR") node = node.nextSibling;
+    else if (isBR(node)) node = node.nextSibling;
 
-    while (node && node.nodeName != "BR") node = node.nextSibling;
+    while (node && !isBR(node)) node = node.nextSibling;
     return node;
   }
 
   function time() {return new Date().getTime();}
 
-  // Replace all DOM nodes in the current selection with new ones.
-  // Needed to prevent issues in IE where the old DOM nodes can be
-  // pasted back into the document, still holding their old undo
-  // information.
-  function scrubPasted(container, start, start2) {
-    var end = select.selectionTopNode(container, true),
-        doc = container.ownerDocument;
-    if (start != null && start.parentNode != container) start = start2;
-    if (start === false) start = null;
-    if (start == end || !end || !container.firstChild) return;
-
-    var clear = traverseDOM(start ? start.nextSibling : container.firstChild);
-    while (end.parentNode == container) try{clear.next();}catch(e){break;}
-    forEach(clear.nodes, function(node) {
-      var newNode = node.nodeName == "BR" ? doc.createElement("BR") : makePartSpan(node.currentText, doc);
-      container.replaceChild(newNode, node);
-    });
-  }
-
   // Client interface for searching the content of the editor. Create
   // these by calling CodeMirror.getSearchCursor. To use, call
   // findNext on the resulting object -- this returns a boolean
@@ -385,8 +363,6 @@ var Editor = (function(){
     this.dirty = [];
     if (options.content)
       this.importCode(options.content);
-    else // FF acts weird when the editable document is completely empty
-      container.appendChild(this.doc.createElement("BR"));
 
     if (!options.readOnly) {
       if (options.continuousScanning !== false) {
@@ -426,23 +402,23 @@ var Editor = (function(){
 
       function cursorActivity() {self.cursorActivity(false);}
       addEventHandler(document.body, "mouseup", cursorActivity);
+      addEventHandler(document.body, "cut", cursorActivity);
+
       addEventHandler(document.body, "paste", function(event) {
         cursorActivity();
-        if (internetExplorer) {
-          var text = null;
-          try {text = window.clipboardData.getData("Text");}catch(e){}
-          if (text != null) {
-            self.replaceSelection(text);
-            event.stop();
-          }
-          else {
-            var start = select.selectionTopNode(self.container, true),
-                start2 = start && start.previousSibling;
-            setTimeout(function(){scrubPasted(self.container, start, start2);}, 0);
-          }
+        var text = null;
+        try {
+          var clipboardData = event.clipboardData || window.clipboardData;
+          if (clipboardData) text = clipboardData.getData('Text');
+        }
+        catch(e) {}
+        if (text !== null) {
+          self.replaceSelection(text);
+          event.stop();
         }
       });
-      addEventHandler(document.body, "cut", cursorActivity);
+
+      addEventHandler(document.body, "beforepaste", method(this, "reroutePasteEvent"));
 
       if (this.options.autoMatchParens)
         addEventHandler(document.body, "click", method(this, "scheduleParenBlink"));
@@ -525,7 +501,7 @@ var Editor = (function(){
       this.checkLine(line);
       var accum = [];
       for (line = line ? line.nextSibling : this.container.firstChild;
-           line && line.nodeName != "BR"; line = line.nextSibling)
+           line && !isBR(line); line = line.nextSibling)
         accum.push(nodeText(line));
       return cleanText(accum.join(""));
     },
@@ -550,7 +526,7 @@ var Editor = (function(){
             before = cur;
             break;
           }
-          var text = (cur.innerText || cur.textContent || cur.nodeValue || "");
+          var text = nodeText(cur);
           if (text.length > position) {
             before = cur.nextSibling;
             content = text.slice(0, position) + content + text.slice(position);
@@ -592,12 +568,38 @@ var Editor = (function(){
     // Replace the selection with another piece of text.
     replaceSelection: function(text) {
       this.history.commit();
+
       var start = select.cursorPos(this.container, true),
           end = select.cursorPos(this.container, false);
       if (!start || !end) return;
 
       end = this.replaceRange(start, end, text);
-      select.setCursorPos(this.container, start, end);
+      select.setCursorPos(this.container, end);
+      webkitLastLineHack(this.container);
+    },
+
+    reroutePasteEvent: function() {
+      if (this.capturingPaste || window.opera) return;
+      this.capturingPaste = true;
+      var te = parent.document.createElement("TEXTAREA");
+      te.style.position = "absolute";
+      te.style.left = "-500px";
+      te.style.width = "10px";
+      te.style.top = nodeTop(frameElement) + "px";
+      parent.document.body.appendChild(te);
+      parent.focus();
+      te.focus();
+
+      var self = this;
+      this.parent.setTimeout(function() {
+        self.capturingPaste = false;
+        self.win.focus();
+        if (self.selectionSnapshot) // IE hack
+          self.win.select.selectCoords(self.win, self.selectionSnapshot);
+        var text = te.value;
+        if (text) self.replaceSelection(text);
+        removeElement(te);
+      }, 10);
     },
 
     replaceRange: function(from, to, text) {
@@ -656,7 +658,7 @@ var Editor = (function(){
     // Intercept enter and tab, and assign their new functions.
     keyDown: function(event) {
       if (this.frozen == "leave") this.frozen = null;
-      if (this.frozen && (!this.keyFilter || this.keyFilter(event))) {
+      if (this.frozen && (!this.keyFilter || this.keyFilter(event.keyCode))) {
         event.stop();
         this.frozen(event);
         return;
@@ -669,7 +671,7 @@ var Editor = (function(){
       if (this.options.autoMatchParens)
         this.scheduleParenBlink();
 
-      // The variouschecks for !altKey are there because AltGr sets both
+      // The various checks for !altKey are there because AltGr sets both
       // ctrlKey and altKey to true, and should not be recognised as
       // Control.
       if (code == 13) { // enter
@@ -734,12 +736,15 @@ var Editor = (function(){
       // keydown event does not prevent the associated keypress event
       // from happening, so we have to cancel enter and tab again
       // here.
-      if ((this.frozen && (!this.keyFilter || this.keyFilter(event))) ||
+      if ((this.frozen && (!this.keyFilter || this.keyFilter(event.keyCode))) ||
           event.code == 13 || (event.code == 9 && this.options.tabMode != "default") ||
           (event.keyCode == 32 && event.shiftKey && this.options.tabMode == "default"))
         event.stop();
       else if (electric && electric.indexOf(event.character) != -1)
         this.parent.setTimeout(function(){self.indentAtCursor(null);}, 0);
+      else if ((event.character == "v" || event.character == "V")
+               && (event.ctrlKey || event.metaKey) && !event.altKey) // ctrl-V
+        this.reroutePasteEvent();
     },
 
     // Mark the node at the cursor dirty when a non-safe key is
@@ -833,10 +838,10 @@ var Editor = (function(){
 
     home: function() {
       var cur = select.selectionTopNode(this.container, true), start = cur;
-      if (cur === false || !(!cur || cur.isPart || cur.nodeName == "BR") || !this.container.firstChild)
+      if (cur === false || !(!cur || cur.isPart || isBR(cur)) || !this.container.firstChild)
         return false;
 
-      while (cur && cur.nodeName != "BR") cur = cur.previousSibling;
+      while (cur && !isBR(cur)) cur = cur.previousSibling;
       var next = cur ? cur.nextSibling : this.container.firstChild;
       if (next && next != start && next.isPart && hasClass(next, "whitespace"))
         select.focusAfterNode(next, this.container);
@@ -891,7 +896,7 @@ var Editor = (function(){
       function tryFindMatch() {
         var stack = [], ch, ok = true;;
         for (var runner = cursor; runner; runner = dir ? runner.nextSibling : runner.previousSibling) {
-          if (runner.className == className && runner.nodeName == "SPAN" && (ch = paren(runner))) {
+          if (runner.className == className && isSpan(runner) && (ch = paren(runner))) {
             if (forward(ch) == dir)
               stack.push(ch);
             else if (!stack.length)
@@ -900,7 +905,7 @@ var Editor = (function(){
               ok = false;
             if (!stack.length) break;
           }
-          else if (runner.dirty || runner.nodeName != "SPAN" && runner.nodeName != "BR") {
+          else if (runner.dirty || !isSpan(runner) && !isBR(runner)) {
             return {node: runner, status: "dirty"};
           }
         }
@@ -958,7 +963,7 @@ var Editor = (function(){
     // selection.
     indentRegion: function(start, end, direction) {
       var current = (start = startOfLine(start)), before = start && startOfLine(start.previousSibling);
-      if (end.nodeName != "BR") end = endOfLine(end, this.container);
+      if (!isBR(end)) end = endOfLine(end, this.container);
 
       do {
         var next = endOfLine(current, this.container);
@@ -1054,7 +1059,7 @@ var Editor = (function(){
 
       if (!this.options.readOnly) select.markSelection(this.win);
       var start, endTime = force ? null : time() + this.options.passTime;
-      while (time() < endTime && (start = this.getDirtyNode())) {
+      while ((time() < endTime || force) && (start = this.getDirtyNode())) {
         var result = this.highlight(start, endTime);
         if (result && result.node && result.dirty)
           this.addDirtyNode(result.node);
@@ -1117,7 +1122,7 @@ var Editor = (function(){
       // Backtrack to the first node before from that has a partial
       // parse stored.
       while (from && (!from.parserFromHere || from.dirty)) {
-        if (maxBacktrack != null && from.nodeName == "BR" && (--maxBacktrack) < 0)
+        if (maxBacktrack != null && isBR(from) && (--maxBacktrack) < 0)
           return false;
         from = from.previousSibling;
       }
@@ -1148,13 +1153,15 @@ var Editor = (function(){
 
       function maybeTouch(node) {
         if (node) {
-          if (lineDirty || node.nextSibling != node.oldNextSibling)
+          var old = node.oldNextSibling;
+          if (lineDirty || old === undefined || node.nextSibling != old)
             self.history.touch(node);
           node.oldNextSibling = node.nextSibling;
         }
         else {
-          if (lineDirty || self.container.firstChild != self.container.oldFirstChild)
-            self.history.touch(node);
+          var old = self.container.oldFirstChild;
+          if (lineDirty || old === undefined || self.container.firstChild != old)
+            self.history.touch(null);
           self.container.oldFirstChild = self.container.firstChild;
         }
       }
@@ -1196,7 +1203,7 @@ var Editor = (function(){
           // Allow empty nodes when they are alone on a line, needed
           // for the FF cursor bug workaround (see select.js,
           // insertNewlineAtCursor).
-          while (part && part.nodeName == "SPAN" && part.currentText == "") {
+          while (part && isSpan(part) && part.currentText == "") {
             var old = part;
             this.remove();
             part = this.get();
@@ -1218,7 +1225,7 @@ var Editor = (function(){
         if (token.value == "\n"){
           // The idea of the two streams actually staying synchronized
           // is such a long shot that we explicitly check.
-          if (part.nodeName != "BR")
+          if (!isBR(part))
             throw "Parser out of sync. Expected BR.";
 
           if (part.dirty || !part.indentation) lineDirty = true;
@@ -1246,7 +1253,7 @@ var Editor = (function(){
           parts.next();
         }
         else {
-          if (part.nodeName != "SPAN")
+          if (!isSpan(part))
             throw "Parser out of sync. Expected SPAN.";
           if (part.dirty)
             lineDirty = true;
index 002004e..7746240 100644 (file)
@@ -52,7 +52,7 @@ var select = {};
     while (pos && pos.offsetParent) {
       y += pos.offsetTop;
       // Don't count X offset for <br> nodes
-      if (pos.nodeName != "BR")
+      if (!isBR(pos))
         x += pos.offsetLeft;
       pos = pos.offsetParent;
     }
@@ -238,7 +238,7 @@ var select = {};
       // Move the start of a range to the start of a node,
       // compensating for the fact that you can't call
       // moveToElementText with text nodes.
-      function moveToNodeStart(range, node) {        
+      function moveToNodeStart(range, node) {
         if (node.nodeType == 3) {
           var count = 0, cur = node.previousSibling;
           while (cur && cur.nodeType == 3) {
@@ -247,22 +247,26 @@ var select = {};
           }
           if (cur) {
             try{range.moveToElementText(cur);}
-            catch(e){alert(cur + " " + cur.nodeType + " " + (cur && cur.outerHTML));}
+            catch(e){return false;}
             range.collapse(false);
           }
           else range.moveToElementText(node.parentNode);
           if (count) range.move("character", count);
         }
-        else try{range.moveToElementText(node);} catch(e) {};
+        else {
+          try{range.moveToElementText(node);}
+          catch(e){return false;}
+        }
+        return true;
       }
 
       // Do a binary search through the container object, comparing
       // the start of each node to the selection
-      var start = 0, end = container.childNodes.length;
-      while (start != end) {
+      var start = 0, end = container.childNodes.length - 1;
+      while (start < end) {
         var middle = Math.ceil((end + start) / 2), node = container.childNodes[middle];
         if (!node) return false; // Don't ask. IE6 manages this sometimes.
-        moveToNodeStart(range2, node);
+        if (!moveToNodeStart(range2, node)) return false;
         if (range.compareEndPoints("StartToStart", range2) == 1)
           start = middle;
         else
@@ -314,7 +318,7 @@ var select = {};
       if (!selection) return null;
 
       var topNode = select.selectionTopNode(container, start);
-      while (topNode && topNode.nodeName != "BR")
+      while (topNode && !isBR(topNode))
         topNode = topNode.previousSibling;
 
       var range = selection.createRange(), range2 = range.duplicate();
@@ -407,7 +411,7 @@ var select = {};
       // ancestors with a suitable offset. This goes down the DOM tree
       // until a 'leaf' is reached (or is it *up* the DOM tree?).
       function normalize(point){
-        while (point.node.nodeType != 3 && point.node.nodeName != "BR") {
+        while (point.node.nodeType != 3 && !isBR(point.node)) {
           var newNode = point.node.childNodes[point.offset] || point.node.nextSibling;
           point.offset = 0;
           while (!newNode && point.node.parentNode) {
@@ -425,8 +429,9 @@ var select = {};
     };
 
     select.selectMarked = function () {
-      if (!currentSelection || !currentSelection.changed) return;
-      var win = currentSelection.window, range = win.document.createRange();
+      var cs = currentSelection;
+      if (!(cs && (cs.changed || (webkit && cs.start.node == cs.end.node)))) return;
+      var win = cs.window, range = win.document.createRange();
 
       function setPoint(point, which) {
         if (point.node) {
@@ -442,8 +447,8 @@ var select = {};
         }
       }
 
-      setPoint(currentSelection.end, "End");
-      setPoint(currentSelection.start, "Start");
+      setPoint(cs.end, "End");
+      setPoint(cs.start, "Start");
       selectRange(range, win);
     };
 
@@ -471,7 +476,7 @@ var select = {};
       var offset = start ? range.startOffset : range.endOffset;
       // Work around (yet another) bug in Opera's selection model.
       if (window.opera && !start && range.endContainer == container && range.endOffset == range.startOffset + 1 &&
-          container.childNodes[range.startOffset] && container.childNodes[range.startOffset].nodeName == "BR")
+          container.childNodes[range.startOffset] && isBR(container.childNodes[range.startOffset]))
         offset--;
 
       // For text nodes, we look at the node itself if the cursor is
@@ -486,7 +491,7 @@ var select = {};
       // Occasionally, browsers will return the HTML node as
       // selection. If the offset is 0, we take the start of the frame
       // ('after null'), otherwise, we take the last node.
-      else if (node.nodeName == "HTML") {
+      else if (node.nodeName.toUpperCase() == "HTML") {
         return (offset == 1 ? null : container.lastChild);
       }
       // If the given node is our 'container', we just look up the
@@ -557,7 +562,7 @@ var select = {};
       if (!range) return;
 
       var topNode = select.selectionTopNode(container, start);
-      while (topNode && topNode.nodeName != "BR")
+      while (topNode && !isBR(topNode))
         topNode = topNode.previousSibling;
 
       range = range.cloneRange();
index 6d9355f..8c1c042 100644 (file)
@@ -7,13 +7,13 @@
  * An easy mistake to make is to let a StopIteration exception finish
  * the token stream while there are still characters pending in the
  * string stream (hitting the end of the buffer while parsing a
- * token). To make it easier to detect such errors, the strings throw
- * an exception when this happens.
+ * token). To make it easier to detect such errors, the stringstreams
+ * throw an exception when this happens.
  */
 
-// Make a string stream out of an iterator that returns strings. This
-// is applied to the result of traverseDOM (see codemirror.js), and
-// the resulting stream is fed to the parser.
+// Make a stringstream stream out of an iterator that returns strings.
+// This is applied to the result of traverseDOM (see codemirror.js),
+// and the resulting stream is fed to the parser.
 window.stringStream = function(source){
   // String that's currently being iterated over.
   var current = "";
index 1930cfb..97daf59 100644 (file)
@@ -146,7 +146,7 @@ History.prototype = {
   // Commit unless there are pending dirty nodes.
   tryCommit: function() {
     if (!window.History) return; // Stop when frame has been unloaded
-    if (this.editor.highlightDirty()) this.commit();
+    if (this.editor.highlightDirty()) this.commit(true);
     else this.scheduleCommit();
   },
 
@@ -250,7 +250,7 @@ History.prototype = {
     function buildLine(node) {
       var text = [];
       for (var cur = node ? node.nextSibling : self.container.firstChild;
-           cur && cur.nodeName != "BR"; cur = cur.nextSibling)
+           cur && !isBR(cur); cur = cur.nextSibling)
         if (cur.currentText) text.push(cur.currentText);
       return {from: node, to: cur, text: cleanText(text.join(""))};
     }
@@ -275,7 +275,7 @@ History.prototype = {
     // Get the BR element after/before the given node.
     function nextBR(node, dir) {
       var link = dir + "Sibling", search = node[link];
-      while (search && search.nodeName != "BR")
+      while (search && !isBR(search))
         search = search[link];
       return search;
     }
index 61f927d..0cd91d4 100644 (file)
@@ -114,3 +114,21 @@ function addEventHandler(node, type, handler, removeFunc) {
 function nodeText(node) {
   return node.textContent || node.innerText || node.nodeValue || "";
 }
+
+function nodeTop(node) {
+  var top = 0;
+  while (node.offsetParent) {
+    top += node.offsetTop;
+    node = node.offsetParent;
+  }
+  return top;
+}
+
+function isBR(node) {
+  var nn = node.nodeName;
+  return nn == "BR" || nn == "br";
+}
+function isSpan(node) {
+  var nn = node.nodeName;
+  return nn == "SPAN" || nn == "span";
+}
diff --git a/project/static/js/models.js b/project/static/js/models.js
new file mode 100644 (file)
index 0000000..9542eac
--- /dev/null
@@ -0,0 +1,242 @@
+/*globals Editor fileId SplitView PanelContainerView EditorView*/
+var documentsUrl = '/api/documents/';
+
+
+Editor.Model = Editor.Object.extend({
+  synced: false,
+  data: null
+});
+
+
+Editor.ToolbarButtonsModel = Editor.Model.extend({
+  _className: 'Editor.ToolbarButtonsModel',
+  serverURL: '/api/toolbar/buttons',
+  buttons: {},
+  
+  init: function() {
+    this._super();
+  },
+  
+  load: function() {
+    if (!this.get('buttons').length) {
+      $.ajax({
+        url: this.serverURL,
+        dataType: 'json',
+        success: this.loadSucceeded.bind(this)
+      });
+    }
+  },
+  
+  loadSucceeded: function(data) {
+    this.set('buttons', data);
+  }
+});
+
+
+// Stany modelu:
+//
+// empty -> loading -> synced -> unsynced -> loading
+//                           \
+//                            -> dirty -> updating -> updated -> synced
+//
+Editor.XMLModel = Editor.Model.extend({
+  _className: 'Editor.XMLModel',
+  serverURL: null,
+  data: '',
+  state: 'empty',
+  
+  init: function(serverURL) {
+    this._super();
+    this.set('state', 'empty');
+    this.serverURL = serverURL;
+    this.toolbarButtonsModel = new Editor.ToolbarButtonsModel();
+    this.addObserver(this, 'data', this.dataChanged.bind(this));
+  },
+  
+  load: function() {
+    if (this.get('state') == 'empty') {
+      this.set('state', 'loading');
+      $.ajax({
+        url: this.serverURL,
+        dataType: 'text',
+        success: this.loadingSucceeded.bind(this)
+      });
+      return true;
+    }
+    return false;
+  },
+  
+  update: function(message) {
+    if (this.get('state') == 'dirty') {
+      this.set('state', 'updating');
+      
+      var payload = {
+        contents: this.get('data')
+      };
+      if (message) {
+        payload.message = message;
+      }
+      
+      $.ajax({
+        url: this.serverURL,
+        type: 'put',
+        dataType: 'json',
+        data: payload,
+        success: this.updatingSucceeded.bind(this),
+        error: this.updatingFailed.bind(this)
+      });
+      return true;
+    }
+    return false;
+  },
+  
+  updatingSucceeded: function() {
+    if (this.get('state') != 'updating') {
+      alert('erroneous state:', this.get('state'));
+    }
+    this.set('state', 'updated');
+  },
+  
+  updatingFailed: function() {
+    if (this.get('state') != 'updating') {
+      alert('erroneous state:', this.get('state'));
+    }
+    this.set('state', 'dirty');
+  },
+  
+  set: function(property, value) {
+    if (property == 'state') {
+      console.log(this.description(), ':', property, '=', value);
+    }
+    return this._super(property, value);
+  },
+  
+  dataChanged: function(property, value) {
+    if (this.get('state') == 'synced') {
+      this.set('state', 'dirty');
+    }
+  },
+  
+  loadingSucceeded: function(data) {
+    if (this.get('state') != 'loading') {
+      alert('erroneous state:', this.get('state'));
+    }
+    this.set('data', data);
+    this.set('state', 'synced');
+  },
+  
+  dispose: function() {
+    this.removeObserver(this);
+    this._super();
+  }
+});
+
+
+Editor.HTMLModel = Editor.Model.extend({
+  _className: 'Editor.HTMLModel',
+  serverURL: null,
+  data: '',
+  state: 'empty',
+  
+  init: function(serverURL) {
+    this._super();
+    this.set('state', 'empty');
+    this.serverURL = serverURL;
+  },
+  
+  load: function() {
+    if (this.get('state') == 'empty') {
+      this.set('state', 'loading');
+      $.ajax({
+        url: this.serverURL,
+        dataType: 'text',
+        success: this.loadingSucceeded.bind(this)
+      });
+    }
+  },
+  
+  loadingSucceeded: function(data) {
+    if (this.get('state') != 'loading') {
+      alert('erroneous state:', this.get('state'));
+    }
+    this.set('data', data);
+    this.set('state', 'synced');
+  },
+  
+  set: function(property, value) {
+    if (property == 'state') {
+      console.log(this.description(), ':', property, '=', value);
+    }
+    return this._super(property, value);
+  }
+});
+
+
+Editor.DocumentModel = Editor.Model.extend({
+  _className: 'Editor.DocumentModel',
+  data: null, // name, text_url, latest_rev, latest_shared_rev, parts_url, dc_url, size
+  contentModels: {},
+  state: 'empty',
+  
+  init: function() {
+    this._super();
+    this.set('state', 'empty');
+    this.load();
+  },
+  
+  load: function() {
+    if (this.get('state') == 'empty') {
+      this.set('state', 'loading');
+      $.ajax({
+        cache: false,
+        url: documentsUrl + fileId,
+        dataType: 'json',
+        success: this.successfulLoad.bind(this)
+      });
+    }
+  },
+  
+  successfulLoad: function(data) {
+    this.set('data', data);
+    this.set('state', 'synced');
+    this.contentModels = {
+      'xml': new Editor.XMLModel(data.text_url),
+      'html': new Editor.HTMLModel(data.html_url)
+    };
+    for (var key in this.contentModels) {
+      this.contentModels[key].addObserver(this, 'state', this.contentModelStateChanged.bind(this));
+    }
+  },
+  
+  contentModelStateChanged: function(property, value, contentModel) {
+    if (value == 'dirty') {
+      for (var key in this.contentModels) {
+        if (this.contentModels[key].guid() != contentModel.guid()) {
+          // console.log(this.contentModels[key].description(), 'frozen');
+          this.contentModels[key].set('state', 'unsynced');
+        }
+      }
+    }
+  },
+  
+  quickSave: function(message) {
+    for (var key in this.contentModels) {
+      if (this.contentModels[key].get('state') == 'dirty') {
+        this.contentModels[key].update(message);
+        break;
+      }
+    }
+  }
+});
+
+
+var leftPanelView, rightPanelContainer, doc;
+
+$(function() {
+  doc = new Editor.DocumentModel();
+  var editor = new EditorView('#body-wrap', doc);
+  editor.freeze();
+  var splitView = new SplitView('#splitview', doc);
+  leftPanelView = new PanelContainerView('#left-panel-container', doc);
+  rightPanelContainer = new PanelContainerView('#right-panel-container', doc);
+});
diff --git a/project/static/js/views/button_toolbar.js b/project/static/js/views/button_toolbar.js
new file mode 100644 (file)
index 0000000..11cc148
--- /dev/null
@@ -0,0 +1,61 @@
+/*globals View render_template scriptletCenter*/
+var ButtonToolbarView = View.extend({
+  _className: 'ButtonToolbarView',
+  template: null,
+  buttons: null,
+  
+  init: function(element, model, parent, template) {
+    this._super(element, model, null);
+    this.parent = parent;
+    this.template = 'button-toolbar-view-template';
+    
+    this.model.addObserver(this, 'buttons', this.modelButtonsChanged.bind(this));
+    this.buttons = this.model.get('buttons');
+    this.model.load();
+    this.render();
+  },
+  
+  modelButtonsChanged: function(property, value) {
+    this.set('buttons', value);
+    this.render();
+  },
+  
+  render: function() {
+    $('.buttontoolbarview-tab', this.element).unbind('click.buttontoolbarview');
+    $('.buttontoolbarview-button', this.element).unbind('click.buttontoolbarview');
+    var self = this;
+    
+    this.element.html(render_template(this.template, this));
+    
+    $('.buttontoolbarview-tab', this.element).bind('click.buttontoolbarview', function() {
+      var groupIndex = $(this).attr('ui:groupindex');
+      $('.buttontoolbarview-group', self.element).each(function() {
+        if ($(this).attr('ui:groupindex') == groupIndex) {
+          $(this).show();
+        } else {
+          $(this).hide();
+        }
+      });
+      $(self.element).trigger('resize');
+    });
+    
+    $('.buttontoolbarview-button', this.element).bind('click.buttontoolbarview', function() {
+      var groupIndex = parseInt($(this).attr('ui:groupindex'), 10);
+      var buttonIndex = parseInt($(this).attr('ui:buttonindex'), 10);
+      var button = self.get('buttons')[groupIndex].buttons[buttonIndex];
+      var scriptletId = button.scriptlet_id;
+      var params = eval('(' + button.params + ')'); // To nie powinno być potrzebne
+      console.log('Executing', scriptletId, 'with params', params);
+      scriptletCenter[scriptletId](self.parent, params);
+    });
+    
+    $(this.element).trigger('resize');
+  },
+  
+  dispose: function() {
+    $('.buttontoolbarview-tab', this.element).unbind('click.buttontoolbarview');
+    $('.buttontoolbarview-button', this.element).unbind('click.buttontoolbarview');
+    this._super();
+  }
+});
+
diff --git a/project/static/js/views/editor.js b/project/static/js/views/editor.js
new file mode 100644 (file)
index 0000000..27b17cb
--- /dev/null
@@ -0,0 +1,37 @@
+/*global View render_template panels */
+var EditorView = View.extend({
+  _className: 'EditorView',
+  element: null,
+  model: null,
+  template: null,
+  
+  init: function(element, model, template) {
+    this._super(element, model, template);
+    this.model.load();
+    
+    $('#action-quick-save', this.element).bind('click.editorview', this.quickSave.bind(this));
+    $('#action-commit', this.element).bind('click.editorview', this.commit.bind(this));
+    $('#action-update', this.element).bind('click.editorview', this.update.bind(this));
+    this.freeze('Ładowanie');
+  },
+  
+  quickSave: function(event) {
+    console.log('quickSave');
+    this.model.quickSave();
+  },
+  
+  commit: function(event) {
+    console.log('commit');
+  },
+  
+  update: function(event) {
+    console.log('update');
+  },
+  
+  dispose: function() {
+    $('#action-quick-save', this.element).unbind('click.editorview');
+    $('#action-commit', this.element).unbind('click.editorview');
+    $('#action-update', this.element).unbind('click.editorview');
+    this._super();
+  }
+});
diff --git a/project/static/js/views/html.js b/project/static/js/views/html.js
new file mode 100644 (file)
index 0000000..3d803fc
--- /dev/null
@@ -0,0 +1,44 @@
+/*global View render_template panels */
+var HTMLView = View.extend({
+  _className: 'HTMLView',
+  element: null,
+  model: null,
+  template: 'html-view-template',
+  
+  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));
+      
+    $('.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);
+  },
+  
+  modelStateChanged: function(property, value) {
+    if (value == 'synced' || value == 'dirty') {
+      this.parent.unfreeze();
+    } else if (value == 'unsynced') {
+      this.parent.freeze('Niezsynchronizowany...');
+    } else if (value == 'loading') {
+      this.parent.freeze('Ładowanie...');
+    } else if (value == 'saving') {
+      this.parent.freeze('Zapisywanie...');
+    }
+  },
+  
+  dispose: function() {
+    this.model.removeObserver(this);
+    this._super();
+  }
+});
+
+// Register view
+panels['html'] = HTMLView;
\ No newline at end of file
diff --git a/project/static/js/views/panel_container.js b/project/static/js/views/panel_container.js
new file mode 100644 (file)
index 0000000..332e83a
--- /dev/null
@@ -0,0 +1,32 @@
+/*globals View render_template panels*/
+
+var PanelContainerView = View.extend({
+  _className: 'PanelContainerView',
+  element: null,
+  model: null,
+  template: 'panel-container-view-template',
+  contentView: null,
+  
+  init: function(element, model, template) {
+    this._super(element, model, template);
+
+    $('select', this.element.get(0)).bind('change.panel-container-view', this.selectChanged.bind(this));
+  },
+  
+  selectChanged: function(event) {
+    var value = $('select', this.element.get(0)).val();
+    var klass = panels[value];
+    if (this.contentView) {
+      this.contentView.dispose();
+      this.contentView = null;
+    }
+    this.contentView = new klass($('.content-view', 
+      this.element.get(0)), this.model.contentModels[value], this);
+  },
+  
+  dispose: function() {
+    $('select', this.element.get(0)).unbind('change.panel-container-view');
+    this._super();
+  }
+});
+
diff --git a/project/static/js/views/split.js b/project/static/js/views/split.js
new file mode 100644 (file)
index 0000000..48f0de7
--- /dev/null
@@ -0,0 +1,117 @@
+/*globals View*/
+
+// Split view inspired by jQuery Splitter Plugin http://methvin.com/splitter/
+var SplitView = View.extend({
+  _className: 'SplitView',
+  splitbarClass: 'splitview-splitbar',
+  activeClass: 'splitview-active',
+  overlayClass: 'splitview-overlay',
+  element: null,
+  model: null,
+  zombie: null,
+  leftViewOffset: 0,
+  
+  // Cache
+  _splitbarWidth: 0,
+  
+  init: function(element, model) {
+    this._super(element, model, null);
+    this.element.css('position', 'relative');
+    this._resizingSubviews = false;
+    
+    this.views = $(">*", this.element[0]).css({
+       position: 'absolute',                     // positioned inside splitter container
+       'z-index': 1,                                           // splitbar is positioned above
+       '-moz-outline-style': 'none',   // don't show dotted outline
+      overflow: 'auto'
+    });
+    
+    this.leftView = $(this.views[0]);
+    this.rightView = $(this.views[1]);
+    this.splitbar = $(this.views[2] || '<div></div>')
+      .insertAfter(this.leftView)
+      .css({
+        position: 'absolute',
+        'user-select': 'none',
+        '-webkit-user-select': 'none',
+        '-khtml-user-select': 'none',
+        '-moz-user-select': 'none',
+        'z-index': 100
+      })
+      .attr('unselectable', 'on')
+      .addClass(this.splitbarClass)
+      .bind('mousedown.splitview', this.beginResize.bind(this));
+    
+    this._splitbarWidth = this.splitbar.outerWidth();
+    
+    // Solomon's algorithm ;-)
+    this.resplit(this.element.width() / 2);
+  },
+    
+  beginResize: function(event) {
+    this.zombie = this.zombie || this.splitbar.clone(false).insertAfter(this.leftView);
+    this.overlay = this.overlay || $('<div></div>').addClass(this.overlayClass).css({
+        position: 'absolute',
+        width: this.element.width(),
+        height: this.element.height(),
+        top: this.element.position().top,
+        left: this.element.position().left
+      }).appendTo(this.element);
+    this.views.css("-webkit-user-select", "none"); // Safari selects A/B text on a move
+    this.splitbar.addClass(this.activeClass);
+    this.leftViewOffset = this.leftView[0].offsetWidth - event.pageX;
+    
+    $(document)
+      .bind('mousemove.splitview', this.resizeChanged.bind(this))
+      .bind('mouseup.splitview', this.endResize.bind(this));
+  },
+  
+  resizeChanged: function(event) {
+    var newPosition = event.pageX + this.leftViewOffset;
+    newPosition = Math.max(0, Math.min(newPosition, this.element.width() - this._splitbarWidth));
+    this.splitbar.css('left', newPosition);
+  },
+
+  endResize: function(event) {
+    var newPosition = event.pageX + this.leftViewOffset;
+    this.zombie.remove();
+    this.zombie = null;
+    this.overlay.remove();
+    this.overlay = null;
+    this.resplit(newPosition);
+
+    $(document)
+      .unbind('mousemove.splitview')
+      .unbind('mouseup.splitview');
+  },
+
+  resized: function(event) {
+    if (!this._resizingSubviews) {
+      this.resplit(Math.min(this.leftView.width(), this.element.width() - this._splitbarWidth));
+    }
+  },
+  
+  resplit: function(newPosition) {
+    newPosition = Math.max(0, Math.min(newPosition, this.element.width() - this._splitbarWidth));
+    this.splitbar.css('left', newPosition);
+    this.leftView.css({
+      left: 0,
+      width: newPosition
+    });
+    this.rightView.css({
+      left: newPosition + this._splitbarWidth,
+      width: this.element.width() - newPosition - this._splitbarWidth
+    });
+    if (!$.browser.msie) {
+      this._resizingSubviews = true;
+                 $(window).trigger('resize');
+                 this._resizingSubviews = false;
+               }
+  },
+  
+  dispose: function() {
+    this.splitter.unbind('mousedown.splitview');
+    this._super();
+  }
+});
+
diff --git a/project/static/js/views/view.js b/project/static/js/views/view.js
new file mode 100644 (file)
index 0000000..52fc6e0
--- /dev/null
@@ -0,0 +1,81 @@
+/*globals Editor render_template*/
+var View = Editor.Object.extend({
+  _className: 'View',
+  element: null,
+  model: null,
+  template: null,
+  overlayClass: 'view-overlay',
+  overlay: null,
+  
+  init: function(element, model, template) {
+    this.element = $(element);
+    this.model = model;
+    this.template = template || this.template;
+    
+    if (this.template) {
+      this.element.html(render_template(this.template, this));
+    }
+    
+    this._resizeHandler = this.resized.bind(this);
+    $(window).bind('resize', this._resizeHandler);
+    $(this.element).bind('resize', this._resizeHandler);
+  },
+  
+  frozen: function() {
+    return !!this.overlay;
+  },
+  
+  freeze: function(message) {
+    this.overlay = this.overlay 
+      || $('<div><div>' + message + '</div></div>')
+            .addClass(this.overlayClass)
+            .css({
+              position: 'absolute',
+              width: this.element.width(),
+              height: this.element.height(),
+              top: this.element.position().top,
+              left: this.element.position().left,
+              'user-select': 'none',
+              '-webkit-user-select': 'none',
+              '-khtml-user-select': 'none',
+              '-moz-user-select': 'none',
+              overflow: 'hidden'
+            })
+            .attr('unselectable', 'on')
+            .appendTo(this.element.parent());
+            
+    this.overlay.children('div').css({
+      position: 'relative',
+      top: this.overlay.height() / 2 - 20
+    });
+  },
+  
+  unfreeze: function() {
+    if (this.frozen()) {
+      this.overlay.remove();
+      this.overlay = null;      
+    }
+  },
+
+  resized: function(event) {
+    if (this.frozen()) {
+      this.overlay.css({
+        position: 'absolute',
+        width: this.element.width(),
+        height: this.element.height(),
+        top: this.element.position().top,
+        left: this.element.position().left
+      }).children('div').css({
+        position: 'relative',
+        top: this.overlay.height() / 2 - 20
+      });
+    }
+  },
+  
+  dispose: function() {
+    $(window).unbind('resize', this._resizeHandler);
+    $(this.element).unbind('resize', this._resizeHandler);
+    this.unfreeze();
+    this.element.contents().remove();
+  }
+});
diff --git a/project/static/js/views/xml.js b/project/static/js/views/xml.js
new file mode 100644 (file)
index 0000000..6b7571a
--- /dev/null
@@ -0,0 +1,87 @@
+/*global View CodeMirror ButtonToolbarView render_template panels */
+var XMLView = View.extend({
+  _className: 'XMLView',
+  element: null,
+  model: null,
+  template: 'xml-view-template',
+  editor: null,
+  buttonToolbar: null,
+  
+  init: function(element, model, parent, template) {
+    this._super(element, model, template);
+    this.parent = parent;
+    this.buttonToolbar = new ButtonToolbarView(
+      $('.xmlview-toolbar', this.element), 
+      this.model.toolbarButtonsModel, parent);
+
+    $('.xmlview-toolbar', this.element).bind('resize.xmlview', this.resized.bind(this));
+    
+    this.parent.freeze('Ładowanie edytora...');
+       this.editor = new CodeMirror($('.xmlview', this.element).get(0), {
+      parserfile: 'parsexml.js',
+      path: "/static/js/lib/codemirror/",
+      stylesheet: "/static/css/xmlcolors.css",
+      parserConfig: {useHTMLKludges: false},
+      textWrapping: false,
+      tabMode: 'spaces',
+      indentUnit: 0,
+      onChange: this.editorDataChanged.bind(this),
+      initCallback: this.editorDidLoad.bind(this)
+    });
+  },
+  
+  resized: function(event) {
+    var height = this.element.height() - $('.xmlview-toolbar', this.element).outerHeight();
+    $('.xmlview', this.element).height(height);
+  },
+  
+  editorDidLoad: function(editor) {
+    $(editor.frame).css({width: '100%', height: '100%'});
+    this.model
+      .addObserver(this, 'data', this.modelDataChanged.bind(this))
+      .addObserver(this, 'state', this.modelStateChanged.bind(this))
+      .load();
+    
+    this.parent.unfreeze();
+      
+    this.editor.setCode(this.model.get('data'));
+    this.modelStateChanged('state', this.model.get('state'));
+        
+    // editor.grabKeys(
+    //   $.fbind(self, self.hotkeyPressed),
+    //   $.fbind(self, self.isHotkey)
+    // );
+  },
+  
+  editorDataChanged: function() {
+    this.model.set('data', this.editor.getCode());
+  },
+  
+  modelDataChanged: function(property, value) {
+    console.log('modelDataChanged');
+    if (this.editor.getCode() != value) {
+      this.editor.setCode(value);
+    }
+  },
+  
+  modelStateChanged: function(property, value) {
+    if (value == 'synced' || value == 'dirty') {
+      this.parent.unfreeze();
+    } else if (value == 'unsynced') {
+      this.parent.freeze('Niezsynchronizowany...');
+    } else if (value == 'loading') {
+      this.parent.freeze('Ładowanie...');
+    } else if (value == 'saving') {
+      this.parent.freeze('Zapisywanie...');
+    }
+  },
+    
+  dispose: function() {
+    this.model.removeObserver(this);
+    $(this.editor.frame).remove();
+    this._super();
+  }
+});
+
+// Register view
+panels['xml'] = XMLView;
index 48ad036..06fbc62 100644 (file)
@@ -11,6 +11,7 @@
         {% endblock %}
     </head>
     <body id="{% block bodyid %}base{% endblock %}">
+       <div id="body-wrap">
        <div id="header">
                <span id="breadcrumbs">{% block breadcrumbs %}<a href="{% url file_list %}">Platforma Redakcyjna</a>{% endblock breadcrumbs %}</span>
                <span id="header-right-toolbar">
@@ -22,5 +23,6 @@
     <div id="content">{% block maincontent %} {% endblock %}</div>
 
     {% block extrabody %}{% endblock %}
+       </div>
     </body>
 </html>
index 22a60f6..d075678 100644 (file)
@@ -1,46 +1,74 @@
 {% extends "base.html" %}
 
 {% block extrahead %}
+       <link rel="stylesheet" href="{{STATIC_URL}}css/html.css" type="text/css" media="screen" title="no title" charset="utf-8">
        <script type="text/javascript" charset="utf-8">
                var fileId = '{{ fileid }}';
        </script>
-    <link rel="stylesheet" href="{{STATIC_URL}}css/toolbar.css" type="text/css" />
-    <link rel="stylesheet" href="{{STATIC_URL}}css/jquery.modal.css" type="text/css" />
-    <script src="{{STATIC_URL}}js/lib/jquery.lazyload.js" type="text/javascript" charset="utf-8"></script>
     <script src="{{STATIC_URL}}js/lib/codemirror/codemirror.js" type="text/javascript" charset="utf-8"></script>
-    <script src="{{STATIC_URL}}js/lib/jquery.wtooltip.js" type="text/javascript" charset="utf-8"></script>
-    <script src="{{STATIC_URL}}js/lib/jquery.json.js" type="text/javascript" charset="utf-8"></script>
-    <script src="{{STATIC_URL}}js/lib/jquery.cookie.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/editor.js" type="text/javascript" charset="utf-8"></script>
-    <script src="{{STATIC_URL}}js/editor.ui.js" type="text/javascript" charset="utf-8"></script>
+       
+       {# Scriptlets #}
+       <script src="http://localhost:8000/api/toolbar/scriptlets" type="text/javascript" charset="utf-8"></script>
+       
+       {# App and views #}
+       <script src="{{STATIC_URL}}js/app.js" type="text/javascript" charset="utf-8"></script>
+       <script src="{{STATIC_URL}}js/views/view.js" type="text/javascript" charset="utf-8"></script>
+       <script src="{{STATIC_URL}}js/views/editor.js" type="text/javascript" charset="utf-8"></script>
+       <script src="{{STATIC_URL}}js/views/button_toolbar.js" type="text/javascript" charset="utf-8"></script>
+       <script src="{{STATIC_URL}}js/views/split.js" type="text/javascript" charset="utf-8"></script>
+       <script src="{{STATIC_URL}}js/views/xml.js" type="text/javascript" charset="utf-8"></script>
+       <script src="{{STATIC_URL}}js/views/html.js" type="text/javascript" charset="utf-8"></script>
+       <script src="{{STATIC_URL}}js/views/panel_container.js" type="text/javascript" charset="utf-8"></script>
+       
+       <script src="{{STATIC_URL}}js/models.js" type="text/javascript" charset="utf-8"></script>
+       
+       {# JavaScript templates #}
+       <script type="text/html" charset="utf-8" id="panel-container-view-template">
+               <select>
+                       <% for (panel in panels) { %>
+                       <option value="<%= panel %>"><%= panel %></option>
+                       <% }; %>
+               </select>
+               <div class="content-view"></div>
+       </script>
+       
+       <script type="text/html" charset="utf-8" id="xml-view-template">
+               <div class="xmlview-toolbar"></div>
+               <div class="xmlview">
+                       
+               </div>
+       </script>
+       
+       <script type="text/html" charset="utf-8" id="html-view-template">
+               <div class="htmlview">
+               </div>
+       </script>
+       
+       <script type="text/html" charset="utf-8" id="button-toolbar-view-template">
+               <div class="buttontoolbarview">
+                       <div class="buttontoolbarview-tabs">
+                       <% for (var i=0; i < buttons.length; i++) { %>
+                               <a href="#" class="buttontoolbarview-tab" ui:groupindex="<%= i %>"><%= buttons[i].name %></a>
+                       <% }; %>
+                       </div>
+                       <div class="buttontoolbarview-groups">
+                       <% for (var i=0; i < buttons.length; i++) { %>
+                               <div class="buttontoolbarview-group" ui:groupIndex="<%= i %>" style="display: none">
+                                       <% for (var j=0; j < buttons[i].buttons.length; j++) { %>
+                                               <% if (buttons[i].buttons[j].scriptlet_id) { %>
+                                               <a href="#" class="buttontoolbarview-button" ui:groupindex="<%= i %>" ui:buttonindex="<%= j %>">
+                                                       <%= buttons[i].buttons[j].label %>
+                                               </a>
+                                               <% } %>
+                                       <% } %>
+                               </div>
+                       <% }; %>
+                       </div>
+               </div>
+       </script>
 {% endblock extrahead %}
 
-{% block extrabody %}
-    <script type="text/javascript" charset="utf-8">
-    $(function() {
-        {% for scriptlet in scriptlets %}
-        editor.registerScriptlet("{{scriptlet.name}}",
-            function(editor, panel, params) {
-                {{ scriptlet.code|safe }}
-            });
-        {% endfor %}
-     });  
-    </script>
-{% endblock extrabody %}
-
-{% load explorer_tags %}
-
-{% block breadcrumbs %}<a href="{% url file_list %}">Platforma Redakcyjna</a> &gt; {{ fileid|bookname }}{% endblock breadcrumbs %}
-
-{% block header-toolbar %}
-<button type="button" class="requires-save toolbar-button" id="toolbar-button-commit">Merge</button>
-<button type="button" class="requires-save toolbar-button" id="toolbar-button-update"
-        ui:ajax-action="{% url file_update fileid %}">Update</button>
-<button type="button" class="provides-save toolbar-button" id="toolbar-button-save" disabled="disabled">Commit</button>
-<button type="button" class="provides-save toolbar-button" id="toolbar-button-quick-save" disabled="disabled">Quick Save</button>
-{% endblock %}
+{% block breadcrumbs %}<a href="{% url file_list %}">Platforma Redakcyjna</a> &gt; {{ fileid }}{% endblock breadcrumbs %}
 
 {% block message-box %}
     <div class="msg-info" id="save-waiting"><p>Zapisuję dane na serwerze.</p><p class="data"></p></div>
     
 {% endblock %}
 
-{% block maincontent %}
-        <div id="panels">
-            {% for n in panel_list %}
-            <div class="panel-wrap{% if forloop.last %} last-panel{% endif %}" id="panel-{{n}}">
-                <div class="panel-toolbar">
-                    <p><label>{{n|capfirst}} panel:
-                    <select name="select-{{n}}-panel" id="panel-{{n}}-select">
-                        {% for panel_type in availble_panels %}
-                        <option value="{% url panel_view path=fileid,name=panel_type.id  %}" p:panel-name="{{ panel_type.id }}">{{panel_type.display_name}}</option>
-                        {% endfor %}
-                    </select>
-                    </label>
+{% block header-toolbar %}
+       <button id="action-update">Update</button> <button id="action-commit">Commit</button> <button id="action-quick-save">Quick Save</button>
+{% endblock %}
 
-                    <span
-                    <span class="toolbar-buttons-container panel-toolbar-extra">
-                        </span>
-                    </span>
-                    <!-- rethink the refresh button - it doesn't work very well -->
-                    <!-- <button type="button" class="refresh-button">Odśwież</button> -->
-                    {# <a href="{% url print_xml fileid %}" target="_new">Wydruk</a> #}
-                    <strong class="change-notification" style="display: none">Widok nieaktualny!</strong>
-                    </p>
-               </div>
-               <div id="panel-{{n}}-content" class="panel-content"></div>
-               <button type="button" class="panel-slider"></button>
-            </div>
-            {% endfor %}
-        </div>
+{% block maincontent %}
+       <div id="splitview">
+               <div id="left-panel-container" class='panel-container'></div>
+           <div id="right-panel-container" class='panel-container'></div>
+    </div>
 
-        <div id="commit-dialog" class="jqmWindow">
-            <form action="{% url file_commit fileid %}" method="POST">
-                <label for="message">Commit message:</label>
-                <textarea cols="60" rows="10" name="message" id="commit-dialog-message"></textarea>
-                <p id="commit-dialog-error-empty-message">Wiadomość nie może być pusta.</p>                
-                <fieldset id="commit-dialog-related-issues" 
-                          ui:ajax-src="http://localhost:3000/publications/issues/{{fileid}}">
-                    <legend>Related issues</legend>
-                    <div class="loading-box" style="display: none;">
-                        <p>Loading related issues...</p>
-                    </div>
-                    <div class="container-box">No related issues.</div>
-                </fieldset>
-                <p>
-                   <input type="button" value="Save" id="commit-dialog-save-button" />
-                   <input type="reset" value="Cancel" id="commit-dialog-cancel-button" />
-                </p>
-            </form>
-        </div>
 
-        <div id="split-dialog" class="jqmWindow">
-            <div class="container-box"> </div>
-            <div class="loading-box" style="display: none;">
-                <p>Loading dialog contents...</p>
-                <!-- <p><button type="button" class="dialog-close-button">Close</button></p> -->
-            </div>
-            <div class="fatal-error-box" style="display: none;">
-                <p>Server error, while loading dialog :(</p>
-                <p><button type="button" class="dialog-close-button">Close</button></p>
-            </div>
-        </div>
-{% endblock maincontent %}    
+    {# <div id="commit-dialog" class="jqmWindow"> #}
+    {#     <form action="{% url file_commit fileid %}" method="POST"> #}
+    {#         <label for="message">Commit message:</label> #}
+    {#         <textarea cols="60" rows="10" name="message" id="commit-dialog-message"></textarea> #}
+    {#         <p id="commit-dialog-error-empty-message">Wiadomość nie może być pusta.</p>                 #}
+    {#         <fieldset id="commit-dialog-related-issues"  #}
+    {#                   ui:ajax-src="http://localhost:3000/publications/issues/{{fileid}}"> #}
+    {#             <legend>Related issues</legend> #}
+    {#             <div class="loading-box" style="display: none;"> #}
+    {#                 <p>Loading related issues...</p> #}
+    {#             </div> #}
+    {#             <div class="container-box">No related issues.</div> #}
+    {#         </fieldset> #}
+    {#         <p> #}
+    {#            <input type="button" value="Save" id="commit-dialog-save-button" /> #}
+    {#            <input type="reset" value="Cancel" id="commit-dialog-cancel-button" /> #}
+    {#         </p> #}
+    {#     </form> #}
+    {# </div> #}
+    {#  #}
+    {# <div id="split-dialog" class="jqmWindow"> #}
+    {#     <div class="container-box"> </div> #}
+    {#     <div class="loading-box" style="display: none;"> #}
+    {#         <p>Loading dialog contents...</p> #}
+    {#         <!-- <p><button type="button" class="dialog-close-button">Close</button></p> --> #}
+    {#     </div> #}
+    {#     <div class="fatal-error-box" style="display: none;"> #}
+    {#         <p>Server error, while loading dialog :(</p> #}
+    {#         <p><button type="button" class="dialog-close-button">Close</button></p> #}
+    {#     </div> #}
+    {# </div> #}
+{% endblock maincontent %}