From ddfd0025304ea7087fdd2a4e06893a6f998b9f19 Mon Sep 17 00:00:00 2001 From: =?utf8?q?=C5=81ukasz=20Rekucki?= Date: Fri, 9 Oct 2009 17:46:33 +0200 Subject: [PATCH] =?utf8?q?Interaktywne=20b=C5=82=C4=99dy,=20gdy=20nie=20ud?= =?utf8?q?a=C5=82o=20si=C4=99=20wczyta=C4=87=20HTML'a.=20Tooltipy=20w=20pr?= =?utf8?q?zyciskach.?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- apps/api/handlers/library_handlers.py | 65 +++--- apps/api/handlers/toolbar_handlers.py | 4 +- apps/toolbar/models.py | 21 +- project/static/js/models.js | 44 ++++- project/static/js/views/button_toolbar.js | 9 +- project/static/js/views/editor.js | 228 +++++++++++----------- project/static/js/views/html.js | 20 +- project/static/js/views/split.js | 3 +- project/static/js/views/xml.js | 25 ++- project/templates/explorer/editor.html | 5 +- 10 files changed, 262 insertions(+), 162 deletions(-) diff --git a/apps/api/handlers/library_handlers.py b/apps/api/handlers/library_handlers.py index d7438a18..488c2d40 100644 --- a/apps/api/handlers/library_handlers.py +++ b/apps/api/handlers/library_handlers.py @@ -38,6 +38,7 @@ log = logging.getLogger('platforma.api') # # Document List Handlers # +# TODO: security check class BasicLibraryHandler(AnonymousBaseHandler): allowed_methods = ('GET',) @@ -51,10 +52,14 @@ class BasicLibraryHandler(AnonymousBaseHandler): return {'documents' : document_list} +# +# This handler controlls the document collection +# class LibraryHandler(BaseHandler): allowed_methods = ('GET', 'POST') anonymous = BasicLibraryHandler + @hglibrary def read(self, request, lib): """Return the list of documents.""" @@ -138,12 +143,15 @@ class LibraryHandler(BaseHandler): lock.release() except LibraryException, e: import traceback - return response.InternalError().django_response(\ - {'exception': traceback.format_exc()} ) + return response.InternalError().django_response({ + "reason": traceback.format_exc() + }) except DocumentAlreadyExists: # Document is already there - return response.EntityConflict().django_response(\ - {"reason": "Document %s already exists." % docid}) + return response.EntityConflict().django_response({ + "reason": "already-exists", + "message": "Document already exists." % docid + }) # # Document Handlers @@ -318,7 +326,8 @@ class DocumentTextHandler(BaseHandler): # we're done :) return document.data('xml') else: - xdoc = parser.WLDocument.from_string(document.data('xml')) + xdoc = parser.WLDocument.from_string(document.data('xml'),\ + parse_dublincore=False) ptext = xdoc.part_as_text(part) if ptext is None: @@ -547,33 +556,35 @@ class MergeHandler(BaseHandler): "provided": target_rev, "latest": udoc.revision }) - if not request.user.has_perm('explorer.document.can_share'): - # User is not permitted to make a merge, right away - # So we instead create a pull request in the database - try: - prq, created = PullRequest.objects.get_or_create( - source_revision = str(udoc.revision), - defaults = { - 'comitter': request.user, - 'document': docid, - 'status': "N", - 'comment': form.cleaned_data['message'] or '$AUTO$ Document shared.', - } - ) - - return response.RequestAccepted().django_response(\ - ticket_status=prq.status, \ - ticket_uri=reverse("pullrequest_view", args=[prq.id]) ) - except IntegrityError, e: - return response.InternalError().django_response() - if form.cleaned_data['type'] == 'update': # update is always performed from the file branch # to the user branch success, changed = udoc.update(request.user.username) - if form.cleaned_data['type'] == 'share': - success, changed = udoc.share(form.cleaned_data['message']) + if form.cleaned_data['type'] == 'share': + if not request.user.has_perm('explorer.document.can_share'): + # User is not permitted to make a merge, right away + # So we instead create a pull request in the database + try: + prq, created = PullRequest.objects.get_or_create( + source_revision = str(udoc.revision), + defaults = { + 'comitter': request.user, + 'document': docid, + 'status': "N", + 'comment': form.cleaned_data['message'] or '$AUTO$ Document shared.', + } + ) + + return response.RequestAccepted().django_response(\ + ticket_status=prq.status, \ + ticket_uri=reverse("pullrequest_view", args=[prq.id]) ) + except IntegrityError: + return response.EntityConflict().django_response({ + 'reason': 'request-already-exist' + }) + else: + success, changed = udoc.share(form.cleaned_data['message']) if not success: return response.EntityConflict().django_response({ diff --git a/apps/api/handlers/toolbar_handlers.py b/apps/api/handlers/toolbar_handlers.py index be773590..5408db3b 100644 --- a/apps/api/handlers/toolbar_handlers.py +++ b/apps/api/handlers/toolbar_handlers.py @@ -17,9 +17,7 @@ class ToolbarHandler(BaseHandler): def read(self, request): groups = toolbar.models.ButtonGroup.objects.all() - return [ {'name': g.name, 'position': g.position,\ - 'buttons': g.button_set.all()} for g in groups ] - + return [g.to_dict(with_buttons=True) for g in groups] class ScriptletsHandler(BaseHandler): allowed_methods = ('GET',) diff --git a/apps/toolbar/models.py b/apps/toolbar/models.py index 04146223..1b986c3e 100644 --- a/apps/toolbar/models.py +++ b/apps/toolbar/models.py @@ -14,6 +14,13 @@ class ButtonGroup(models.Model): def __unicode__(self): return self.name + def to_dict(self, with_buttons=False): + d = {'name': self.name, 'position': self.position} + + if with_buttons: + d['buttons'] = [ b.to_dict() for b in self.button_set.all() ] + + return d #class ButtonGroupManager(models.Manager): # @@ -67,8 +74,18 @@ class Button(models.Model): if self.key_mod & 0x01: mods.append('Alt') if self.key_mod & 0x02: mods.append('Ctrl') if self.key_mod & 0x04: mods.append('Shift') - mods.append('"'+self.key+'"') - return '+'.join(mods) + mods.append(str(self.key)) + return '[' + '+'.join(mods) + ']' + + def to_dict(self): + return { + 'label': self.label, + 'tooltip': (self.tooltip or '') + self.hotkey_name(), + 'key': self.key, + 'key_mod': self.key_mod, + 'params': self.params, + 'scriptlet_id': self.scriptlet_id + } def __unicode__(self): return self.label diff --git a/project/static/js/models.js b/project/static/js/models.js index 9a42bca1..c03aa1e7 100644 --- a/project/static/js/models.js +++ b/project/static/js/models.js @@ -176,7 +176,7 @@ Editor.HTMLModel = Editor.Model.extend({ this.set('state', 'loading'); // load the transformed data - messageCenter.addMessage('info', 'Wczytuję HTML...'); + // messageCenter.addMessage('info', 'Wczytuję HTML...'); $.ajax({ url: this.htmlURL, @@ -196,16 +196,42 @@ Editor.HTMLModel = Editor.Model.extend({ } this.set('data', data); this.set('state', 'synced'); - messageCenter.addMessage('success', 'Wczytałem HTML :-)'); + // messageCenter.addMessage('success', 'Wczytałem HTML :-)'); }, - loadingFailed: function() { + loadingFailed: function(response) { if (this.get('state') != 'loading') { alert('erroneous state:', this.get('state')); } - this.set('error', 'Nie udało się załadować panelu'); + + var json_response = null; + var message = ""; + + try { + json_response = $.evalJSON(response.responseText); + + if(json_response.reason == 'xml-parse-error') { + + message = json_response.message.replace(/(line\s+)(\d+)(\s+)/i, + "$1$2$3"); + + message = message.replace(/(line\s+)(\d+)(\,\s*column\s+)(\d+)/i, + "$1$2$3$4"); + + + } + else { + message = json_response.message || json_response.reason || "nieznany błąd."; + } + } + catch (e) { + message = response.statusText; + } + + this.set('error', '

Nie udało się wczytać widoku HTML:

' + message); + this.set('state', 'error'); - messageCenter.addMessage('error', 'Nie udało mi się wczytać HTML. Spróbuj ponownie :-('); + // messageCenter.addMessage('error', 'Nie udało mi się wczytać HTML. Spróbuj ponownie :-('); }, getXMLPart: function(elem, callback) @@ -548,12 +574,12 @@ $(function() toolbarUrl = $('#api-toolbar-url').text(); doc = new Editor.DocumentModel(); - var editor = new EditorView('#body-wrap', doc); - editor.freeze(); - var flashView = new FlashView('#flashview', messageCenter); - var splitView = new SplitView('#splitview', doc); + EditorView = new EditorView('#body-wrap', doc); + EditorView.freeze(); leftPanelView = new PanelContainerView('#left-panel-container', doc); rightPanelContainer = new PanelContainerView('#right-panel-container', doc); + + var flashView = new FlashView('#flashview', messageCenter); }); diff --git a/project/static/js/views/button_toolbar.js b/project/static/js/views/button_toolbar.js index 7e4b6eab..7f039985 100644 --- a/project/static/js/views/button_toolbar.js +++ b/project/static/js/views/button_toolbar.js @@ -47,6 +47,7 @@ var ButtonToolbarView = View.extend({ buttonPressed: function(event) { + var self = this; var target = event.target; var groupIndex = parseInt($(target).attr('ui:groupindex'), 10); @@ -57,9 +58,13 @@ var ButtonToolbarView = View.extend({ console.log('Executing', scriptletId, 'with params', params); try { - scriptletCenter.scriptlets[scriptletId](this.parent, params); + self.parent.freeze('Wykonuję akcję...'); + setTimeout(function() { + scriptletCenter.scriptlets[scriptletId](self.parent, params); + self.parent.unfreeze(); + }, 10); } catch(e) { - console.log("Scriptlet", scriptletId, "failed."); + console.log("Scriptlet", scriptletId, "failed.", e); } }, diff --git a/project/static/js/views/editor.js b/project/static/js/views/editor.js index 10c77f63..2793141f 100644 --- a/project/static/js/views/editor.js +++ b/project/static/js/views/editor.js @@ -1,134 +1,142 @@ /*global View render_template panels */ var EditorView = View.extend({ - _className: 'EditorView', - element: null, - model: null, - template: null, + _className: 'EditorView', + element: null, + model: null, + template: null, - init: function(element, model, template) { - this._super(element, model, template); + init: function(element, model, template) { + this._super(element, model, template); - this.quickSaveButton = $('#action-quick-save', this.element).bind('click.editorview', this.quickSave.bind(this)); - this.commitButton = $('#action-commit', this.element).bind('click.editorview', this.commit.bind(this)); - this.updateButton = $('#action-update', this.element).bind('click.editorview', this.update.bind(this)); - this.mergeButton = $('#action-merge', this.element).bind('click.editorview', this.merge.bind(this)); + this.quickSaveButton = $('#action-quick-save', this.element).bind('click.editorview', this.quickSave.bind(this)); + this.commitButton = $('#action-commit', this.element).bind('click.editorview', this.commit.bind(this)); + this.updateButton = $('#action-update', this.element).bind('click.editorview', this.update.bind(this)); + this.mergeButton = $('#action-merge', this.element).bind('click.editorview', this.merge.bind(this)); - this.model.addObserver(this, 'state', this.modelStateChanged.bind(this)); - this.modelStateChanged('state', this.model.get('state')); - - // Inicjalizacja okien jQuery Modal - $('#commit-dialog', this.element). - jqm({ - modal: true, - onShow: this.loadRelatedIssues.bind(this) - }); + this.model.addObserver(this, 'state', this.modelStateChanged.bind(this)); + this.modelStateChanged('state', this.model.get('state')); + + this.splitView = new SplitView('#splitview', doc); + + // Inicjalizacja okien jQuery Modal + $('#commit-dialog', this.element). + jqm({ + modal: true, + onShow: this.loadRelatedIssues.bind(this) + }); - $('#commit-dialog-cancel-button', this.element).click(function() { - $('#commit-dialog-error-empty-message').hide(); - $('#commit-dialog').jqmHide(); - }); + $('#commit-dialog-cancel-button', this.element).click(function() { + $('#commit-dialog-error-empty-message').hide(); + $('#commit-dialog').jqmHide(); + }); - // $('#split-dialog').jqm({ - // modal: true, - // onShow: $.fbind(self, self.loadSplitDialog) - // }). - // jqmAddClose('button.dialog-close-button'); + // $('#split-dialog').jqm({ + // modal: true, + // onShow: $.fbind(self, self.loadSplitDialog) + // }). + // jqmAddClose('button.dialog-close-button'); - this.model.load(); - }, + this.model.load(); + }, - quickSave: function(event) { - this.model.saveDirtyContentModel(); - }, + quickSave: function(event) { + this.model.saveDirtyContentModel(); + }, - commit: function(event) { - $('#commit-dialog', this.element).jqmShow({callback: this.doCommit.bind(this)}); - }, + commit: function(event) { + $('#commit-dialog', this.element).jqmShow({ + callback: this.doCommit.bind(this) + }); + }, - doCommit: function(message) { - this.model.saveDirtyContentModel(message); - }, + doCommit: function(message) { + this.model.saveDirtyContentModel(message); + }, - update: function(event) { - this.model.update(); - }, + update: function(event) { + this.model.update(); + }, - merge: function(event) { - $('#commit-dialog', this.element).jqmShow({callback: this.doMerge.bind(this)}); - }, + merge: function(event) { + $('#commit-dialog', this.element).jqmShow({ + callback: this.doMerge.bind(this) + }); + }, - doMerge: function(message) { - this.model.merge(message); - }, + doMerge: function(message) { + this.model.merge(message); + }, - loadRelatedIssues: function(hash) { - var self = this; - var c = $('#commit-dialog-related-issues'); + loadRelatedIssues: function(hash) { + var self = this; + var c = $('#commit-dialog-related-issues'); - $('#commit-dialog-save-button').click(function(event, data) - { - if ($('#commit-dialog-message').val().match(/^\s*$/)) { - $('#commit-dialog-error-empty-message').fadeIn(); - } else { - $('#commit-dialog-error-empty-message').hide(); - $('#commit-dialog').jqmHide(); + $('#commit-dialog-save-button').click(function(event, data) + { + if ($('#commit-dialog-message').val().match(/^\s*$/)) { + $('#commit-dialog-error-empty-message').fadeIn(); + } else { + $('#commit-dialog-error-empty-message').hide(); + $('#commit-dialog').jqmHide(); - var message = $('#commit-dialog-message').val(); - $('#commit-dialog-related-issues input:checked') - .each(function() { message += ' refs #' + $(this).val(); }); - console.log("COMMIT APROVED", hash.t); - hash.t.callback(message); - } - return false; - }); + var message = $('#commit-dialog-message').val(); + $('#commit-dialog-related-issues input:checked') + .each(function() { + message += ' refs #' + $(this).val(); + }); + console.log("COMMIT APROVED", hash.t); + hash.t.callback(message); + } + return false; + }); - $("div.loading-box", c).show(); - $("div.fatal-error-box", c).hide(); - $("div.container-box", c).hide(); + $("div.loading-box", c).show(); + $("div.fatal-error-box", c).hide(); + $("div.container-box", c).hide(); - $.getJSON(c.attr('ui:ajax-src') + '?callback=?', - function(data, status) - { - var fmt = ''; - $(data).each( function() { - fmt += '\n'; - }); - $("div.container-box", c).html(fmt); - $("div.loading-box", c).hide(); - $("div.container-box", c).show(); - }); + $.getJSON(c.attr('ui:ajax-src') + '?callback=?', + function(data, status) + { + var fmt = ''; + $(data).each( function() { + fmt += '\n'; + }); + $("div.container-box", c).html(fmt); + $("div.loading-box", c).hide(); + $("div.container-box", c).show(); + }); - hash.w.show(); - }, + hash.w.show(); + }, - modelStateChanged: function(property, value) { - // Uaktualnia stan przycisków - if (value == 'dirty') { - this.quickSaveButton.attr('disabled', null); - this.commitButton.attr('disabled', null); - this.updateButton.attr('disabled', 'disabled'); - this.mergeButton.attr('disabled', 'disabled'); - } else if (value == 'synced') { - this.quickSaveButton.attr('disabled', 'disabled'); - this.commitButton.attr('disabled', 'disabled'); - this.updateButton.attr('disabled', null); - this.mergeButton.attr('disabled', null); - } else if (value == 'empty') { - this.quickSaveButton.attr('disabled', 'disabled'); - this.commitButton.attr('disabled', 'disabled'); - this.updateButton.attr('disabled', 'disabled'); - this.mergeButton.attr('disabled', 'disabled'); - } - }, + modelStateChanged: function(property, value) { + // Uaktualnia stan przycisków + if (value == 'dirty') { + this.quickSaveButton.attr('disabled', null); + this.commitButton.attr('disabled', null); + this.updateButton.attr('disabled', 'disabled'); + this.mergeButton.attr('disabled', 'disabled'); + } else if (value == 'synced') { + this.quickSaveButton.attr('disabled', 'disabled'); + this.commitButton.attr('disabled', 'disabled'); + this.updateButton.attr('disabled', null); + this.mergeButton.attr('disabled', null); + } else if (value == 'empty') { + this.quickSaveButton.attr('disabled', 'disabled'); + this.commitButton.attr('disabled', 'disabled'); + this.updateButton.attr('disabled', 'disabled'); + this.mergeButton.attr('disabled', 'disabled'); + } + }, - dispose: function() { - $('#action-quick-save', this.element).unbind('click.editorview'); - $('#action-commit', this.element).unbind('click.editorview'); - $('#action-update', this.element).unbind('click.editorview'); - $('#action-merge', this.element).unbind('click.editorview'); + dispose: function() { + $('#action-quick-save', this.element).unbind('click.editorview'); + $('#action-commit', this.element).unbind('click.editorview'); + $('#action-update', this.element).unbind('click.editorview'); + $('#action-merge', this.element).unbind('click.editorview'); - this.model.removeObserver(this); - this._super(); - } + this.model.removeObserver(this); + this._super(); + } }); diff --git a/project/static/js/views/html.js b/project/static/js/views/html.js index d9de4926..a3db1d28 100644 --- a/project/static/js/views/html.js +++ b/project/static/js/views/html.js @@ -25,7 +25,10 @@ var HTMLView = View.extend({ this.$printLink.attr('href', base + "?revision=" + this.model.get('revision')); }, - modelStateChanged: function(property, value) { + modelStateChanged: function(property, value) + { + var self = $(this); + if (value == 'synced' || value == 'dirty') { this.unfreeze(); } else if (value == 'unsynced') { @@ -36,6 +39,21 @@ var HTMLView = View.extend({ this.freeze('Zapisywanie...'); } else if (value == 'error') { this.freeze(this.model.get('error')); + $('.xml-editor-ref', this.overlay).click( + function(event) { + console.log("Sending scroll rq.", this); + try { + var href = $(this).attr('href').split('-'); + var line = parseInt(href[1]); + var column = parseInt(href[2]); + + $(document).trigger('xml-scroll-request', {line:line, column:column}); + } catch(e) { + console.log(e); + } + + return false; + }); } }, diff --git a/project/static/js/views/split.js b/project/static/js/views/split.js index 48f0de73..30eda4ab 100644 --- a/project/static/js/views/split.js +++ b/project/static/js/views/split.js @@ -17,7 +17,7 @@ var SplitView = View.extend({ init: function(element, model) { this._super(element, model, null); this.element.css('position', 'relative'); - this._resizingSubviews = false; + this._resizingSubviews = false; this.views = $(">*", this.element[0]).css({ position: 'absolute', // positioned inside splitter container @@ -28,6 +28,7 @@ var SplitView = View.extend({ this.leftView = $(this.views[0]); this.rightView = $(this.views[1]); + this.splitbar = $(this.views[2] || '
') .insertAfter(this.leftView) .css({ diff --git a/project/static/js/views/xml.js b/project/static/js/views/xml.js index 00547d19..1681caee 100644 --- a/project/static/js/views/xml.js +++ b/project/static/js/views/xml.js @@ -1,4 +1,4 @@ -/*global View CodeMirror ButtonToolbarView render_template panels */ +/*global View CodeMirror ToolbarView render_template panels */ var XMLView = View.extend({ _className: 'XMLView', element: null, @@ -18,8 +18,11 @@ var XMLView = View.extend({ var self = this; $('.xmlview-toolbar', this.element).bind('resize.xmlview', this.resized.bind(this)); - - + + // scroll to the given position (if availble) + this.scrollCallback = this.scrollOnRequest.bind(this); + $(document).bind('xml-scroll-request', this.scrollCallback); + this.parent.freeze('Ładowanie edytora...'); this.editor = new CodeMirror($('.xmlview', this.element).get(0), { parserfile: 'parsexml.js', @@ -28,7 +31,7 @@ var XMLView = View.extend({ parserConfig: { useHTMLKludges: false }, - textWrapping: false, + textWrapping: true, tabMode: 'spaces', indentUnit: 0, onChange: this.editorDataChanged.bind(this), @@ -91,6 +94,8 @@ var XMLView = View.extend({ }, dispose: function() { + $(document).unbind('xml-scroll-request', this.scrollCallback); + this.model.removeObserver(this); $(this.editor.frame).remove(); this._super(); @@ -104,7 +109,7 @@ var XMLView = View.extend({ var ch = String.fromCharCode(code & 0xff).toLowerCase(); /* # console.log(ch.charCodeAt(0), '#', buttons); */ - var buttons = $('.buttontoolbarview-button[title='+ch+']', this.element); + var buttons = $('.buttontoolbarview-button[hotkey='+ch+']', this.element); var mod = 0; if(event.altKey) mod |= 0x01; @@ -141,6 +146,16 @@ var XMLView = View.extend({ this.buttonToolbar.buttonPressed({ target: button }); + }, + + scrollOnRequest: function(event, data) + { + try { + var line = this.editor.nthLine(data.line); + this.editor.selectLines(line, (data.column-1)); + } catch(e) { + console.log('Exception in scrollOnRequest:', e); + } } }); diff --git a/project/templates/explorer/editor.html b/project/templates/explorer/editor.html index b338e6d3..3c775d4e 100644 --- a/project/templates/explorer/editor.html +++ b/project/templates/explorer/editor.html @@ -123,8 +123,9 @@