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')) )
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']
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']
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
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)
#
# 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
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."
})
# Same goes for shared view
return response.SuccessAllOk().django_response({
+ "result": "success",
"name": user_doc_new.id,
"user": user_doc_new.owner,
--- /dev/null
+# -*- 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)
#
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)
def django_response(self, ticket_status, ticket_uri):
return ResponseObject.django_response(self, {
+ 'result': 'accepted',
'status': ticket_status,
'refer_to': ticket_uri })
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)
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
__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
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()
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()
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):
--- /dev/null
+# -*- 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()
/* 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
// Handle JSON error responses in uniform way
function parseXHRError(response)
{
- var message = ""
+ var message = "";
+ var level = "";
+
try {
var json = $.evalJSON(response.responseText);
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;
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) {
var node = place;
place = function(n){node.appendChild(n);};
}
-
if (options.lineNumbers) place = wrapLineNumberDiv(place);
place(frame);
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
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);},
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"));
nodeQueue.push(node);
return yield(node.currentText, c);
}
- else if (isBR(node)) {
+ else if (node.nodeName == "BR") {
nodeQueue.push(node);
return yield("\n", c);
}
// 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;
}
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) {
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(""));
},
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);
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();
if (!start || !end) return;
end = this.replaceRange(start, end, text);
- select.setCursorPos(this.container, end);
+ // select.setCursorPos(this.container, end);
webkitLastLineHack(this.container);
},
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);
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)
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"};
}
}
// 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);
// 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;
}
// 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();
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;
parts.next();
}
else {
- if (!isSpan(part))
+ if (part.nodeName != "SPAN")
throw "Parser out of sync. Expected SPAN.";
if (part.dirty)
lineDirty = true;
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;
}
}
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
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
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();
// 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) {
};
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) {
}
}
- setPoint(cs.end, "End");
- setPoint(cs.start, "Start");
+ setPoint(currentSelection.end, "End");
+ setPoint(currentSelection.start, "Start");
selectRange(range, win);
};
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
// 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
if (!range) return;
var topNode = select.selectionTopNode(container, start);
- while (topNode && !isBR(topNode))
+ while (topNode && topNode.nodeName != "BR")
topNode = topNode.previousSibling;
range = range.cloneRange();
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(""))};
}
// 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;
}
}
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";
-}
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>');
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');
},
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
dataType: 'text; charset=utf-8',
data: {
fragment: data,
- part: path
+ chunk: path,
+ format: 'nl'
},
success: function(htmldata) {
elem.replaceWith(htmldata);
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');
},
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({
},
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
$('#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,
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();
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);
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'),