From: Łukasz Rekucki Date: Mon, 28 Sep 2009 14:25:43 +0000 (+0200) Subject: Merge branch 'zuber-view-refactor' X-Git-Url: https://git.mdrn.pl/redakcja.git/commitdiff_plain/b348abeb083671e831f711d3d41949a9b3f3a7b4?hp=b9bce2dc718a9146a202057e4bb1809927d9caf6 Merge branch 'zuber-view-refactor' Conflicts: apps/api/handlers/library_handlers.py --- diff --git a/apps/api/handlers/library_handlers.py b/apps/api/handlers/library_handlers.py index 64b95c2d..7db7b795 100644 --- a/apps/api/handlers/library_handlers.py +++ b/apps/api/handlers/library_handlers.py @@ -278,17 +278,19 @@ class DocumentTextHandler(BaseHandler): f = lib._fileopen(resolve('xml'), 'w+') f.write(data) f.close() - + + ndoc = None ndoc = current.invoke_and_commit(\ xml_update_action, lambda d: (msg, current.owner) ) try: # return the new revision number - return response.SuccessAllOk.django_response({ + return response.SuccessAllOk().django_response({ "document": ndoc.id, "subview": "xml", "previous_revision": current.revision, - "updated_revision": ndoc.revision + "updated_revision": ndoc.revision, + "url": reverse("doctext_view", args=[ndoc.id, ndoc.revision]) }) except Exception, e: if ndoc: lib._rollback() diff --git a/apps/api/tests/__init__.py b/apps/api/tests/__init__.py index f28b3b2f..751a085c 100644 --- a/apps/api/tests/__init__.py +++ b/apps/api/tests/__init__.py @@ -32,10 +32,11 @@ def temprepo(name): finally: if not clean and self.response: print "RESULT", func.__name__, ">>>" - print self.response + print self.response.content print "<<<" - - # shutil.rmtree(temp, True) + else: + shutil.rmtree(temp, True) + settings.REPOSITORY_PATH = '' return decorated @@ -141,24 +142,26 @@ class SimpleTest(TestCase): self.response = self.client.get(resp['text_url']) self.assertEqual(self.response.status_code, 200) self.assertEqual(self.response.content, "Ala ma kota\n") -# -# -# @temprepo('simple') -# def test_document_text_update(self): -# self.assertTrue(self.client.login(username='admin', password='admin')) -# TEXT = u"Ala ma kota i psa" -# -# self.response = self.client.put( \ -# reverse("doctext_view", args=['testfile']), {'contents': TEXT }) -# self.assertEqual(self.response.status_code, 200) -# -# self.response = self.client.get( \ -# reverse("doctext_view", args=['testfile']) ) -# self.assertEqual(self.response.status_code, 200) -# self.assertEqual(self.response.content, TEXT) + + + @temprepo('simple') + def test_document_text_update(self): + self.assertTrue(self.client.login(username='admin', password='admin')) + TEXT = u"Ala ma kota i psa" + + self.response = self.client.get( + reverse("document_view", args=['sample']) ) + + resp = self.assert_json_response() + + self.response = self.client.put(resp['text_url'], {'contents': TEXT }) + result = self.assert_json_response(must_have={u'document': u'sample'} ) + + #self.response = self.client.get(result['url']) + #self.assertEqual(self.response.content, TEXT) def assert_json_response(self, must_have={}, exclude=[], code=200): - self.assertEqual(self.response.status_code, code) + self.assertEqual(self.response.status_code, code) result = json.loads(self.response.content) for (k,v) in must_have.items(): diff --git a/project/static/css/master.css b/project/static/css/master.css index 44bd62ec..7caa732f 100644 --- a/project/static/css/master.css +++ b/project/static/css/master.css @@ -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,71 @@ 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; +} + +.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 index 00000000..15ee0c0f --- /dev/null +++ b/project/static/js/app.js @@ -0,0 +1,178 @@ +/*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 = {}; + console.log('Created', this.guid()); + }, + + 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 = []; diff --git a/project/static/js/editor.js b/project/static/js/editor.js index 79b7fa90..6918f9e5 100644 --- a/project/static/js/editor.js +++ b/project/static/js/editor.js @@ -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() { diff --git a/project/static/js/editor.ui.js b/project/static/js/editor.ui.js index f66d977b..c8d47f66 100755 --- a/project/static/js/editor.ui.js +++ b/project/static/js/editor.ui.js @@ -2,116 +2,116 @@ * UI related Editor methods */ Editor.prototype.setupUI = function() { - // set up the UI visually and attach callbacks +// // set up the UI visually and attach callbacks var self = this; - - var resize_start = function(event, mydata) { - $(document).bind('mousemove', mydata, resize_changed). - bind('mouseup', mydata, resize_stop); - - $('.panel-overlay', mydata.root).css('display', 'block'); - return false; - } - var resize_changed = function(event) { - var old_width = parseInt(event.data.overlay.css('width')); - var delta = event.pageX + event.data.hotspot_x - old_width; - - if(old_width + delta < 12) delta = 12 - old_width; - if(old_width + delta > $(window).width()) - delta = $(window).width() - old_width; - - event.data.overlay.css({ - 'width': old_width + delta - }); - - if(event.data.overlay.next) { - var left = parseInt(event.data.overlay.next.css('left')); - event.data.overlay.next.css('left', left+delta); - } - - return false; - }; - - var resize_stop = function(event) { - $(document).unbind('mousemove', resize_changed).unbind('mouseup', resize_stop); - // $('.panel-content', event.data.root).css('display', 'block'); - var overlays = $('.panel-content-overlay', event.data.root); - $('.panel-content-overlay', event.data.root).each(function(i) { - if( $(this).data('panel').hasClass('last-panel') ) - $(this).data('panel').css({ - 'left': $(this).css('left'), - 'right': $(this).css('right') - }); - else - $(this).data('panel').css({ - 'left': $(this).css('left'), - 'width': $(this).css('width') - }); - }); - $('.panel-overlay', event.data.root).css('display', 'none'); - $(event.data.root).trigger('stopResize'); - }; - - /* - * Prepare panels (overlays & stuff) - */ - /* create an overlay */ - var panel_root = self.rootDiv; - var overlay_root = $("
"); - panel_root.append(overlay_root); - - var prev = null; - - $('*.panel-wrap', panel_root).each( function() - { - var panel = $(this); - var handle = $('.panel-slider', panel); - var overlay = $("
 
"); - overlay_root.append(overlay); - overlay.data('panel', panel); - overlay.data('next', null); - - if (prev) prev.next = overlay; - - if( panel.hasClass('last-panel') ) - { - overlay.css({ - 'left': panel.css('left'), - 'right': panel.css('right') - }); - } - else { - overlay.css({ - 'left': panel.css('left'), - 'width': panel.css('width') - }); - // $.log('Has handle: ' + panel.attr('id')); - overlay.append(handle.clone()); - /* attach the trigger */ - handle.mousedown(function(event) { - var touch_data = { - root: panel_root, - overlay: overlay, - hotspot_x: event.pageX - handle.position().left - }; - - $(this).trigger('hpanel:panel-resize-start', touch_data); - return false; - }); - $('.panel-content', panel).css('right', - (handle.outerWidth() || 10) + 'px'); - $('.panel-content-overlay', panel).css('right', - (handle.outerWidth() || 10) + 'px'); - }; - - prev = overlay; - }); - - panel_root.bind('hpanel:panel-resize-start', resize_start); - self.rootDiv.bind('stopResize', function() { - self.savePanelOptions(); - }); - +// +// var resize_start = function(event, mydata) { +// $(document).bind('mousemove', mydata, resize_changed). +// bind('mouseup', mydata, resize_stop); +// +// $('.panel-overlay', mydata.root).css('display', 'block'); +// return false; +// } +// var resize_changed = function(event) { +// var old_width = parseInt(event.data.overlay.css('width')); +// var delta = event.pageX + event.data.hotspot_x - old_width; +// +// if(old_width + delta < 12) delta = 12 - old_width; +// if(old_width + delta > $(window).width()) +// delta = $(window).width() - old_width; +// +// event.data.overlay.css({ +// 'width': old_width + delta +// }); +// +// if(event.data.overlay.next) { +// var left = parseInt(event.data.overlay.next.css('left')); +// event.data.overlay.next.css('left', left+delta); +// } +// +// return false; +// }; +// +// var resize_stop = function(event) { +// $(document).unbind('mousemove', resize_changed).unbind('mouseup', resize_stop); +// // $('.panel-content', event.data.root).css('display', 'block'); +// var overlays = $('.panel-content-overlay', event.data.root); +// $('.panel-content-overlay', event.data.root).each(function(i) { +// if( $(this).data('panel').hasClass('last-panel') ) +// $(this).data('panel').css({ +// 'left': $(this).css('left'), +// 'right': $(this).css('right') +// }); +// else +// $(this).data('panel').css({ +// 'left': $(this).css('left'), +// 'width': $(this).css('width') +// }); +// }); +// $('.panel-overlay', event.data.root).css('display', 'none'); +// $(event.data.root).trigger('stopResize'); +// }; +// +// /* +// * Prepare panels (overlays & stuff) +// */ +// /* create an overlay */ +// var panel_root = self.rootDiv; +// var overlay_root = $("
"); +// panel_root.append(overlay_root); +// +// var prev = null; +// +// $('*.panel-wrap', panel_root).each( function() +// { +// var panel = $(this); +// var handle = $('.panel-slider', panel); +// var overlay = $("
 
"); +// overlay_root.append(overlay); +// overlay.data('panel', panel); +// overlay.data('next', null); +// +// if (prev) prev.next = overlay; +// +// if( panel.hasClass('last-panel') ) +// { +// overlay.css({ +// 'left': panel.css('left'), +// 'right': panel.css('right') +// }); +// } +// else { +// overlay.css({ +// 'left': panel.css('left'), +// 'width': panel.css('width') +// }); +// // $.log('Has handle: ' + panel.attr('id')); +// overlay.append(handle.clone()); +// /* attach the trigger */ +// handle.mousedown(function(event) { +// var touch_data = { +// root: panel_root, +// overlay: overlay, +// hotspot_x: event.pageX - handle.position().left +// }; +// +// $(this).trigger('hpanel:panel-resize-start', touch_data); +// return false; +// }); +// $('.panel-content', panel).css('right', +// (handle.outerWidth() || 10) + 'px'); +// $('.panel-content-overlay', panel).css('right', +// (handle.outerWidth() || 10) + 'px'); +// }; +// +// prev = overlay; +// }); +// +// panel_root.bind('hpanel:panel-resize-start', resize_start); +// self.rootDiv.bind('stopResize', function() { +// self.savePanelOptions(); +// }); +// /* * Connect panel actions */ @@ -187,8 +187,6 @@ Editor.prototype.setupUI = function() { onShow: $.fbind(self, self.loadSplitDialog) }). jqmAddClose('button.dialog-close-button'); - -// $('#split-dialog'). } Editor.prototype.loadRelatedIssues = function(hash) diff --git a/project/static/js/lib/codemirror/codemirror.js b/project/static/js/lib/codemirror/codemirror.js index 25da6f74..f63ed07e 100644 --- a/project/static/js/lib/codemirror/codemirror.js +++ b/project/static/js/lib/codemirror/codemirror.js @@ -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);}, diff --git a/project/static/js/lib/codemirror/editor.js b/project/static/js/lib/codemirror/editor.js index 757d3eaf..b7c53c70 100644 --- a/project/static/js/lib/codemirror/editor.js +++ b/project/static/js/lib/codemirror/editor.js @@ -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; diff --git a/project/static/js/lib/codemirror/select.js b/project/static/js/lib/codemirror/select.js index 002004e2..7746240e 100644 --- a/project/static/js/lib/codemirror/select.js +++ b/project/static/js/lib/codemirror/select.js @@ -52,7 +52,7 @@ var select = {}; while (pos && pos.offsetParent) { y += pos.offsetTop; // Don't count X offset for
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(); diff --git a/project/static/js/lib/codemirror/stringstream.js b/project/static/js/lib/codemirror/stringstream.js index 6d9355f4..8c1c0422 100644 --- a/project/static/js/lib/codemirror/stringstream.js +++ b/project/static/js/lib/codemirror/stringstream.js @@ -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 = ""; diff --git a/project/static/js/lib/codemirror/undo.js b/project/static/js/lib/codemirror/undo.js index 1930cfbd..97daf593 100644 --- a/project/static/js/lib/codemirror/undo.js +++ b/project/static/js/lib/codemirror/undo.js @@ -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; } diff --git a/project/static/js/lib/codemirror/util.js b/project/static/js/lib/codemirror/util.js index 61f927d1..0cd91d4e 100644 --- a/project/static/js/lib/codemirror/util.js +++ b/project/static/js/lib/codemirror/util.js @@ -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 index 00000000..b2c7440c --- /dev/null +++ b/project/static/js/models.js @@ -0,0 +1,150 @@ +/*globals Editor fileId SplitView PanelContainerView*/ +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); + } +}); + + +Editor.XMLModel = Editor.Model.extend({ + _className: 'Editor.XMLModel', + serverURL: null, + data: '', + + init: function(serverURL) { + this._super(); + this.serverURL = serverURL; + this.toolbarButtonsModel = new Editor.ToolbarButtonsModel(); + }, + + getData: function() { + if (!this.data) { + this.reload(); + } + return this.data; + }, + + load: function() { + if (!this.get('synced')) { + $.ajax({ + url: this.serverURL, + dataType: 'text', + success: this.reloadSucceeded.bind(this) + }); + } + }, + + reloadSucceeded: function(data) { + this.set('data', data); + this.set('synced', true); + } +}); + + +Editor.HTMLModel = Editor.Model.extend({ + _className: 'Editor.HTMLModel', + serverURL: null, + data: '', + + init: function(serverURL) { + this._super(); + this.serverURL = serverURL; + }, + + load: function() { + if (!this.get('synced')) { + $.ajax({ + url: this.serverURL, + dataType: 'text', + success: this.reloadSucceeded.bind(this) + }); + } + }, + + reloadSucceeded: function(data) { + this.set('data', data); + this.set('synced', true); + } +}); + + +Editor.DocumentModel = Editor.Model.extend({ + _className: 'Editor.DocumentModel', + data: null, // name, text_url, latest_rev, latest_shared_rev, parts_url, dc_url, size + contentModels: {}, + + init: function() { + this._super(); + this.load(); + }, + + load: function() { + console.log('DocumentModel#load'); + $.ajax({ + cache: false, + url: documentsUrl + fileId, + dataType: 'json', + success: this.successfulLoad.bind(this) + }); + }, + + successfulLoad: function(data) { + console.log('DocumentModel#successfulLoad:', data); + this.set('data', data); + 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, 'data', this.contentModelDataChanged.bind(this)); + } + }, + + contentModelDataChanged: function(property, value, contentModel) { + console.log('data of', contentModel.description(), 'changed!'); + for (var key in this.contentModels) { + if (this.contentModels[key].guid() != contentModel.guid()) { + console.log(this.contentModels[key].description(), 'frozen'); + this.contentModels[key].set('synced', false); + } + } + } +}); + + +var leftPanelView, rightPanelContainer, doc; + +$(function() { + doc = new Editor.DocumentModel(); + var editor = new EditorView('body', doc); + 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 index 00000000..3e0a1488 --- /dev/null +++ b/project/static/js/views/button_toolbar.js @@ -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(event) { + 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.scriptlets[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 index 00000000..b3c9563b --- /dev/null +++ b/project/static/js/views/editor.js @@ -0,0 +1,12 @@ +/*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(); + } +}); diff --git a/project/static/js/views/html.js b/project/static/js/views/html.js new file mode 100644 index 00000000..41c34979 --- /dev/null +++ b/project/static/js/views/html.js @@ -0,0 +1,42 @@ +/*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, 'synced', this.modelSyncChanged.bind(this)); + + $('.htmlview', this.element).html(this.model.get('data')); + if (!this.model.get('synced')) { + this.parent.freeze('Niezsynchronizowany...'); + this.model.load(); + } + }, + + modelDataChanged: function(property, value) { + $('.htmlview', this.element).html(value); + }, + + modelSyncChanged: function(property, value) { + if (value) { + this.parent.unfreeze(); + } else { + this.parent.freeze('Niezsynchronizowany...'); + } + }, + + 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 index 00000000..332e83a6 --- /dev/null +++ b/project/static/js/views/panel_container.js @@ -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 index 00000000..48f0de73 --- /dev/null +++ b/project/static/js/views/split.js @@ -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] || '
') + .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 || $('
').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 index 00000000..52fc6e04 --- /dev/null +++ b/project/static/js/views/view.js @@ -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 + || $('
' + message + '
') + .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 index 00000000..460317bc --- /dev/null +++ b/project/static/js/views/xml.js @@ -0,0 +1,85 @@ +/*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(); + console.log('.xmlview height =', height); + $('.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, 'synced', this.modelSyncChanged.bind(this)); + + this.parent.unfreeze(); + + this.editor.setCode(this.model.get('data')); + if (!this.model.get('synced')) { + this.parent.freeze('Niezsynchronizowany...'); + this.model.load(); + } + + // editor.grabKeys( + // $.fbind(self, self.hotkeyPressed), + // $.fbind(self, self.isHotkey) + // ); + }, + + editorDataChanged: function() { + this.model.set('data', this.editor.getCode()); + }, + + modelDataChanged: function(property, value) { + if (this.editor.getCode() != value) { + this.editor.setCode(value); + } + }, + + modelSyncChanged: function(property, value) { + if (value) { + this.parent.unfreeze(); + } else { + this.parent.freeze('Niezsynchronizowany...'); + } + }, + + dispose: function() { + this.model.removeObserver(this); + $(this.editor.frame).remove(); + this._super(); + } +}); + +// Register view +panels['xml'] = XMLView; diff --git a/project/templates/explorer/editor.html b/project/templates/explorer/editor.html index 22a60f60..a6bcc41a 100644 --- a/project/templates/explorer/editor.html +++ b/project/templates/explorer/editor.html @@ -4,43 +4,70 @@ - - - - - - - - - - + + {# Scriptlets #} + + + {# App and views #} + + + + + + + + + + + + {# JavaScript templates #} + + + + + + + {% endblock extrahead %} -{% block extrabody %} - -{% endblock extrabody %} - -{% load explorer_tags %} - -{% block breadcrumbs %}Platforma Redakcyjna > {{ fileid|bookname }}{% endblock breadcrumbs %} - -{% block header-toolbar %} - - - - -{% endblock %} +{% block breadcrumbs %}Platforma Redakcyjna > {{ fileid }}{% endblock breadcrumbs %} {% block message-box %}

Zapisuję dane na serwerze.

@@ -56,64 +83,46 @@ {% endblock %} -{% block maincontent %} -
- {% for n in panel_list %} -
-
-

+{% block header-toolbar %} + +{% endblock %} - - - - - - {# Wydruk #} - -

-
-
- -
- {% endfor %} -
+{% block maincontent %} +
+
+
+
-
-
- - -

Wiadomość nie może być pusta.

- -

- - -

-
-
-
-
- - -
-{% endblock maincontent %} + {#
#} + {#
#} + {# #} + {# #} + {#

Wiadomość nie może być pusta.

#} + {# #} + {#

#} + {# #} + {# #} + {#

#} + {#
#} + {#
#} + {# #} + {#
#} + {#
#} + {# #} + {# #} + {#
#} +{% endblock maincontent %} diff --git a/project/templates/toolbar_api/scriptlets.js b/project/templates/toolbar_api/scriptlets.js index 801e5d3e..6f5258c9 100644 --- a/project/templates/toolbar_api/scriptlets.js +++ b/project/templates/toolbar_api/scriptlets.js @@ -8,8 +8,23 @@ function ScriptletCenter() { {% endfor %} _none: null - }; + }; } +ScriptletCenter.prototype.XMLEditorSelectedText = function(panel) { + return panel.contentView.editor.selection(); +} + +ScriptletCenter.prototype.XMLEditorReplaceSelectedText = function(panel, replacement) +{ + panel.contentView.editor.replaceSelection(replacement); + /* TODO: fire the change event */ +} + +ScriptletCenter.prototype.XMLEditorMoveCursorForward = function(panel, n) { + var pos = panel.contentView.editor.cursorPosition(); + panel.contentView.editor.selectLines(pos.line, pos.character + n); +} + scriptletCenter = new ScriptletCenter(); \ No newline at end of file