Merge branch 'master' of stigma.nowoczesnapolska.org.pl:platforma
authorzuber <marek@stepniowski.com>
Tue, 20 Oct 2009 16:08:21 +0000 (18:08 +0200)
committerzuber <marek@stepniowski.com>
Tue, 20 Oct 2009 16:08:21 +0000 (18:08 +0200)
Conflicts:
apps/api/handlers/library_handlers.py

20 files changed:
apps/api/forms.py
apps/api/handlers/library_handlers.py
apps/api/handlers/text_handler.py [new file with mode: 0644]
apps/api/resources.py
apps/api/response.py
lib/wlrepo/__init__.py
lib/wlrepo/mercurial_backend/__init__.py
lib/wlrepo/mercurial_backend/document.py
lib/wlrepo/mercurial_backend/library.py
lib/wlrepo/mercurial_backend/revision.py [new file with mode: 0644]
platforma/static/css/html.css
platforma/static/js/app.js
platforma/static/js/lib/codemirror/codemirror.js
platforma/static/js/lib/codemirror/editor.js
platforma/static/js/lib/codemirror/select.js
platforma/static/js/lib/codemirror/undo.js
platforma/static/js/lib/codemirror/util.js
platforma/static/js/models.js
platforma/static/js/views/editor.js
platforma/urls.py

index c5e0b67..22cadb9 100644 (file)
@@ -11,7 +11,6 @@ from django.contrib.auth.models import User
 import re
 from django.utils import simplejson as json
 
-
 class MergeRequestForm(forms.Form):
     # should the target document revision be updated or shared
     type = forms.ChoiceField(choices=(('update', 'Update'), ('share', 'Share')) )
@@ -63,9 +62,9 @@ PRQ_USER_RE = re.compile(r"^\$prq-(\d{1,10})$", re.UNICODE)
 class DocumentRetrieveForm(forms.Form):    
     revision = forms.RegexField(regex=r'latest|[0-9a-z]{40}', required=False)
     user = forms.CharField(required=False)
-
+        
     def clean_user(self):
-        # why, oh why does django doesn't implement this!!!
+        # why, oh why doesn't django implement this!!!
         # value = super(DocumentRetrieveForm, self).clean_user()
         value = self.cleaned_data['user']        
         
@@ -89,13 +88,34 @@ class DocumentRetrieveForm(forms.Form):
            
 
 class TextRetrieveForm(DocumentRetrieveForm):
-    part = forms.CharField(required=False)
+    chunk = forms.CharField(required=False)
+    format = forms.CharField(required=False)
+
+    def clean_format(self):
+        value = self.cleaned_data['format']
+        if not value:
+            return 'raw'
+
+        if value not in ('nl', 'raw'):
+            raise forms.ValidationError("Invalid text format")
+        return value
 
 class TextUpdateForm(DocumentRetrieveForm):
     message = forms.CharField(required=False)
     contents = forms.CharField(required=False)
     chunks = forms.CharField(required=False)
 
+    format = forms.CharField(required=False)
+
+    def clean_format(self):
+        value = self.cleaned_data['format']
+        if not value:
+            return 'raw'
+
+        if value not in ('nl', 'raw'):
+            raise forms.ValidationError("Invalid text format")
+        return value
+
     def clean_message(self):
         value = self.cleaned_data['message']
 
index 9751fef..c84cab8 100644 (file)
@@ -11,11 +11,9 @@ __doc__ = "Module documentation."
 from piston.handler import BaseHandler, AnonymousBaseHandler
 from django.http import HttpResponse
 
-import re
 from datetime import date
 
 from django.core.urlresolvers import reverse
-from django.utils import simplejson as json
 from django.db import IntegrityError
 
 import librarian
@@ -392,162 +390,6 @@ class DocumentGalleryHandler(BaseHandler):
 
         return galleries
 
-#
-# Document Text View
-#
-
-XINCLUDE_REGEXP = r"""<(?:\w+:)?include\s+[^>]*?href=("|')wlrepo://(?P<link>[^\1]+?)\1\s*[^>]*?>"""
-#
-#
-#
-
-class DocumentTextHandler(BaseHandler):
-    allowed_methods = ('GET', 'POST')
-
-    @validate_form(forms.TextRetrieveForm, 'GET')
-    @hglibrary
-    def read(self, request, form, docid, lib):
-        """Read document as raw text"""        
-        try:
-            revision = form.cleaned_data['revision']
-            part = form.cleaned_data['part']
-            user = form.cleaned_data['user'] or request.user.username            
-            
-            document = lib.document_for_rev(revision)
-            
-            if document.id != docid:
-                return response.BadRequest().django_response({
-                    'reason': 'name-mismatch',
-                    'message': 'Provided revision is not valid for this document'
-                })
-
-            if document.owner != user:
-                return response.BadRequest().django_response({
-                    'reason': 'user-mismatch',
-                    'message': "Provided revision doesn't belong to user %s" % user
-                })
-
-            for error in check_user(request, user):
-                return error
-            
-            if not part:                
-                return document.data('xml')
-            
-            xdoc = parser.WLDocument.from_string(document.data('xml'),\
-                parse_dublincore=False)
-            ptext = xdoc.part_as_text(part)
-
-            if ptext is None:
-                return response.EntityNotFound().django_response({
-                      'reason': 'no-part-in-document'                     
-                })
-
-            return ptext
-        except librarian.ParseError, e:
-            return response.EntityNotFound().django_response({
-                'reason': 'invalid-document-state',
-                'exception': type(e),
-                'message': e.message
-            })
-        except (EntryNotFound, RevisionNotFound), e:
-            return response.EntityNotFound().django_response({
-                'reason': 'not-found',
-                'exception': type(e), 'message': e.message
-            })   
-
-    @validate_form(forms.TextUpdateForm, 'POST')
-    @hglibrary
-    def create(self, request, form, docid, lib):
-        try:
-            revision = form.cleaned_data['revision']
-            msg = form.cleaned_data['message']
-            user = form.cleaned_data['user'] or request.user.username
-
-            # do not allow changing not owned documents
-            # (for now... )
-            
-            
-            if user != request.user.username:
-                return response.AccessDenied().django_response({
-                    'reason': 'insufficient-priviliges',
-                })
-            
-            current = lib.document(docid, user)
-            orig = lib.document_for_rev(revision)
-
-            if current != orig:
-                return response.EntityConflict().django_response({
-                        "reason": "out-of-date",
-                        "provided_revision": orig.revision,
-                        "latest_revision": current.revision })
-            
-            if form.cleaned_data.has_key('contents'):
-                data = form.cleaned_data['contents']
-            else:                               
-                chunks = form.cleaned_data['chunks']
-                xdoc = parser.WLDocument.from_string(current.data('xml'))
-                errors = xdoc.merge_chunks(chunks)
-
-                if len(errors):
-                    return response.EntityConflict().django_response({
-                            "reason": "invalid-chunks",
-                            "message": "Unable to merge following parts into the document: %s " % ",".join(errors)
-                    })
-
-                data = xdoc.serialize()
-
-            # try to find any Xinclude tags
-            includes = [m.groupdict()['link'] for m in (re.finditer(\
-                XINCLUDE_REGEXP, data, flags=re.UNICODE) or []) ]
-
-            log.info("INCLUDES: %s", includes)
-
-            # TODO: provide useful routines to make this simpler
-            def xml_update_action(lib, resolve):
-                try:
-                    f = lib._fileopen(resolve('parts'), 'r')
-                    stored_includes = json.loads(f.read())
-                    f.close()
-                except:
-                    stored_includes = []
-                
-                if stored_includes != includes:
-                    f = lib._fileopen(resolve('parts'), 'w+')
-                    f.write(json.dumps(includes))
-                    f.close()
-
-                    lib._fileadd(resolve('parts'))
-
-                    # update the parts cache
-                    PartCache.update_cache(docid, current.owner,\
-                        stored_includes, includes)
-
-                # now that the parts are ok, write xml
-                f = lib._fileopen(resolve('xml'), 'w+')
-                f.write(data.encode('utf-8'))
-                f.close()
-
-            ndoc = None
-            ndoc = current.invoke_and_commit(\
-                xml_update_action, lambda d: (msg, user) )
-
-            try:
-                # return the new revision number
-                return response.SuccessAllOk().django_response({
-                    "document": ndoc.id,
-                    "user": user,
-                    "subview": "xml",
-                    "previous_revision": current.revision,
-                    "revision": ndoc.revision,
-                    'timestamp': ndoc.revision.timestamp,
-                    "url": reverse("doctext_view", args=[ndoc.id])
-                })
-            except Exception, e:
-                if ndoc: lib._rollback()
-                raise e        
-        except RevisionNotFound, e:
-            return response.EntityNotFound(mimetype="text/plain").\
-                django_response(e.message)
 
 
 #
@@ -651,6 +493,11 @@ class MergeHandler(BaseHandler):
             # update is always performed from the file branch
             # to the user branch
             user_doc_new = base_doc.update(request.user.username)
+
+            if user_doc_new == user_doc:
+                return response.SuccessAllOk().django_response({
+                    "result": "no-op"
+                })
                 
             # shared document is the same
             doc_new = doc
@@ -659,12 +506,17 @@ class MergeHandler(BaseHandler):
             if not base_doc.up_to_date():
                 return response.BadRequest().django_response({
                     "reason": "not-fast-forward",
-                    "message": "You must first update yout branch to the latest version."
+                    "message": "You must first update your branch to the latest version."
+                })
+
+            if base_doc.parentof(doc) or base_doc.has_parent_from(doc):
+                return response.SuccessAllOk().django_response({
+                    "result": "no-op"
                 })
 
             # check for unresolved conflicts            
             if base_doc.has_conflict_marks():
-                return response.BadRequest().django_response({
+                return response.BadRequest().django_response({                    
                     "reason": "unresolved-conflicts",
                     "message": "There are unresolved conflicts in your file. Fix them, and try again."
                 })
@@ -714,6 +566,7 @@ class MergeHandler(BaseHandler):
         # Same goes for shared view
         
         return response.SuccessAllOk().django_response({
+            "result": "success",
             "name": user_doc_new.id,
             "user": user_doc_new.owner,
 
diff --git a/apps/api/handlers/text_handler.py b/apps/api/handlers/text_handler.py
new file mode 100644 (file)
index 0000000..5e34ab4
--- /dev/null
@@ -0,0 +1,184 @@
+# -*- encoding: utf-8 -*-
+
+__author__= "Łukasz Rekucki"
+__date__ = "$2009-10-19 14:34:42$"
+__doc__ = "Module documentation."
+
+#import api.forms as forms
+#import api.response as response
+#
+#from api.utils import validate_form, hglibrary
+#from api.models import PartCache
+#
+
+#
+#from piston.handler import BaseHandler
+#
+#from wlrepo import *
+
+import re
+from library_handlers import *
+
+import librarian
+from librarian import parser
+
+#
+# Document Text View
+#
+
+XINCLUDE_REGEXP = r"""<(?:\w+:)?include\s+[^>]*?href=("|')wlrepo://(?P<link>[^\1]+?)\1\s*[^>]*?>"""
+#
+#
+#
+
+class DocumentTextHandler(BaseHandler):
+    allowed_methods = ('GET', 'POST')
+
+    @validate_form(forms.TextRetrieveForm, 'GET')
+    @hglibrary
+    def read(self, request, form, docid, lib):
+        """Read document as raw text"""
+        try:
+            revision = form.cleaned_data['revision']
+            chunk = form.cleaned_data['chunk']
+            user = form.cleaned_data['user'] or request.user.username
+            format = form.cleaned_data['format']
+
+            document = lib.document_for_rev(revision)
+
+            if document.id != docid:
+                return response.BadRequest().django_response({
+                    'reason': 'name-mismatch',
+                    'message': 'Provided revision is not valid for this document'
+                })
+
+            if document.owner != user:
+                return response.BadRequest().django_response({
+                    'reason': 'user-mismatch',
+                    'message': "Provided revision doesn't belong to user %s" % user
+                })
+
+            for error in check_user(request, user):
+                return error
+
+            if not chunk:
+                return document.data('xml')
+
+            xdoc = parser.WLDocument.from_string(document.data('xml'),\
+                parse_dublincore=False)
+
+            xchunk = xdoc.chunk(chunk)
+
+            if xchunk is None:
+                return response.EntityNotFound().django_response({
+                      'reason': 'no-part-in-document',
+                      'path': chunk
+                })
+
+            return librarian.serialize_children(xchunk, format=format)
+
+        except librarian.ParseError, e:
+            return response.EntityNotFound().django_response({
+                'reason': 'invalid-document-state',
+                'exception': type(e),
+                'message': e.message
+            })
+        except (EntryNotFound, RevisionNotFound), e:
+            return response.EntityNotFound().django_response({
+                'reason': 'not-found',
+                'exception': type(e), 'message': e.message
+            })
+
+    @validate_form(forms.TextUpdateForm, 'POST')
+    @hglibrary
+    def create(self, request, form, docid, lib):
+        try:
+            revision = form.cleaned_data['revision']
+            msg = form.cleaned_data['message']
+            user = form.cleaned_data['user'] or request.user.username
+
+            # do not allow changing not owned documents
+            # (for now... )
+
+
+            if user != request.user.username:
+                return response.AccessDenied().django_response({
+                    'reason': 'insufficient-priviliges',
+                })
+
+            current = lib.document(docid, user)
+            orig = lib.document_for_rev(revision)
+
+            if current != orig:
+                return response.EntityConflict().django_response({
+                        "reason": "out-of-date",
+                        "provided_revision": orig.revision,
+                        "latest_revision": current.revision })
+
+            if form.cleaned_data.has_key('contents'):
+                data = form.cleaned_data['contents']
+            else:
+                chunks = form.cleaned_data['chunks']
+                xdoc = parser.WLDocument.from_string(current.data('xml'))
+                errors = xdoc.merge_chunks(chunks)
+
+                if len(errors):
+                    return response.EntityConflict().django_response({
+                            "reason": "invalid-chunks",
+                            "message": "Unable to merge following parts into the document: %s " % ",".join(errors)
+                    })
+
+                data = xdoc.serialize()
+
+            # try to find any Xinclude tags
+            includes = [m.groupdict()['link'] for m in (re.finditer(\
+                XINCLUDE_REGEXP, data, flags=re.UNICODE) or []) ]
+
+            log.info("INCLUDES: %s", includes)
+
+            # TODO: provide useful routines to make this simpler
+            def xml_update_action(lib, resolve):
+                try:
+                    f = lib._fileopen(resolve('parts'), 'r')
+                    stored_includes = json.loads(f.read())
+                    f.close()
+                except:
+                    stored_includes = []
+
+                if stored_includes != includes:
+                    f = lib._fileopen(resolve('parts'), 'w+')
+                    f.write(json.dumps(includes))
+                    f.close()
+
+                    lib._fileadd(resolve('parts'))
+
+                    # update the parts cache
+                    PartCache.update_cache(docid, current.owner,\
+                        stored_includes, includes)
+
+                # now that the parts are ok, write xml
+                f = lib._fileopen(resolve('xml'), 'w+')
+                f.write(data.encode('utf-8'))
+                f.close()
+
+            ndoc = None
+            ndoc = current.invoke_and_commit(\
+                xml_update_action, lambda d: (msg, user) )
+
+            try:
+                # return the new revision number
+                return response.SuccessAllOk().django_response({
+                    "document": ndoc.id,
+                    "user": user,
+                    "subview": "xml",
+                    "previous_revision": current.revision,
+                    "revision": ndoc.revision,
+                    'timestamp': ndoc.revision.timestamp,
+                    "url": reverse("doctext_view", args=[ndoc.id])
+                })
+            except Exception, e:
+                if ndoc: lib._rollback()
+                raise e
+        except RevisionNotFound, e:
+            return response.EntityNotFound(mimetype="text/plain").\
+                django_response(e.message)
index 4e0a118..fbdb89b 100644 (file)
@@ -14,9 +14,11 @@ authdata = {'authentication': DjangoAuth()}
 #
 
 import api.handlers.library_handlers as dh
+from api.handlers.text_handler import DocumentTextHandler
+
 library_resource = Resource(dh.LibraryHandler, **authdata)
 document_resource = Resource(dh.DocumentHandler, **authdata)
-document_text_resource = Resource(dh.DocumentTextHandler, **authdata)
+document_text_resource = Resource(DocumentTextHandler, **authdata)
 document_html_resource = Resource(dh.DocumentHTMLHandler, **authdata)
 # document_dc_resource = Resource(dh.DocumentDublinCoreHandler, **authdata)
 document_gallery = Resource(dh.DocumentGalleryHandler, **authdata)
index b5513c4..4675015 100644 (file)
@@ -49,6 +49,7 @@ class RequestAccepted(ResponseObject):
 
     def django_response(self, ticket_status, ticket_uri):
         return ResponseObject.django_response(self, {
+            'result': 'accepted',
             'status': ticket_status,
             'refer_to': ticket_uri })     
         
index 0f373d4..f8c07c6 100644 (file)
@@ -73,6 +73,9 @@ class Document(object):
     def parentof(self, other):
         return self._revision.parentof(other._revision)
 
+    def has_parent_from(self, other):
+        return self._revision.has_parent_from(other._revision)
+
     def ancestorof(self, other):
         return self._revision.ancestorof(other._revision)
 
@@ -129,7 +132,7 @@ class DocumentAlreadyExists(LibraryException):
 
 def open_library(path, proto, *args, **kwargs):
     if proto == 'hg':
-        import wlrepo.mercurial_backend
-        return wlrepo.mercurial_backend.MercurialLibrary(path, *args, **kwargs)
+        import wlrepo.mercurial_backend.library
+        return wlrepo.mercurial_backend.library.MercurialLibrary(path, *args, **kwargs)
 
     raise NotImplemented()
\ No newline at end of file
index 22a9d52..f797d39 100644 (file)
@@ -8,97 +8,7 @@ __date__ = "$2009-09-25 09:20:22$"
 __doc__ = "Module documentation."
 
 import wlrepo
-from mercurial.node import nullid
-
-from mercurial import encoding
-encoding.encoding = 'utf-8'
-
-class MercurialRevision(wlrepo.Revision):
-
-    def __init__(self, lib, changectx):
-        super(MercurialRevision, self).__init__(lib)
-        self._changectx = changectx
-        
-        branchname = self._changectx.branch()
-        if branchname.startswith("$doc:"):
-            self._docname = branchname[5:]
-            self._username = None
-        elif branchname.startswith("$user:"):
-            idx = branchname.find("$doc:")
-            if(idx < 0):
-                raise ValueError("Revision %s is not a valid document revision." % changectx.hex());
-            self._username = branchname[6:idx]
-            self._docname = branchname[idx+5:]
-        else:
-            raise ValueError("Revision %s is not a valid document revision." % changectx.hex());
-        
-    @property
-    def document_name(self):
-        return self._docname and self._docname.decode('utf-8')
-
-    @property
-    def user_name(self):
-        return self._username and self._username.decode('utf-8')
-
-    def hgrev(self):
-        return self._changectx.node()
-        
-    def hgcontext(self):
-        return self._changectx
-
-    def hgbranch(self):
-        return self._changectx.branch()
-
-    @property
-    def timestamp(self):
-        return self._changectx.date()[0]
-
-    def __unicode__(self):
-        return u"%s" % self._changectx.hex()
-
-    def __str__(self):
-        return self.__unicode__().encode('utf-8')
-
-    def __repr__(self):
-        return "%s" % self._changectx.hex()
-
-    def ancestorof(self, other):
-        nodes = list(other._changectx._parents)
-        while nodes[0].node() != nullid:
-            v = nodes.pop(0)
-            if v == self._changectx:
-                return True
-            nodes.extend( v._parents )
-        return False
-
-    def parentof(self, other):
-        return self._changectx in other._changectx._parents
-
-    def has_common_ancestor(self, other):
-        a = self._changectx.ancestor(other._changectx)       
-        return (a.branch() == self._changectx.branch())
-
-    def has_children(self):
-        return bool(self._library._hgrepo.changelog.children(self.hgrev()))
-
-    def merge_with(self, other, user, message):
-        message = self._library._sanitize_string(message)
-        lock = self._library.lock(True)
-        try:
-            self._library._checkout(self._changectx.node())
-            status = self._library._merge(other._changectx.node())
-            if status.isclean():
-                self._library._commit(user=user, message=message)
-                return True
-            else:
-                return False
-        finally:
-            lock.release()
-
-    def __eq__(self, other):
-        return self._changectx.node() == other._changectx.node()
-
-
-from wlrepo.mercurial_backend.library import MercurialLibrary
 
 
+from mercurial import encoding
+encoding.encoding = 'utf-8'
\ No newline at end of file
index 3911d04..f7da32d 100644 (file)
@@ -106,10 +106,19 @@ class MercurialDocument(wlrepo.Document):
         return self._library.document_for_rev(fullid)
 
     def up_to_date(self):
-        return self.ismain() or (\
-            self.shared().ancestorof(self) )
+        if self.ismain():
+            return True
+
+        shared = self.shared()
         
-            
+        if shared.parentof(self):
+            return True
+
+        if shared.has_parent_from(self):
+            return True
+
+        return False
+                    
     def update(self, user):
         """Update parts of the document."""
         lock = self.library.lock()
@@ -117,78 +126,69 @@ class MercurialDocument(wlrepo.Document):
             if self.ismain():
                 # main revision of the document
                 return self
-            
-            if self._revision.has_children():                
-                raise UpdateException("Revision has children.")
 
-            sv = self.shared()
+            # check for children in this branch
+            if self._revision.has_children(limit_branch=True):
+                raise wlrepo.UpdateException("Revision %s has children." % self.revision)
 
-            if self.parentof(sv):
+            shared = self.shared()
+
+            # the shared version comes from our version
+            if self.parentof(self.shared()):
+                return self
+
+            # no changes since last update
+            if shared.ancestorof(self):
                 return self
 
-            if sv.ancestorof(self):
+            # last share was from this branch
+            if shared.has_parent_from(self):
                 return self
 
             if self._revision.merge_with(sv._revision, user=user,\
                 message="$AUTO$ Personal branch update."):
                 return self.latest()
             else:
-                raise UpdateException("Merge failed.")
+                raise wlrepo.UpdateException("Merge failed.")
         finally:
             lock.release()  
 
     def share(self, message):
         lock = self.library.lock()
         try:            
+
+            # nothing to do
             if self.ismain():
-                return False # always shared
+                return self
 
-            user = self._revision.user_name
-            main = self.shared()._revision
-            local = self._revision            
+            shared = self.shared()
 
-            # Case 1:
+            # we just did this - move on
+            if self.parentof(shared):
+                return shared
+
+            # No changes since update
+            if shared.parentof(self):
+                return shared
+
+            # The good situation
+            #
             #         * local
             #         |
-            #         * <- can also be here!
-            #        /|
+            #        >* 
+            #        ||
             #       / |
             # main *  *
             #      |  |
-            # The local branch has been recently updated,
-            # so we don't need to update yet again, but we need to
-            # merge down to default branch, even if there was
-            # no commit's since last update
-            #
-            # This is actually the only good case!
-            if main.ancestorof(local):
-                success, changed = main.merge_with(local, user=user, message=message)
+            if shared.ancestorof(self):               
+                success = shared._revision.merge_with(self._revision, user=self.owner, message=message)
 
                 if not success:
-                    raise LibraryException("Merge failed.")
-
-                return changed
-            
-            # Case 2:
-            # main *
-            #      |
-            #      * <- this case overlaps with previos one
-            #      |\
-            #      | \
-            #      |  * local
-            #      |  |
-            #
-            # There was a recent merge to the defaul branch and
-            # no changes to local branch recently.
-            #
-            # Nothing to do
-            elif local.ancestorof(main):                
-                return False
+                    raise wlrepo.LibraryException("Merge failed.")
+                
+                return shared.latest()
 
-            # In all other cases, the local needs an update
-            # and possibly conflict resolution, so fail
-            raise LibraryExcepton("Document not prepared for sharing.")
-        
+            raise wlrepo.LibraryException("Unrecognized share-state.")
         finally:
             lock.release()
 
index 84e7654..cde7af9 100644 (file)
@@ -13,7 +13,7 @@ from mercurial import ui as hgui
 from mercurial import error
 import wlrepo
 
-from wlrepo.mercurial_backend import MercurialRevision
+from wlrepo.mercurial_backend.revision import MercurialRevision
 from wlrepo.mercurial_backend.document import MercurialDocument
 
 class MergeStatus(object):
diff --git a/lib/wlrepo/mercurial_backend/revision.py b/lib/wlrepo/mercurial_backend/revision.py
new file mode 100644 (file)
index 0000000..f05637d
--- /dev/null
@@ -0,0 +1,103 @@
+# -*- encoding: utf-8 -*-
+
+__author__= "Łukasz Rekucki"
+__date__ = "$2009-10-20 12:31:48$"
+__doc__ = "Module documentation."
+
+import wlrepo
+from mercurial.node import nullid
+
+class MercurialRevision(wlrepo.Revision):
+    def __init__(self, lib, changectx):
+        super(MercurialRevision, self).__init__(lib)
+        self._changectx = changectx
+
+        branchname = self._changectx.branch()
+        if branchname.startswith("$doc:"):
+            self._docname = branchname[5:]
+            self._username = None
+        elif branchname.startswith("$user:"):
+            idx = branchname.find("$doc:")
+            if(idx < 0):
+                raise ValueError("Revision %s is not a valid document revision." % changectx.hex());
+            self._username = branchname[6:idx]
+            self._docname = branchname[idx+5:]
+        else:
+            raise ValueError("Revision %s is not a valid document revision." % changectx.hex());
+
+    @property
+    def document_name(self):
+        return self._docname and self._docname.decode('utf-8')
+
+    @property
+    def user_name(self):
+        return self._username and self._username.decode('utf-8')
+
+    def hgrev(self):
+        return self._changectx.node()
+
+    def hgcontext(self):
+        return self._changectx
+
+    def hgbranch(self):
+        return self._changectx.branch()
+
+    @property
+    def timestamp(self):
+        return self._changectx.date()[0]
+
+    def __unicode__(self):
+        return u"%s" % self._changectx.hex()
+
+    def __str__(self):
+        return self.__unicode__().encode('utf-8')
+
+    def __repr__(self):
+        return "%s" % self._changectx.hex()
+
+    def ancestorof(self, other):
+        nodes = list(other._changectx._parents)
+        while nodes[0].node() != nullid:
+            v = nodes.pop(0)
+            if v == self._changectx:
+                return True
+            nodes.extend( v._parents )
+        return False
+
+    def parentof(self, other):
+        return self._changectx in other._changectx._parents
+
+    def has_common_ancestor(self, other):
+        a = self._changectx.ancestor(other._changectx)
+        return (a.branch() == self._changectx.branch())
+
+    def has_children(self, limit_branch=False):
+        for child in self._changectx.children():
+            cbranch = child.branch()
+            if (not limit_branch) or (cbranch == self.hgbranch()):
+                return True
+        return False
+
+    def has_parent_from(self, rev):
+        branch = rev.hgbranch()        
+        for parent in self._changectx.parents():
+            if parent.branch() == branch:
+                return True            
+        return False
+
+    def merge_with(self, other, user, message):
+        message = self._library._sanitize_string(message)
+        lock = self._library.lock(True)
+        try:
+            self._library._checkout(self._changectx.node())
+            status = self._library._merge(other._changectx.node())
+            if status.isclean():
+                self._library._commit(user=user, message=message)
+                return True
+            else:
+                return False
+        finally:
+            lock.release()
+
+    def __eq__(self, other):
+        return self._changectx.node() == other._changectx.node()
index 336a365..fcc111e 100644 (file)
@@ -1,7 +1,7 @@
 /* Style widoku HTML. Nie należy tu ustawiać position ani marginesów */
 .htmlview {
     font-size: 16px;
-    font: Georgia, "Times New Roman", serif;
+    font-family: "Georgia", "Times New Roman", serif;
     line-height: 1.5em;
     padding: 3em;    
 }
     font-style: normal;
     font-variant: small-caps;
 }
+
+.htmlview .parse-warning {
+    display: block;
+    font-size: 10pt;
+    background: #C0C0C0;
+    margin: 1em;
+}
+
+.parse-warning .message {
+    color: purple;
+    font-weight: bold;
+}
\ No newline at end of file
index 8dae56e..ec19ee3 100644 (file)
@@ -183,7 +183,9 @@ Editor.Object = Class.extend({
 // Handle JSON error responses in uniform way
 function parseXHRError(response)
 {
-    var message = ""
+    var message = "";
+    var level = "";
+    
     try {
       var json = $.evalJSON(response.responseText);
 
@@ -193,16 +195,38 @@ function parseXHRError(response)
 
           message = message.replace(/(line\s+)(\d+)(\,\s*column\s+)(\d+)/i,
             "<a class='xml-editor-ref' href='#xml-$2-$4'>$1$2$3$4</a>");
+
+          level = "warning";
       }
       else {
-        message = json.message || json.reason || "Nieznany błąd :((";
+         message = json.message || json.reason || "Nieznany błąd :((";
+         level = "error";
       }
-
     } catch(e) {
         // not a valid JSON response
-        message = response.statusText;
+        message = response.statusText || 'Brak połączenia z serwerem';
+        level = "error";
+    }
+    
+    return {error_message: message, error_level: level};
+}
+
+function parseXHRResponse(xhr) {
+    var response = {}
+    
+    if(xhr.status >= 200 && xhr.status < 300) 
+    {
+        response.success = true;
+        try {
+            response.data = $.evalJSON(xhr.responseText);
+        } catch(e) {
+            response.data = {};
+        }
+
+        return response;
     }
-    return message;
+
+    return parseXHRError(xhr);
 }
 
 Editor.Object._lastGuid = 0;
index f63ed07..97e2657 100644 (file)
@@ -81,10 +81,13 @@ var CodeMirror = (function(){
 
     var nextNum = 1, barWidth = null;
     function sizeBar() {
-      for (var root = frame; root.parentNode; root = root.parentNode);
-      if (root != document || !win.Editor) {
-        clearInterval(sizeInterval);
-        return;
+      if (!frame.offsetWidth || !win.Editor) {
+        for (var cur = frame; cur.parentNode; cur = cur.parentNode) {
+          if (cur != document) {
+            clearInterval(sizeInterval);
+            return;
+          }
+        }
       }
 
       if (nums.offsetWidth != barWidth) {
@@ -132,7 +135,6 @@ var CodeMirror = (function(){
       var node = place;
       place = function(n){node.appendChild(n);};
     }
-    
     if (options.lineNumbers) place = wrapLineNumberDiv(place);
     place(frame);
 
@@ -173,14 +175,10 @@ var CodeMirror = (function(){
 
     getCode: function() {return this.editor.getCode();},
     setCode: function(code) {this.editor.importCode(code);},
-    selection: function() {this.focusIfIE(); return this.editor.selectedText();},
+    selection: function() {return this.editor.selectedText();},
     reindent: function() {this.editor.reindent();},
-    reindentSelection: function() {this.focusIfIE(); this.editor.reindentSelection(null);},
+    reindentSelection: function() {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
@@ -208,7 +206,10 @@ var CodeMirror = (function(){
 
     setParser: function(name) {this.editor.setParser(name);},
 
-    cursorPosition: function(start) {this.focusIfIE(); return this.editor.cursorPosition(start);},
+    cursorPosition: function(start) {
+      if (this.win.select.ie_selection) this.focus();
+      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 3d7a205..47d8412 100644 (file)
@@ -80,13 +80,13 @@ var Editor = (function(){
         if (text.length) leaving = false;
         result.push(node);
       }
-      else if (isBR(node) && node.childNodes.length == 0) {
+      else if (node.nodeName == "BR" && node.childNodes.length == 0) {
         leaving = true;
         result.push(node);
       }
       else {
         forEach(node.childNodes, simplifyNode);
-        if (!leaving && newlineElements.hasOwnProperty(node.nodeName.toUpperCase())) {
+        if (!leaving && newlineElements.hasOwnProperty(node.nodeName)) {
           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 (isBR(node)) {
+      else if (node.nodeName == "BR") {
         nodeQueue.push(node);
         return yield("\n", c);
       }
@@ -195,20 +195,23 @@ var Editor = (function(){
 
   // Determine the text size of a processed node.
   function nodeSize(node) {
-    return isBR(node) ? 1 : node.currentText.length;
+    if (node.nodeName == "BR")
+      return 1;
+    else
+      return 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 && !isBR(node)) node = node.previousSibling;
+    while (node && node.nodeName != "BR") node = node.previousSibling;
     return node;
   }
   function endOfLine(node, container) {
     if (!node) node = container.firstChild;
-    else if (isBR(node)) node = node.nextSibling;
+    else if (node.nodeName == "BR") node = node.nextSibling;
 
-    while (node && !isBR(node)) node = node.nextSibling;
+    while (node && node.nodeName != "BR") node = node.nextSibling;
     return node;
   }
 
@@ -363,6 +366,8 @@ 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) {
@@ -501,7 +506,7 @@ var Editor = (function(){
       this.checkLine(line);
       var accum = [];
       for (line = line ? line.nextSibling : this.container.firstChild;
-           line && !isBR(line); line = line.nextSibling)
+           line && line.nodeName != "BR"; line = line.nextSibling)
         accum.push(nodeText(line));
       return cleanText(accum.join(""));
     },
@@ -526,7 +531,7 @@ var Editor = (function(){
             before = cur;
             break;
           }
-          var text = nodeText(cur);
+          var text = (cur.innerText || cur.textContent || cur.nodeValue || "");
           if (text.length > position) {
             before = cur.nextSibling;
             content = text.slice(0, position) + content + text.slice(position);
@@ -565,7 +570,8 @@ var Editor = (function(){
       return cleanText(text.join("\n"));
     },
 
-    // Replace the selection with another piece of text.
+    // Replace the selection with another p
+    // iece of text.
     replaceSelection: function(text) {
       this.history.commit();
 
@@ -574,7 +580,7 @@ var Editor = (function(){
       if (!start || !end) return;
 
       end = this.replaceRange(start, end, text);
-      select.setCursorPos(this.container, end);
+      // select.setCursorPos(this.container, end);
       webkitLastLineHack(this.container);
     },
 
@@ -838,10 +844,10 @@ var Editor = (function(){
 
     home: function() {
       var cur = select.selectionTopNode(this.container, true), start = cur;
-      if (cur === false || !(!cur || cur.isPart || isBR(cur)) || !this.container.firstChild)
+      if (cur === false || !(!cur || cur.isPart || cur.nodeName == "BR") || !this.container.firstChild)
         return false;
 
-      while (cur && !isBR(cur)) cur = cur.previousSibling;
+      while (cur && cur.nodeName != "BR") 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);
@@ -896,7 +902,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 && isSpan(runner) && (ch = paren(runner))) {
+          if (runner.className == className && runner.nodeName == "SPAN" && (ch = paren(runner))) {
             if (forward(ch) == dir)
               stack.push(ch);
             else if (!stack.length)
@@ -905,7 +911,7 @@ var Editor = (function(){
               ok = false;
             if (!stack.length) break;
           }
-          else if (runner.dirty || !isSpan(runner) && !isBR(runner)) {
+          else if (runner.dirty || runner.nodeName != "SPAN" && runner.nodeName != "BR") {
             return {node: runner, status: "dirty"};
           }
         }
@@ -963,7 +969,7 @@ var Editor = (function(){
     // selection.
     indentRegion: function(start, end, direction) {
       var current = (start = startOfLine(start)), before = start && startOfLine(start.previousSibling);
-      if (!isBR(end)) end = endOfLine(end, this.container);
+      if (end.nodeName != "BR") end = endOfLine(end, this.container);
 
       do {
         var next = endOfLine(current, this.container);
@@ -1122,7 +1128,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 && isBR(from) && (--maxBacktrack) < 0)
+        if (maxBacktrack != null && from.nodeName == "BR" && (--maxBacktrack) < 0)
           return false;
         from = from.previousSibling;
       }
@@ -1203,7 +1209,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 && isSpan(part) && part.currentText == "") {
+          while (part && part.nodeName == "SPAN" && part.currentText == "") {
             var old = part;
             this.remove();
             part = this.get();
@@ -1225,7 +1231,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 (!isBR(part))
+          if (part.nodeName != "BR")
             throw "Parser out of sync. Expected BR.";
 
           if (part.dirty || !part.indentation) lineDirty = true;
@@ -1253,7 +1259,7 @@ var Editor = (function(){
           parts.next();
         }
         else {
-          if (!isSpan(part))
+          if (part.nodeName != "SPAN")
             throw "Parser out of sync. Expected SPAN.";
           if (part.dirty)
             lineDirty = true;
index 7746240..d513ba5 100644 (file)
@@ -52,7 +52,7 @@ var select = {};
     while (pos && pos.offsetParent) {
       y += pos.offsetTop;
       // Don't count X offset for <br> nodes
-      if (!isBR(pos))
+      if (pos.nodeName != "BR")
         x += pos.offsetLeft;
       pos = pos.offsetParent;
     }
@@ -247,17 +247,13 @@ var select = {};
           }
           if (cur) {
             try{range.moveToElementText(cur);}
-            catch(e){return false;}
+            catch(e){}
             range.collapse(false);
           }
           else range.moveToElementText(node.parentNode);
           if (count) range.move("character", count);
         }
-        else {
-          try{range.moveToElementText(node);}
-          catch(e){return false;}
-        }
-        return true;
+        else range.moveToElementText(node);
       }
 
       // Do a binary search through the container object, comparing
@@ -266,7 +262,7 @@ var select = {};
       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.
-        if (!moveToNodeStart(range2, node)) return false;
+        moveToNodeStart(range2, node);
         if (range.compareEndPoints("StartToStart", range2) == 1)
           start = middle;
         else
@@ -318,7 +314,7 @@ var select = {};
       if (!selection) return null;
 
       var topNode = select.selectionTopNode(container, start);
-      while (topNode && !isBR(topNode))
+      while (topNode && topNode.nodeName != "BR")
         topNode = topNode.previousSibling;
 
       var range = selection.createRange(), range2 = range.duplicate();
@@ -411,7 +407,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 && !isBR(point.node)) {
+        while (point.node.nodeType != 3 && point.node.nodeName != "BR") {
           var newNode = point.node.childNodes[point.offset] || point.node.nextSibling;
           point.offset = 0;
           while (!newNode && point.node.parentNode) {
@@ -429,9 +425,8 @@ var select = {};
     };
 
     select.selectMarked = function () {
-      var cs = currentSelection;
-      if (!(cs && (cs.changed || (webkit && cs.start.node == cs.end.node)))) return;
-      var win = cs.window, range = win.document.createRange();
+      if (!currentSelection || !currentSelection.changed) return;
+      var win = currentSelection.window, range = win.document.createRange();
 
       function setPoint(point, which) {
         if (point.node) {
@@ -447,8 +442,8 @@ var select = {};
         }
       }
 
-      setPoint(cs.end, "End");
-      setPoint(cs.start, "Start");
+      setPoint(currentSelection.end, "End");
+      setPoint(currentSelection.start, "Start");
       selectRange(range, win);
     };
 
@@ -476,7 +471,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] && isBR(container.childNodes[range.startOffset]))
+          container.childNodes[range.startOffset] && container.childNodes[range.startOffset].nodeName == "BR")
         offset--;
 
       // For text nodes, we look at the node itself if the cursor is
@@ -491,7 +486,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.toUpperCase() == "HTML") {
+      else if (node.nodeName == "HTML") {
         return (offset == 1 ? null : container.lastChild);
       }
       // If the given node is our 'container', we just look up the
@@ -562,7 +557,7 @@ var select = {};
       if (!range) return;
 
       var topNode = select.selectionTopNode(container, start);
-      while (topNode && !isBR(topNode))
+      while (topNode && topNode.nodeName != "BR")
         topNode = topNode.previousSibling;
 
       range = range.cloneRange();
index 97daf59..5f717a9 100644 (file)
@@ -250,7 +250,7 @@ History.prototype = {
     function buildLine(node) {
       var text = [];
       for (var cur = node ? node.nextSibling : self.container.firstChild;
-           cur && !isBR(cur); cur = cur.nextSibling)
+           cur && cur.nodeName != "BR"; 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 && !isBR(search))
+      while (search && search.nodeName != "BR")
         search = search[link];
       return search;
     }
index 0cd91d4..796025e 100644 (file)
@@ -123,12 +123,3 @@ function nodeTop(node) {
   }
   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";
-}
index c57f4f0..d148640 100644 (file)
@@ -88,10 +88,12 @@ Editor.XMLModel = Editor.Model.extend({
         messageCenter.addMessage('success', 'xmlload', 'Wczytałem XML :-)');
     },
   
-    loadingFailed: function() {
+    loadingFailed: function(response)
+    {
         if (this.get('state') != 'loading') {
             alert('erroneous state:', this.get('state'));
         }
+        
         var message = parseXHRError(response);
         
         this.set('error', '<h2>Błąd przy ładowaniu XML</h2><p>'+message+'</p>');
@@ -218,9 +220,9 @@ Editor.HTMLModel = Editor.Model.extend({
             alert('erroneous state:', this.get('state'));
         }
         
-        var message = parseXHRError(response);
+        var err = parseXHRError(response);
         
-        this.set('error', '<p>Nie udało się wczytać widoku HTML: </p>' + message);
+        this.set('error', '<p>Nie udało się wczytać widoku HTML: </p>' + err.error_message);
         this.set('state', 'error');        
     },
 
@@ -244,10 +246,12 @@ Editor.HTMLModel = Editor.Model.extend({
             data: {
                 revision: this.get('revision'),
                 user: this.document.get('user'),
-                part: path
+                chunk: path,
+                format: 'nl'
             },
             success: function(data) {
                 self.xmlParts[path] = data;
+                console.log(data);
                 callback(path, data);
             },
             // TODO: error handling
@@ -273,7 +277,8 @@ Editor.HTMLModel = Editor.Model.extend({
             dataType: 'text; charset=utf-8',
             data: {
                 fragment: data,
-                part: path
+                chunk: path,
+                format: 'nl'
             },
             success: function(htmldata) {
                 elem.replaceWith(htmldata);
@@ -462,8 +467,8 @@ Editor.DocumentModel = Editor.Model.extend({
             alert('erroneous state:', this.get('state'));
         }
         
-        var message = parseXHRError(response);        
-        this.set('error', '<h2>Nie udało się wczytać dokumentu</h2><p>'+message+"</p>");
+        var err = parseXHRError(response);
+        this.set('error', '<h2>Nie udało się wczytać dokumentu</h2><p>'+err.error_message+"</p>");
         this.set('state', 'error');
     },
   
@@ -517,53 +522,58 @@ Editor.DocumentModel = Editor.Model.extend({
                 revision: this.get('revision'),
                 user: this.get('user')
             },
-            complete: this.updateCompleted.bind(this),
-            success: function(data) {
-                this.set('updateData', data);
-                console.log("new data:", data)
-            }.bind(this)
+            complete: this.updateCompleted.bind(this),            
         });
     },
   
-    updateCompleted: function(xhr, textStatus) {
-        console.log(xhr.status, textStatus);
-        
-        if (xhr.status == 200) 
+    updateCompleted: function(xhr, textStatus)
+    {
+        console.log(xhr.status, xhr.responseText);
+        var response = parseXHRResponse(xhr);
+        if(response.success)
         {
-            var udata = this.get('updateData');
-            if(udata.timestamp == udata.parent_timestamp)
+            if( (response.data.result == 'no-op')
+             || (response.data.timestamp == response.data.parent_timestamp))
             {
-                // no change
+                if( (response.data.revision) && (response.data.revision != this.get('revision')) )
+                {
+                    // we're out of sync
+                    this.set('state', 'unsynced');
+                    return;
+                }
+                
                 messageCenter.addMessage('info', 'doc_update',
-                    'Nic się nie zmieniło od ostatniej aktualizacji. Po co mam uaktualniać?');
+                    'Już posiadasz najbardziej aktualną wersję.');
+                    this.set('state', 'synced');
+                return;
+            }
+
+            // result: success
+            this.set('revision', response.data.revision);
+            this.set('user', response.data.user);
 
+            messageCenter.addMessage('info', 'doc_update',
+                'Uaktualnienie dokumentu do wersji ' + response.data.revision);
+
+            for (var key in this.contentModels) {
+                this.contentModels[key].set('revision', this.get('revision') );
+                this.contentModels[key].set('state', 'empty');
             }
-            else {
-                this.set('revision', udata.revision);
-                this.set('user', udata.user);
-                messageCenter.addMessage('info', 'doc_update', 
-                    'Uaktualnienie dokumentu do wersji ' + udata.revision);
-
-                for (var key in this.contentModels) {
-                    this.contentModels[key].set('revision', this.get('revision') );
-                    this.contentModels[key].set('state', 'empty');
-                }
-            }        
-        } else if (xhr.status == 409) { // Konflikt podczas operacji
-            messageCenter.addMessage('error', 'doc_update',
-                'Wystąpił konflikt podczas aktualizacji. Pędź po programistów! :-(');
-        } else {
-            messageCenter.addMessage('critical', 'doc_update',
-                'Nieoczekiwany błąd. Pędź po programistów! :-(');
+
+            this.set('state', 'synced');
+            return;
         }
+
+        // no success means trouble
+        messageCenter.addMessage(response.error_level, 'doc_update', 
+            response.error_message);       
         
-        this.set('state', 'synced');
-        this.set('updateData', null);
+        this.set('state', 'unsynced');
     },
   
     merge: function(message) {
         this.set('state', 'loading');
-        messageCenter.addMessage('info', null, 
+        messageCenter.addMessage('info', 'doc_merge',
             'Scalam dokument z głównym repozytorium...');
             
         $.ajax({
@@ -584,28 +594,50 @@ Editor.DocumentModel = Editor.Model.extend({
     },
   
     mergeCompleted: function(xhr, textStatus) {
-        console.log(xhr.status, textStatus);
-        if (xhr.status == 200) { // Sukces
-            this.set('revision', this.get('updateData').revision);
-            this.set('user', this.get('updateData').user);
-            
-            for (var key in this.contentModels) {
-                this.contentModels[key].set('revision', this.get('revision'));
-                this.contentModels[key].set('state', 'empty');
+        console.log(xhr.status, xhr.responseText);
+        var response = parseXHRResponse(xhr);
+        if(response.success)
+        {
+            if( (response.data.result == 'no-op')
+             || (response.data.shared_timestamp == response.data.shared_parent_timestamp))
+            {
+                if( (response.data.revision) && (response.data.revision != this.get('revision')) )
+                {
+                    // we're out of sync
+                    this.set('state', 'unsynced');
+                    return;
+                }
+
+                messageCenter.addMessage('info', 'doc_merge',
+                    'Twoja aktualna wersja nie różni się od ostatnio zatwierdzonej.');
+                this.set('state', 'synced');
+                return;
+            }
+
+            if( response.data.result == 'accepted')
+            {
+                messageCenter.addMessage('info', 'doc_merge',
+                    'Prośba o zatwierdzenie została przyjęta i oczekuję na przyjęcie.');
+                this.set('state', 'synced');
+                return;
             }
 
-            messageCenter.addMessage('success', null, 'Scaliłem dokument z głównym repozytorium :-)');
-        } else if (xhr.status == 202) { // Wygenerowano PullRequest
-            messageCenter.addMessage('success', null, 'Wysłałem prośbę o scalenie dokumentu z głównym repozytorium.');
-        } else if (xhr.status == 204) { // Nic nie zmieniono
-            messageCenter.addMessage('info', null, 'Nic się nie zmieniło od ostatniego scalenia. Po co mam scalać?');
-        } else if (xhr.status == 409) { // Konflikt podczas operacji
-            messageCenter.addMessage('error', null, 'Wystąpił konflikt podczas scalania. Pędź po programistów! :-(');
-        } else if (xhr.status == 500) {
-            messageCenter.addMessage('critical', null, 'Błąd serwera. Pędź po programistów! :-(');
+            // result: success
+            this.set('revision', response.data.revision);
+            this.set('user', response.data.user);
+
+            messageCenter.addMessage('info', 'doc_merge',
+                'Twoja wersja dokumentu została zatwierdzona.');
+            
+            this.set('state', 'synced');
+            return;
         }
-        this.set('state', 'synced');
-        this.set('mergeData', null);
+
+        // no success means trouble
+        messageCenter.addMessage(response.error_level, 'doc_merge',
+            response.error_message);
+
+        this.set('state', 'unsynced');
     },
   
     // For debbuging
index d43caaa..cfd1816 100644 (file)
@@ -30,6 +30,27 @@ var EditorView = View.extend({
             $('#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();
+                });
+                
+                var ctx = $('#commit-dialog').data('context');
+                console.log("COMMIT APROVED", ctx);
+                ctx.callback(message);
+            }
+            return false;
+        });
+        
     
         // $('#split-dialog').jqm({
         //      modal: true,
@@ -71,25 +92,8 @@ var EditorView = View.extend({
     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();
-
-                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;
-        });
+        
+        $('#commit-dialog').data('context', hash.t);
 
         $("div.loading-box", c).show();
         $("div.fatal-error-box", c).hide();
@@ -118,7 +122,7 @@ var EditorView = View.extend({
             this.commitButton.attr('disabled', null);
             this.updateButton.attr('disabled', 'disabled');
             this.mergeButton.attr('disabled', 'disabled');
-        } else if (value == 'synced') {            
+        } else if (value == 'synced' || value == 'unsynced') {
             this.quickSaveButton.attr('disabled', 'disabled');
             this.commitButton.attr('disabled', 'disabled');
             this.updateButton.attr('disabled', null);
index 5ff1b6a..786a653 100644 (file)
@@ -13,9 +13,8 @@ urlpatterns = patterns('',
     url(r'^$', 'explorer.views.file_list', name='file_list'),        
     url(r'^file/upload', 'explorer.views.file_upload', name='file_upload'),
 
-
-    url(r'^managment/pull-requests$', 'explorer.views.pull_requests'),
-        
+    url(r'^management/pull-requests$', 'explorer.views.pull_requests'),
+  
     # Editor panels
     url(r'^editor/'+PATH_END, 'explorer.views.display_editor', name='editor_view'),
     url(r'^editor/$', 'explorer.views.file_list', name='editor_base'),