# HG changeset patch
# User raph
# Date 1279206525 -7200
# Node ID 6959c2875f4767f9166cf26c5a86ee3b1eb9cb8e
# Parent 7c40b98f627f04f27bc515967195ca2f9794e893# Parent 875e57f5f6ac74a1d6fb0e2623157fff254c338f
Merge with 875e57f5f6ac74a1d6fb0e2623157fff254c338f
diff -r 875e57f5f6ac -r 6959c2875f47 buildout.cfg
--- a/buildout.cfg Fri Jun 11 16:21:53 2010 +0200
+++ b/buildout.cfg Thu Jul 15 17:08:45 2010 +0200
@@ -4,6 +4,9 @@
django
python
django-extensions
+ django-piston
+ omelette
+unzip = true
develop = .
[python]
@@ -23,12 +26,14 @@
pythonpath = src
src/cm
${django-extensions:location}
+ ${django-piston:location}
eggs =
django-flash
django-tagging
- django-piston
+# django-piston
+# api dependency
# django-css
- chardet
+# chardet
feedparser
PIL
BeautifulSoup
@@ -44,4 +49,12 @@
[django-extensions]
recipe=zerokspot.recipe.git
repository=git://github.com/django-extensions/django-extensions.git
-#rev=7c73978b55fcadbe2cd6f2abbefbedb5a85c2c8c
\ No newline at end of file
+#rev=7c73978b55fcadbe2cd6f2abbefbedb5a85c2c8c
+
+[django-piston]
+recipe = mercurialrecipe
+repository = http://bitbucket.org/jespern/django-piston
+
+[omelette]
+recipe = collective.recipe.omelette
+eggs = ${django:eggs}
\ No newline at end of file
diff -r 875e57f5f6ac -r 6959c2875f47 src/cm/api/__init__.py
diff -r 875e57f5f6ac -r 6959c2875f47 src/cm/api/handlers.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/cm/api/handlers.py Thu Jul 15 17:08:45 2010 +0200
@@ -0,0 +1,474 @@
+from piston.handler import AnonymousBaseHandler, BaseHandler
+from piston.utils import rc
+
+from cm.models import Text,TextVersion, Role, UserRole, Comment
+from cm.views import get_keys_from_dict, get_textversion_by_keys_or_404, get_text_by_keys_or_404, get_textversion_by_keys_or_404, redirect
+from cm.security import get_texts_with_perm, has_perm, get_viewable_comments, \
+ has_perm_on_text_api
+from cm.security import get_viewable_comments
+from cm.utils.embed import embed_html
+from cm.views.create import CreateTextContentForm, create_text
+from cm.views.texts import client_exchange, text_view_frame, text_view_comments, text_export
+from cm.views.feeds import text_feed
+from piston.utils import validate
+from settings import SITE_URL
+
+URL_PREFIX = SITE_URL + '/api'
+
+class AnonymousTextHandler(AnonymousBaseHandler):
+ type = "Text methods"
+ title = "Read text info"
+ fields = ('key', 'title', 'format', 'content', 'created', 'modified', 'nb_comments', 'nb_versions', 'embed_html', ('last_text_version', ('created','modified', 'format', 'title', 'content')))
+ allowed_methods = ('GET', )
+ model = Text
+ desc = """ Read text identified by `key`."""
+ args = None
+
+ @staticmethod
+ def endpoint():
+ return URL_PREFIX + '/text/{key}/'
+
+
+ @has_perm_on_text_api('can_view_text')
+ def read(self, request, key):
+
+ text = get_text_by_keys_or_404(key)
+ setattr(text,'nb_comments',len(get_viewable_comments(request, text.last_text_version.comment_set.all(), text)))
+ setattr(text,'nb_versions',text.get_versions_number())
+ setattr(text,'embed_html',embed_html(text.key))
+
+ return text
+
+class TextHandler(BaseHandler):
+ type = "Text methods"
+ anonymous = AnonymousTextHandler
+ allowed_methods = ('GET',)
+ no_display = True
+
+class AnonymousTextVersionHandler(AnonymousBaseHandler):
+ type = "Text methods"
+ title = "Read text version info"
+ fields = ('key', 'title', 'format', 'content', 'created', 'modified', 'nb_comments',)
+ allowed_methods = ('GET', )
+ model = Text
+ desc = """ Read text version identified by `version_key` inside text identified by `key`."""
+ args = None
+
+ @staticmethod
+ def endpoint():
+ return URL_PREFIX + '/text/{key}/{version_key}/'
+
+
+ @has_perm_on_text_api('can_view_text')
+ def read(self, request, key, version_key):
+ text_version = get_textversion_by_keys_or_404(version_key, key=key)
+ setattr(text_version,'nb_comments',len(get_viewable_comments(request, text_version.comment_set.all(), text_version.text)))
+
+ return text_version
+
+class TextVersionHandler(BaseHandler):
+ type = "Text methods"
+ anonymous = AnonymousTextVersionHandler
+ fields = ('key', 'title', 'format', 'content', 'created', 'modified', 'nb_comments',)
+ allowed_methods = ('GET', )
+ model = Text
+ no_display = True
+
+ @has_perm_on_text_api('can_view_text')
+ def read(self, request, key, version_key):
+ text_version = get_textversion_by_keys_or_404(version_key, key=key)
+ setattr(text_version,'nb_comments',len(get_viewable_comments(request, text_version.comment_set.all(), text_version.text)))
+
+ return text_version
+
+class AnonymousTextListHandler(AnonymousBaseHandler):
+ title = "List texts"
+ type = "Text methods"
+ fields = ('key', 'title', 'created', 'modified', 'nb_comments', 'nb_versions',)
+ allowed_methods = ('GET',)
+ model = Text
+ desc = """Lists texts on workspace."""
+
+ @staticmethod
+ def endpoint():
+ return URL_PREFIX + '/text/'
+
+ def read(self, request):
+ order_by = '-id'
+ texts = get_texts_with_perm(request, 'can_view_text').order_by(order_by)
+ return texts
+
+class TextListHandler(BaseHandler):
+ title = "Create text"
+ type = "Text methods"
+ allowed_methods = ('GET', 'POST')
+ fields = ('key', 'title', 'created', 'modified', 'nb_comments', 'nb_versions',)
+ model = Text
+ anonymous = AnonymousTextListHandler
+ desc = "Create a text with the provided parameters."
+ args = """
+`title`: title of the text
+`format`: format content ('markdown', 'html')
+`content`: content (in specified format)
+`anon_role`: role to give to anon users: null, 4: commentator, 5: observer
+ """
+
+ @staticmethod
+ def endpoint():
+ return URL_PREFIX + '/text/'
+
+ def read(self, request):
+ order_by = '-id'
+ texts = get_texts_with_perm(request, 'can_view_text').order_by(order_by)
+ return texts
+
+ def create(self, request):
+ form = CreateTextContentForm(request.POST)
+ if form.is_valid():
+ text = create_text(request.user, form.cleaned_data)
+ anon_role = request.POST.get('anon_role', None)
+ if anon_role:
+ userrole = UserRole.objects.create(user=None, role=Role.objects.get(id=anon_role), text=text)
+ return {'key' : text.key , 'version_key' : text.last_text_version.key, 'created': text.created}
+ else:
+ resp = rc.BAD_REQUEST
+ return resp
+
+from cm.exception import UnauthorizedException
+from cm.views.texts import text_delete
+
+class TextDeleteHandler(BaseHandler):
+ type = "Text methods"
+ allowed_methods = ('POST', )
+ title = "Delete text"
+ desc = "Delete the text identified by `key`."
+
+ @staticmethod
+ def endpoint():
+ return URL_PREFIX + '/text/{key}/delete/'
+
+ def create(self, request, key):
+ """
+ Delete text identified by `key`.
+ """
+ try:
+ text_delete(request, key=key)
+ except UnauthorizedException:
+ return rc.FORBIDDEN
+ except KeyError:
+ return rc.BAD_REQUEST
+ return rc.DELETED
+
+from cm.views.texts import text_pre_edit
+
+class TextPreEditHandler(BaseHandler):
+ type = "Text methods"
+ allowed_methods = ('POST', )
+ title = "Ask for edit impact"
+ desc = "Returns the number of impacted comments."
+ args = """
+`new_format`: new format content ('markdown', 'html')
+`new_content`: new content (in specified format)
+ """
+
+ @staticmethod
+ def endpoint():
+ return URL_PREFIX + '/text/{key}/pre_edit/'
+
+ def create(self, request, key):
+ return text_pre_edit(request, key=key)
+
+from cm.views.texts import text_edit
+
+class TextEditHandler(BaseHandler):
+ allowed_methods = ('POST', )
+ type = "Text methods"
+ title = "Edit text"
+ desc = "Update text identified by `key`."
+ args = """
+`title`: new title of the text
+`format`: new format content ('markdown', 'html')
+`content`: new content (in specified format)
+`note`: note to add to edit
+`new_version`: boolean: should a new version of the text be created?
+`keep_comments`: boolean: should existing comments be keep (if possible)?
+ """
+
+ @staticmethod
+ def endpoint():
+ return URL_PREFIX + '/text/{key}/edit/'
+
+
+ def create(self, request, key):
+ res = text_edit(request, key=key)
+ text = get_text_by_keys_or_404(key)
+ text_version = text.last_text_version
+ return {'version_key' : text_version.key , 'created': text_version.created}
+
+
+class AnonymousTextFeedHandler(AnonymousBaseHandler):
+ allowed_methods = ('GET',)
+ type = "Text methods"
+ title = "Text feed"
+ desc = "Returns text RSS feed."
+ args = None
+
+ @staticmethod
+ def endpoint():
+ return URL_PREFIX + '/text/{key}/feed/?'
+
+ def read(self, request, key):
+ return text_feed(request, key=key)
+
+class TextFeedHandler(BaseHandler):
+ type = "Text methods"
+ anonymous = AnonymousTextFeedHandler
+ allowed_methods = ('GET',)
+ no_display = True
+
+ def read(self, request, key):
+ return text_feed(request, key=key)
+
+class TextVersionRevertHandler(BaseHandler):
+ allowed_methods = ('POST', )
+ type = "Text methods"
+ title = "Revert to specific text version"
+ desc = "Revert to a text version (i.e. copy this text_version which becomes the last text_version)."
+ args = """
+`key`: text's key
+`version_key`: key of the version to revert to
+ """
+
+ @staticmethod
+ def endpoint():
+ return URL_PREFIX + '/text/{key}/{version_key}/revert/'
+
+
+ def create(self, request, key, version_key):
+ text_version = get_textversion_by_keys_or_404(version_key, key=key)
+ new_text_version = text_version.text.revert_to_version(version_key)
+ return {'version_key' : new_text_version.key , 'created': new_text_version.created}
+
+class TextVersionDeleteHandler(BaseHandler):
+ allowed_methods = ('POST', )
+ type = "Text methods"
+ title = "Delete a specific text version"
+ desc = "Delete a text version."
+ args = """
+`key`: text's key
+`version_key`: key of the version to delete
+ """
+
+ @staticmethod
+ def endpoint():
+ return URL_PREFIX + '/text/{key}/{version_key}/delete/'
+
+
+ def create(self, request, key, version_key):
+ text_version = get_textversion_by_keys_or_404(version_key, key=key)
+ text_version.delete()
+ return rc.ALL_OK
+
+## client methods
+
+class AnonymousClientHandler(AnonymousBaseHandler):
+ allowed_methods = ('POST',)
+ type = "Client methods"
+ title = "Handles client methods"
+ desc = "Handles client (ajax text view) methods."
+ args = """
+post arguments
+ """
+
+ @staticmethod
+ def endpoint():
+ return URL_PREFIX + '/client/'
+
+ def create(self, request):
+ return client_exchange(request)
+
+class ClientHandler(BaseHandler):
+ type = "Client methods"
+ anonymous = AnonymousClientHandler
+ allowed_methods = ('POST',)
+ no_display = True
+
+ def create(self, request):
+ return client_exchange(request)
+
+## embed methods
+from django.views.i18n import javascript_catalog
+from cm.urls import js_info_dict
+
+class JSI18NHandler(AnonymousBaseHandler):
+ allowed_methods = ('GET',)
+ type = "Embed methods"
+ title = "Get js i18n dicts"
+ desc = ""
+ args = None
+
+ @staticmethod
+ def endpoint():
+ return URL_PREFIX + '/jsi18n/'
+
+ def read(self, request):
+ return javascript_catalog(request, **js_info_dict)
+
+
+class AnonymousCommentFrameHandler(AnonymousBaseHandler):
+ allowed_methods = ('GET',)
+ type = "Embed methods"
+ title = "Displays embedable frame"
+ desc = ""
+ args = None
+
+ @staticmethod
+ def endpoint():
+ return URL_PREFIX + '/text/{key}/comments_frame/?prefix=/api'
+
+ def read(self, request, key):
+ return text_view_frame(request, key=key)
+
+class CommentFrameHandler(BaseHandler):
+ type = "Embed methods"
+ anonymous = AnonymousCommentFrameHandler
+ allowed_methods = ('GET',)
+ no_display = True
+
+ def read(self, request, key):
+ return text_view_frame(request, key=key)
+
+class AnonymousCommentHandler(AnonymousBaseHandler):
+ allowed_methods = ('GET',)
+ type = "Embed methods"
+ title = "Displays embedable frame"
+ no_display = True
+ desc = ""
+ args = None
+
+ @staticmethod
+ def endpoint():
+ return URL_PREFIX + '/text/{key}/comments/{version_key}/?'
+
+ def read(self, request, key, version_key):
+ return text_view_comments(request, key=key, version_key=version_key)
+
+class CommentHandler(BaseHandler):
+ type = "Embed methods"
+ anonymous = AnonymousCommentHandler
+ allowed_methods = ('GET',)
+ no_display = True
+
+ def read(self, request, key, version_key):
+ return text_view_comments(request, key=key, version_key=version_key)
+
+
+class AnonymousTextExportHandler(AnonymousBaseHandler):
+ allowed_methods = ('POST',)
+ type = "Embed methods"
+ title = "undocumented"
+ no_display = True
+ desc = ""
+ args = None
+
+ @staticmethod
+ def endpoint():
+ return URL_PREFIX + ' undocumented'
+
+ def create(self, request, key, format, download, whichcomments, withcolor):
+ return text_export(request, key, format, download, whichcomments, withcolor, adminkey=None)
+
+class TextExportHandler(BaseHandler):
+ type = "Embed methods"
+ anonymous = AnonymousTextExportHandler
+ allowed_methods = ('POST',)
+ no_display = True
+
+ def create(self, request, key, format, download, whichcomments, withcolor):
+ return text_export(request, key, format, download, whichcomments, withcolor, adminkey=None)
+
+class AnonymousCommentsHandler(AnonymousBaseHandler):
+ allowed_methods = ('GET',)
+ type = "Comment methods"
+ fields = ('id_key', 'title', 'format', 'content', 'created', 'name', ('text_version' , ('key', ('text', ('key',))) ))
+ model = Comment
+ title = "Get comments"
+ desc = "Get comments from the workspace, most recent first."
+ args = """
+`keys`: (optional) comma separated keys : limit comments from these texts only
+`name`: (optional) limit comments from this user only
+`limit`: (optional) limit number of comments returned
+ """
+
+ @staticmethod
+ def endpoint():
+ return URL_PREFIX + '/comments/'
+
+ def read(self, request):
+ name = request.GET.get('name', None)
+ limit = request.GET.get('limit', None)
+ keys = request.GET.get('keys', None)
+ query = Comment.objects.all()
+ if keys:
+ query = query.filter(text_version__text__key__in=keys.split(','))
+ if name:
+ query = query.filter(name=name)
+ query = query.order_by('-created')
+ if limit:
+ query = query[:int(limit)]
+ return query
+
+class CommentsHandler(BaseHandler):
+ type = "Comment methods"
+ anonymous = AnonymousCommentsHandler
+ allowed_methods = ('GET',)
+ fields = ('id_key', 'title', 'format', 'content', 'created', 'name', ('text_version' , ('key', ('text', ('key',))) ))
+ model = Comment
+ no_display = True
+
+ def read(self, request):
+ name = request.GET.get('name', None)
+ limit = request.GET.get('limit', None)
+ keys = request.GET.get('keys', None)
+ query = Comment.objects.all()
+ if keys:
+ query = query.filter(text_version__text__key__in=keys.split(','))
+ if name:
+ query = query.filter(name=name)
+ query = query.order_by('-created')
+ if limit:
+ query = query[:int(limit)]
+ return query
+
+from piston.doc import documentation_view
+
+from piston.handler import handler_tracker
+from django.template import RequestContext
+from piston.doc import generate_doc
+from django.shortcuts import render_to_response
+
+def documentation(request):
+ """
+ Generic documentation view. Generates documentation
+ from the handlers you've defined.
+ """
+ docs = [ ]
+
+ for handler in handler_tracker:
+ doc = generate_doc(handler)
+ setattr(doc,'type', handler.type)
+ docs.append(doc)
+
+ def _compare(doc1, doc2):
+ #handlers and their anonymous counterparts are put next to each other.
+ name1 = doc1.name.replace("Anonymous", "")
+ name2 = doc2.name.replace("Anonymous", "")
+ return cmp(name1, name2)
+
+ #docs.sort(_compare)
+
+ return render_to_response('api_doc.html',
+ { 'docs': docs }, RequestContext(request))
+
+from piston.doc import generate_doc
+DocHandler = generate_doc(TextPreEditHandler)
diff -r 875e57f5f6ac -r 6959c2875f47 src/cm/api/urls.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/cm/api/urls.py Thu Jul 15 17:08:45 2010 +0200
@@ -0,0 +1,54 @@
+from django.conf.urls.defaults import *
+
+from piston.resource import Resource
+from piston.authentication import HttpBasicAuthentication
+
+from cm.api.handlers import *
+auth = HttpBasicAuthentication(realm='Comt API')
+
+text_handler = Resource(handler=TextHandler, authentication=auth)
+textversion_handler = Resource(handler=TextVersionHandler, authentication=auth)
+text_list_handler = Resource(handler=TextListHandler, authentication=auth)
+text_delete_handler = Resource(handler=TextDeleteHandler, authentication=auth)
+text_pre_edit_handler = Resource(handler=TextPreEditHandler, authentication=auth)
+text_edit_handler = Resource(handler=TextEditHandler, authentication=auth)
+text_feed_handler = Resource(handler=TextFeedHandler, authentication=auth)
+
+tv_revert_handler = Resource(handler=TextVersionRevertHandler, authentication=auth)
+tv_delete_handler = Resource(handler=TextVersionDeleteHandler, authentication=auth)
+
+text_export_handler = Resource(handler=TextExportHandler, authentication=auth)
+
+comments_handler = Resource(handler=CommentsHandler, authentication=auth)
+
+client_handler = Resource(handler=ClientHandler, authentication=auth)
+
+jsi8n_handler = Resource(handler=JSI18NHandler, authentication=None)
+
+comment_frame_handler = Resource(handler=CommentFrameHandler, authentication=auth)
+comment_handler = Resource(handler=CommentHandler, authentication=auth)
+
+#doc_handler = Resource(handler=DocHandler)
+
+urlpatterns = patterns('',
+ url(r'^text/(?P\w*)/$', text_handler),
+ url(r'^text/$', text_list_handler),
+
+ url(r'^text/(?P\w*)/(?P\w*)/revert/$', tv_revert_handler),
+ url(r'^text/(?P\w*)/(?P\w*)/delete/$', tv_delete_handler),
+
+ url(r'^text/(?P\w*)/comments_frame/$', comment_frame_handler),
+ url(r'^text/(?P\w*)/comments/(?P\w*)/$', comment_handler),
+
+ url(r'^text/(?P\w*)/export/(?P\w*)/(?P\w*)/(?P\w*)/(?P\w*)/$', text_export_handler),
+
+ url(r'^text/(?P\w*)/feed/$', text_feed_handler),
+ url(r'^text/(?P\w*)/delete/$', text_delete_handler),
+ url(r'^text/(?P\w*)/pre_edit/$', text_pre_edit_handler),
+ url(r'^text/(?P\w*)/edit/$', text_edit_handler),
+ url(r'^text/(?P\w*)/(?P\w*)/$', textversion_handler),
+ url(r'^comments/$', comments_handler),
+ url(r'^client/$', client_handler),
+ url(r'^jsi18n/$', jsi8n_handler),
+ url(r'^doc/$', documentation),
+)
diff -r 875e57f5f6ac -r 6959c2875f47 src/cm/cm_settings.py
--- a/src/cm/cm_settings.py Fri Jun 11 16:21:53 2010 +0200
+++ b/src/cm/cm_settings.py Thu Jul 15 17:08:45 2010 +0200
@@ -28,4 +28,7 @@
TRACKING_HTML = get_setting('TRACKING_HTML', '')
# Store IP (or not) in activity
-STORE_ACTIVITY_IP = get_setting('STORE_ACTIVITY_IP', True)
\ No newline at end of file
+STORE_ACTIVITY_IP = get_setting('STORE_ACTIVITY_IP', True)
+
+# Show 'decorated' users in comments (not structural creator id)
+DECORATED_CREATORS = get_setting('DECORATED_CREATORS', False)
\ No newline at end of file
diff -r 875e57f5f6ac -r 6959c2875f47 src/cm/fixtures/test_content.yaml
--- a/src/cm/fixtures/test_content.yaml Fri Jun 11 16:21:53 2010 +0200
+++ b/src/cm/fixtures/test_content.yaml Thu Jul 15 17:08:45 2010 +0200
@@ -121,6 +121,19 @@
mod_posteriori: True
key: "textversion_key_1"
adminkey: "tv_adminkey_1"
+
+- model : cm.textversion
+ pk: 0
+ fields:
+ created: "2009-01-14 04:01:12"
+ modified: "2009-01-14 04:01:12"
+ title: 'title 1, aposteriori moderation'
+ format: 'markdown'
+ content: 'zzz hhhh aaa bbb ccc ddd eee fff ggg'
+ text: 1
+ mod_posteriori: True
+ key: "textversion_key_0"
+ adminkey: "tv_adminkey_0"
- model : cm.text
pk: 1
@@ -360,6 +373,58 @@
adminkey: "text_adminkey_3"
user: 2
+# text 4
+- model : cm.textversion
+ pk: 4
+ fields:
+ created: "2009-02-13 04:01:12"
+ modified: "2009-02-13 04:01:12"
+ title: 'title 4, public text'
+ format: 'markdown'
+ content: 'aaa bbb ccc ddd eee fff ggg'
+ text: 4
+ mod_posteriori: True
+ key: "textversion_key_4"
+ adminkey: "tv_adminkey_4"
+
+- model : cm.text
+ pk: 4
+ fields:
+ created: "2009-02-13 04:01:12"
+ modified: "2009-02-13 04:01:12"
+ last_text_version: 4
+ title: 'title 4, public text'
+ state: "approved"
+ key: "text_key_4"
+ adminkey: "text_adminkey_4"
+ user: 1
+
+# text 5
+- model : cm.textversion
+ pk: 5
+ fields:
+ created: "2009-02-13 04:01:12"
+ modified: "2009-02-13 04:01:12"
+ title: 'title 5, public text'
+ format: 'markdown'
+ content: 'aaa bbb ccc ddd eee fff ggg'
+ text: 5
+ mod_posteriori: True
+ key: "textversion_key_5"
+ adminkey: "tv_adminkey_5"
+
+- model : cm.text
+ pk: 5
+ fields:
+ created: "2009-02-13 04:01:12"
+ modified: "2009-02-13 04:01:12"
+ last_text_version: 5
+ title: 'title 5, public text'
+ state: "approved"
+ key: "text_key_5"
+ adminkey: "text_adminkey_5"
+ user: 1
+
############### userrole ###############
# user 1 is global Manager
@@ -426,6 +491,24 @@
user: 4
text: 2
+# user null (anon is Commentator on text 4)
+# userrole 8
+- model : cm.userrole
+ pk: 8
+ fields:
+ role: 4
+ user: null
+ text: 4
+
+# user null (anon is Commentator on text 5)
+# userrole 9
+- model : cm.userrole
+ pk: 9
+ fields:
+ role: 4
+ user: null
+ text: 5
+
############### comment ###############
# comment 1 (visible on text 2)
diff -r 875e57f5f6ac -r 6959c2875f47 src/cm/media/js/client/c_client-min.js
--- a/src/cm/media/js/client/c_client-min.js Fri Jun 11 16:21:53 2010 +0200
+++ b/src/cm/media/js/client/c_client-min.js Thu Jul 15 17:08:45 2010 +0200
@@ -1,1 +1,1 @@
-hasPerm=function(a){return(-1!=CY.Array.indexOf(sv_user_permissions,a));};Layout=function(){};Layout.prototype={init:function(){},isInFrame:function(){return(!CY.Lang.isUndefined(parent)&&parent.location!=location&&CY.Lang.isFunction(parent.f_getFrameFilterData));},isInComentSite:function(){var b=false;try{if(!CY.Lang.isUndefined(sv_site_url)&&!CY.Lang.isUndefined(parent)&&!CY.Lang.isUndefined(parent.parent)){var a=new String(parent.parent.location);b=(a.indexOf(sv_site_url)==0);}}catch(c){b=false;}return b;},sliderValToPx:function(d){var a=CY.DOM.winWidth();if(this.isInFrame()){a=parent.$(parent).width();}var b=d/100;b=Math.min(b,gConf.sliderFixedMin);b=Math.max(b,gConf.sliderFixedMax);var c=b*a;return Math.floor(c);},getTopICommentsWidth:function(){return this.getTopICommentsWidthFromWidth(this.sliderValToPx(gPrefs.get("layout","comments_col_width")));},getTopICommentsWidthFromWidth:function(b){var a=b-(2*gConf.iCommentThreadPadding);return a-7;},setLeftColumnWidth:function(a){CY.get("#contentcolumn").setStyle("marginLeft",a+"px");CY.get("#leftcolumn").setStyle("width",a+"px");},parentInterfaceUnfreeze:function(){if(this.isInFrame()){parent.f_interfaceUnfreeze();}}};Preferences=function(){this.prefs={};};Preferences.prototype={init:function(){this._read();},_read:function(){for(var b in gConf.defaultPrefs){this.prefs[b]={};for(var a in gConf.defaultPrefs[b]){var c=null;if(b=="user"&&(a=="name"||a=="email")){c=CY.Cookie.get("user_"+a);}else{c=CY.Cookie.getSub(b,a);}this.prefs[b][a]=(c==null)?gConf.defaultPrefs[b][a]:c;}}},persist:function(b,a,d){var c={path:"/",expires:(new Date()).setFullYear(2100,0,1)};if(b=="user"&&(a=="name"||a=="email")){CY.Cookie.set("user_"+a,d,c);}else{CY.Cookie.setSub(b,a,d,c);}this.prefs[b][a]=d;},get:function(b,a){return this.prefs[b][a];},readDefault:function(b,a){return gConf.defaultPrefs[b][a];},reset:function(a){for(var b=0;b0){this._animateTo(a);}}},showSingleComment:function(a){this._q.add({fn:CY.bind(this.setPreventClickOn,this)});this._showSingleComment(a);this._q.add({fn:CY.bind(this.setPreventClickOff,this)});this._q.run();},browse:function(a,b){var c=gIComments.browse(a,b);if(c!=null){this.showSingleComment(c);}},_showComments:function(c,b,a){this._q.add({fn:function(){gShowingAllComments=a;gIComments.hide();var d=CY.Array.map(c,function(g){return gDb.getComment(g);});var f=gDb.getThreads(d);gIComments.fetch(f);if(c.length>0){if(a){CY.get("document").set("scrollTop",0);}else{gIComments.activate(c[0]);var e=CY.get(".c-id-"+c[0]);if(e&&!e.inViewportRegion()){e.scrollIntoView(true);}}}gIComments.setPosition([gConf.iCommentLeftPadding,b]);gIComments.show();}});},_animateTo:function(a){this._q.add({fn:function(){gIComments.setAnimationToPositions(a);}},{id:"animationRun",autoContinue:false,fn:CY.bind(gIComments.runAnimations,gIComments)});},_animateToTop:function(){var a=gIComments.getTopPosition();if(a!=null){this._animateTo(a[1]);}},animateToTop:function(){this._q.add({fn:CY.bind(this.setPreventClickOn,this)});this._animateToTop();this._q.add({fn:CY.bind(this.setPreventClickOff,this)});this._q.run();},showAllComments:function(){checkForOpenedDialog(null,function(){gShowingAllComments=true;var a=CY.Array.map(gDb.comments,function(b){return b.id;});this.showComments(a,[0,0],true);},this,null);},showScopeRemovedComments:function(){checkForOpenedDialog(null,function(){gShowingAllComments=true;var b=CY.Array.filter(gDb.comments,function(c){return(c.start_wrapper==-1);});var a=CY.Array.map(b,function(d){return d.id;});this.showComments(a,[0,0],true);},this,null);},showComments:function(c,b,a){checkForOpenedDialog(null,function(){this._q.add({fn:CY.bind(this.setPreventClickOn,this)});this._showComments(c,b[1],a);this._animateTo(b[1]);this._q.add({fn:CY.bind(this.setPreventClickOff,this)});this._q.run();},this,null);},openComment:function(a){this._q.add({fn:CY.bind(this.setPreventClickOn,this)});var b=gIComments.getTopPosition()[1];this._q.add({fn:function(){gIComments.open(a.commentId);gIComments.refresh(a.commentId);}});this._animateTo(b);this._q.add({fn:CY.bind(this.setPreventClickOff,this)});this._q.run();},closeComment:function(a){checkForOpenedDialog(a,function(){this._q.add({fn:CY.bind(this.setPreventClickOn,this)});var b=gIComments.getTopPosition()[1];this._q.add({fn:function(){var c=gDb.getComment(a.commentId);gIComments.close(a.commentId);if(c.reply_to_id!=null){gIComments.refresh(c.reply_to_id);}}});this._animateTo(b);this._q.add({fn:CY.bind(this.setPreventClickOff,this)});this._q.run();},this,null);},activate:function(a){gIComments.activate(a.commentId);}};readyForAction=function(){return !gSync._iPreventClick;};getWrapperAncestor=function(a){var b=a;while(b!=null){if(CY.DOM.hasClass(b,"c-s")){return b;}b=b.parentNode;}return null;};hasWrapperAncestor=function(a){return(getWrapperAncestor(a)!=null);};getSelectionInfo=function(){var J=null,m=null,D=0,c=0,h="";if(window.getSelection){var r=window.getSelection();if(r.rangeCount>0){var l=r.getRangeAt(0);h=l.toString();if(h!=""){var E=document.createRange();E.setStart(r.anchorNode,r.anchorOffset);E.collapse(true);var B=document.createRange();B.setEnd(r.focusNode,r.focusOffset);B.collapse(false);var I=(B.compareBoundaryPoints(2,E)==1);J=(I)?r.anchorNode.parentNode:r.focusNode.parentNode;innerStartNode=(I)?r.anchorNode:r.focusNode;m=(I)?r.focusNode.parentNode:r.anchorNode.parentNode;innerEndNode=(I)?r.focusNode:r.anchorNode;D=(I)?r.anchorOffset:r.focusOffset;c=(I)?r.focusOffset:r.anchorOffset;if(!hasWrapperAncestor(m)&&hasWrapperAncestor(J)){var z=document.createRange();z.setStart(innerStartNode,D);var b=getWrapperAncestor(J);var q=b;z.setEndAfter(q);var f=parseInt(b.id.substring("sv_".length));while(z.toString().lengthv.compareBoundaryPoints(2,l))){J=k.firstChild;D=0;m=F.lastChild;c=CY.DOM.getText(F).length;w=true;break;}}if(w){break;}}}}}E.detach();B.detach();}else{return null;}}else{return null;}}else{if(document.selection){var d=document.selection.createRange();if(d.text.length==0){return null;}var a=d.parentElement();var H=d.duplicate();var u=d.duplicate();H.collapse(true);u.collapse(false);J=H.parentElement();while(H.moveStart("character",-1)!=0){if(H.parentElement()!=J){break;}D++;}m=u.parentElement();while(u.moveEnd("character",-1)!=0){if(u.parentElement()!=m){break;}c++;}h=d.text;}}if(!hasWrapperAncestor(J)||!hasWrapperAncestor(m)){return null;}return{text:h,start:{elt:J,offset:D},end:{elt:m,offset:c}};};Db=function(){this.comments=null;this.allComments=null;this.commentsByDbId={};this.allCommentsByDbId={};this.ordered_comment_ids={};};Db.prototype={init:function(){this.allComments=CY.JSON.parse(sv_comments);if(sv_read_only){this.initToReadOnly();}this._computeAllCommentsByDbId();this._reorder();},_del:function(a,e,g){var f=e[g];for(var c=0;cm[l.id]){m[l.id]=d;}}}for(var b in m){var h=this.allCommentsByDbId[b].id;var r=false;for(var k=0,c=n.length;k0){a=a.concat(this.getThreads(c[b].replies));}}return a;},_getPath:function(b,e){var a=[e];var d=e;while(d.reply_to_id!=null){d=b[d.reply_to_id];a.push(d);}return a;},getPath:function(a){return this._getPath(this.commentsByDbId,a);},getComment:function(a){return this.commentsByDbId[a];},getCommentByIdKey:function(a){for(var c in this.commentsByDbId){var b=this.commentsByDbId[c];if(b.id_key==a){return b;}}return null;},isChild:function(d,b){var c=this.commentsByDbId[d];var a=(d==b);while((!a)&&(c.reply_to_id!=null)){c=this.commentsByDbId[c.reply_to_id];a=(c.id==b);}return a;},initToReadOnly:function(f,c){for(var b=0,a=this.allComments.length;b0){var g=-1;if((f=="prev")||(f=="next")){for(var e=0;e=0)&&(ea.length){break;}}CY.error("internal error in db browse (could not find any filtered comment)");}return null;},computeFilterResults:function(n){var a={};if(n){for(key in n){if(key.indexOf("filter_")==0){a[key.substr("filter_".length)]=n[key];}}}else{if(gLayout.isInFrame()){a=parent.f_getFrameFilterData();}}var v=[];var w=[];var b="";if("name" in a){b=a.name;}this.filterByName(b,v,w);var p=[];var c=[];var C="";if("date" in a){C=a.date;}this.filterByDate(C,p,c);var g=[];var f=[];var t="";if("text" in a){t=a.text;}this.filterByText(t,g,f);var x=[];var m=[];var A="";if("tag" in a){A=a.tag;}this.filterByTag(A,x,m);var u=[];var e=[];var k="";if("state" in a){k=a.state;}this.filterByState(k,u,e);var d=[];var z=[];for(var y=0,j=v.length;yc){if(e.reply_to_id==null){d.push(e.id);}else{a.push(e.id);}}}},getCommentsAndRepliesCounts:function(d){var b=0;var f=0;var a=(d)?this.allComments:this.comments;var e=this.getThreads(a);for(var c=0;c