diff --git a/.hgtags b/.hgtags index 9a71360985767be4a6138b3e5db2e8adc386d329_LmhndGFncw==..f3028cde1494abe23e81cff0a85fa1be76668595_LmhndGFncw== 100644 --- a/.hgtags +++ b/.hgtags @@ -10,3 +10,8 @@ 0e01981cc5698cb482026488041968104e8cea7b cubicweb-comment-debian-version-1.5.0-1 5dcf4eca8238c65353bd3b47ee174d43715682d2 cubicweb-comment-version-1.6.0 1e74587f201a84f8c77bf10116a4179b1743588d cubicweb-comment-debian-version-1.6.0-1 +4add56175ecca553de4cac1b9da370cfde011024 oldstable +29ae664a7d7cbc19b31f032beb161b78de9450dc cubicweb-comment-version-1.6.1 +0a7368f774db033fc4839ee01480d12bf51f1630 cubicweb-comment-debian-version-1.6.1-1 +15faf8f3b555feb5422fe2c1edc567225b574a09 cubicweb-comment-version-1.6.2 +a14db4d459494a413d481c6e658718e5845a52d1 cubicweb-comment-debian-version-1.6.2-1 diff --git a/__pkginfo__.py b/__pkginfo__.py index 9a71360985767be4a6138b3e5db2e8adc386d329_X19wa2dpbmZvX18ucHk=..f3028cde1494abe23e81cff0a85fa1be76668595_X19wa2dpbmZvX18ucHk= 100644 --- a/__pkginfo__.py +++ b/__pkginfo__.py @@ -4,7 +4,7 @@ modname = 'comment' distname = "cubicweb-%s" % modname -numversion = (1, 6, 0) +numversion = (1, 6, 2) version = '.'.join(str(num) for num in numversion) license = 'LGPL' @@ -15,7 +15,7 @@ author_email = "contact@logilab.fr" web = 'http://www.cubicweb.org/project/%s' % distname -short_desc = "comment component for the CubicWeb framework" +short_desc = "commenting system for the CubicWeb framework" long_desc = """\ Summary ------- diff --git a/data/cubes.comment.css b/data/cubes.comment.css index 9a71360985767be4a6138b3e5db2e8adc386d329_ZGF0YS9jdWJlcy5jb21tZW50LmNzcw==..f3028cde1494abe23e81cff0a85fa1be76668595_ZGF0YS9jdWJlcy5jb21tZW50LmNzcw== 100644 --- a/data/cubes.comment.css +++ b/data/cubes.comment.css @@ -39,3 +39,7 @@ li.comment li.comment { margin-left: 2.8em; } + +.replyComment { + padding-left: 30px; +} diff --git a/data/cubes.comment.js b/data/cubes.comment.js index 9a71360985767be4a6138b3e5db2e8adc386d329_ZGF0YS9jdWJlcy5jb21tZW50Lmpz..f3028cde1494abe23e81cff0a85fa1be76668595_ZGF0YS9jdWJlcy5jb21tZW50Lmpz 100644 --- a/data/cubes.comment.js +++ b/data/cubes.comment.js @@ -1,6 +1,6 @@ /* * :organization: Logilab - * :copyright: 2003-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved. + * :copyright: 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. * :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr */ @@ -10,12 +10,12 @@ function _getText(textarea) { if (typeof(FCKeditor) != 'undefined') { - var fck = FCKeditorAPI.GetInstance(textarea.id); - if (fck) { - return fck.GetHTML(); - } + var fck = FCKeditorAPI.GetInstance(textarea.id); + if (fck) { + return fck.GetHTML(); + } } return textarea.value; } /* this function is called on inlined-comment editions @@ -17,8 +17,8 @@ } return textarea.value; } /* this function is called on inlined-comment editions - * It calls the add_comment method on the jsoncontroller and reload - * the comment's component + * It calls the [add|eid]_comment method on the jsoncontroller and [re]load + * only the view for the added or edited comment */ @@ -24,5 +24,5 @@ */ -function processComment(eid, funcname) { - var buttonbar = jQuery('#comment' + eid + 'bbar').hide(); - var divNode = jQuery('#comment' + eid + 'Slot'); +function processComment(eid, funcname, creation) { + var divId = 'comment' + eid + 'Slot' + var divNode = jQuery('#'+divId); var textarea = divNode.find('textarea')[0]; @@ -28,3 +28,2 @@ var textarea = divNode.find('textarea')[0]; - var comment = _getText(textarea); if (funcname) { @@ -30,8 +29,37 @@ if (funcname) { - // store original value for edit cancel - textarea.setAttribute('cubicweb:origval', comment); - var format = 'text/html'; // no select widget if fckeditor is used - var select = divNode.find('select')[0]; - if (select) { - format = firstSelected(select); + var format = 'text/html'; // no select widget if fckeditor is used + var select = divNode.find('select')[0]; + if (select) { + format = firstSelected(select); + } + d = asyncRemoteExec(funcname, eid, _getText(textarea), format); + d.addCallback(function (neweid) { + if (creation) { + var commentNode = jQuery('#comment'+ eid); + var ul = null; + if (!commentNode.length) { + // we are adding a comment to the top level entity + commentNode = jQuery('#commentsectionComponent'); + klass = 'comment'; + } else { + klass = 'section'; + } + ul = commentNode.find('> ul:first'); + if (!ul.length) { + ul = jQuery(UL({'class': klass})); + commentNode.append(ul); + } + ul.append(LI({'id': 'comment'+ neweid, 'class': 'comment'}, + DIV({'id': 'comment'+ neweid + 'Div'}))); + divNode.remove(); + eid = neweid; + } + replacePageChunk('comment' + eid + 'Div', rql_for_eid(eid), 'treeitem'); + }); + } else { + // comment cancelled, close div holding the form + divNode.remove(); + // on edition, show back the comment's content + if (!creation) { + jQuery('#comment' + eid + 'Div div').show(); } @@ -37,12 +65,4 @@ } - d = asyncRemoteExec(funcname, eid, comment, format); - d.addCallback(function () { - var rooteid = getNode('commentsectionComponent').getAttribute('cubicweb:rooteid'); - reloadComponent('commentsection', rql_for_eid(rooteid), 'contentnavigation'); - }); - } else { // this is of course terrbily inefficient since we reload the whole tree - var rooteid = getNode('commentsectionComponent').getAttribute('cubicweb:rooteid'); - reloadComponent('commentsection', rql_for_eid(rooteid), 'contentnavigation'); } } diff --git a/debian/changelog b/debian/changelog index 9a71360985767be4a6138b3e5db2e8adc386d329_ZGViaWFuL2NoYW5nZWxvZw==..f3028cde1494abe23e81cff0a85fa1be76668595_ZGViaWFuL2NoYW5nZWxvZw== 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,15 @@ +cubicweb-comment (1.6.2-1) unstable; urgency=low + + * new upstream release + + -- Sylvain Thénault <sylvain.thenault@logilab.fr> Thu, 08 Apr 2010 11:38:52 +0200 + +cubicweb-comment (1.6.1-1) unstable; urgency=low + + * new upstream release + + -- Sandrine Ribeau <sandrine.ribeau@logilab.fr> Wed, 24 Mar 2010 10:38:54 -0700 + cubicweb-comment (1.6.0-1) unstable; urgency=low * new upstream release diff --git a/entities.py b/entities.py index 9a71360985767be4a6138b3e5db2e8adc386d329_ZW50aXRpZXMucHk=..f3028cde1494abe23e81cff0a85fa1be76668595_ZW50aXRpZXMucHk= 100644 --- a/entities.py +++ b/entities.py @@ -12,10 +12,10 @@ from cubicweb.interfaces import ITree from cubicweb.mixins import TreeMixIn from cubicweb.selectors import implements -from cubicweb.entities import AnyEntity +from cubicweb.entities import AnyEntity, fetch_config class Comment(TreeMixIn, AnyEntity): """customized class for Comment entities""" __regid__ = 'Comment' tree_attribute = 'comments' __implements__ = AnyEntity.__implements__ + (ITree,) @@ -16,9 +16,11 @@ class Comment(TreeMixIn, AnyEntity): """customized class for Comment entities""" __regid__ = 'Comment' tree_attribute = 'comments' __implements__ = AnyEntity.__implements__ + (ITree,) + fetch_attrs, fetch_order = fetch_config(('creation_date',), + 'creation_date', order='ASC') def dc_title(self): return u'#%s' % self.eid diff --git a/schema.py b/schema.py index 9a71360985767be4a6138b3e5db2e8adc386d329_c2NoZW1hLnB5..f3028cde1494abe23e81cff0a85fa1be76668595_c2NoZW1hLnB5 100644 --- a/schema.py +++ b/schema.py @@ -1,6 +1,6 @@ """ :organization: Logilab -:copyright: 2003-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +:copyright: 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr """ from yams.buildobjs import EntityType, RelationType, SubjectRelation @@ -22,7 +22,6 @@ comments = SubjectRelation('Comment', cardinality='1*', composite='object') class comments(RelationType): - __permissions__ = { 'read': ('managers', 'users', 'guests'), 'add': ('managers', 'users',), diff --git a/test/unittest_comment.py b/test/unittest_comment.py index 9a71360985767be4a6138b3e5db2e8adc386d329_dGVzdC91bml0dGVzdF9jb21tZW50LnB5..f3028cde1494abe23e81cff0a85fa1be76668595_dGVzdC91bml0dGVzdF9jb21tZW50LnB5 100644 --- a/test/unittest_comment.py +++ b/test/unittest_comment.py @@ -89,6 +89,24 @@ self.assertEquals(comment2.path(), [teid, eid1, eid2]) self.assertEquals(comment2.root().eid, teid) + def test_comments_ascending_order(self): + teid = self.execute('BlogEntry X')[0][0] + c1 = self.entity('INSERT Comment X: X content "one", X comments Y WHERE Y eid %s'%teid) + eid1 = c1.eid + c11 = self.execute('INSERT Comment X: X content "one-one", X comments Y WHERE Y eid %s'%eid1) + c12 = self.execute('INSERT Comment X: X content "one-two", X comments Y WHERE Y eid %s'%eid1) + c2 = self.entity('INSERT Comment X: X content "two", X comments Y WHERE Y eid %s'%teid) + eid2 = c2.eid + c21= self.execute('INSERT Comment X: X content "two-one", X comments Y WHERE Y eid %s'%eid2) + c22= self.execute('INSERT Comment X: X content "two-two", X comments Y WHERE Y eid %s'%eid2) + self.commit() + rql = u'Any C,CD,CC,CCF,U,UL,US,UF ORDERBY CD WHERE C is Comment, '\ + 'C comments X, C creation_date CD, C content CC, C content_format CCF, ' \ + 'C created_by U?, U login UL, U firstname UF, U surname US, X eid %(x)s' + all_comments = self.execute(rql, {'x': teid}) + self.assertEquals([c.eid for c in all_comments.entities()], [self.c[0][0], eid1, eid2]) + self.assertEquals([c.eid for c in c1.children()], [c11[0][0], c12[0][0]]) + def test_fullthreadtext_views(self): c = self.entity('Comment X') c2eid = self.execute('INSERT Comment X: X content %(text)s, X content_format "text/html", ' diff --git a/views.py b/views.py index 9a71360985767be4a6138b3e5db2e8adc386d329_dmlld3MucHk=..f3028cde1494abe23e81cff0a85fa1be76668595_dmlld3MucHk= 100644 --- a/views.py +++ b/views.py @@ -1,7 +1,7 @@ """Specific views and actions for application using the Comment entity type :organization: Logilab -:copyright: 2003-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +:copyright: 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr """ __docformat__ = "restructuredtext en" @@ -27,7 +27,7 @@ from cubicweb.web.formwidgets import Button from cubicweb.web.views import primary, baseviews, xmlrss from cubicweb.web.component import EntityVComponent -from cubicweb.web.views.basecontrollers import JSonController +from cubicweb.web.views.basecontrollers import JSonController, jsonize uicfg.autoform_section.tag_subject_of(('*', 'comments', '*'), formtype='main', section='hidden') @@ -114,6 +114,9 @@ self._cw.add_css('cubes.comment.css') entity = self.cw_rset.get_entity(row, col) actions = self._cw.vreg['actions'] + # DOM id of the whole comment's content + cdivid = 'comment%sDiv' % entity.eid + self.w(u'<div id="%s">' % cdivid) self.w(u'<div class="commentInfo">') self.w(self._cw.format_date(entity.creation_date)) self.w(u' %s' % self._cw.format_time(entity.creation_date)) @@ -137,9 +140,11 @@ editaction = actions.select_or_none('edit_comment', self._cw, rset=self.cw_rset, row=row) if editaction is not None: - url = self._cw.build_ajax_replace_url( - 'comment%s' % entity.eid, rql_for_eid(entity.eid), - 'editcomment') + # split(':', 1)[1] to remove javascript: + formjs = self._cw.build_ajax_replace_url( + cdivid, rql_for_eid(entity.eid), + 'editcomment', 'append').split(':', 1)[1] + url = "javascript: jQuery('#%s div').hide(); %s" % (cdivid, formjs) self.w(u' | <span class="replyto"><a href="%s">%s</a></span>' % (xml_escape(url), self._cw._(editaction.title))) @@ -148,6 +153,7 @@ if deleteaction is not None: url = self._cw.build_ajax_replace_url( 'comment%s' % entity.eid, rql_for_eid(entity.eid), - 'deleteconf') + 'deleteconf', __redirectpath=entity.root().rest_path()) + self.w(u' | <span class="replyto"><a href="%s">%s</a></span>' % (xml_escape(url), self._cw._(deleteaction.title))) @@ -152,13 +158,11 @@ self.w(u' | <span class="replyto"><a href="%s">%s</a></span>' % (xml_escape(url), self._cw._(deleteaction.title))) - - self.w(u'</div>\n') - text = entity.printable_value('content') - if not kwargs.get('full'): - maxsize = self._cw.property_value('navigation.short-line-size') - text = safe_cut(text, maxsize) - self.w(u'<div class="commentBody">%s</div>\n' % text) - self.w(u'<div id="comment%sHolder"></div>' % entity.eid) + self.w(u'</div>\n') # close comment's info div + self.w(u'<div class="commentBody">%s</div>\n' + % entity.printable_value('content')) + # holder for reply form + self.w(u'<div id="comment%sHolder" class="replyComment"></div>' % entity.eid) + self.w(u'</div>\n') # close comment's content div class CommentThreadView(TreeViewMixIn, baseviews.ListView): @@ -180,8 +184,7 @@ entity = self.cw_rset.get_entity(row, col) self.wview('inlinecommentform', None, commented=entity) - class InlineEditCommentForm(FormViewMixIn, EntityView): __regid__ = 'editcomment' __select__ = implements('Comment') @@ -184,8 +187,8 @@ class InlineEditCommentForm(FormViewMixIn, EntityView): __regid__ = 'editcomment' __select__ = implements('Comment') - jsfunc = "processComment(%s, '%s')" + jsfunc = "processComment(%s, '%s', false)" jsonmeth = 'edit_comment' def cell_call(self, row, col): @@ -198,5 +201,6 @@ # hack to avoid tabindex conflicts caused by Ajax requests self._cw.next_tabindex = count(20).next jseid = dumps(commented.eid) + cancel_action = self.jsfunc % (jseid, '') buttons = [Button(onclick=self.jsfunc % (jseid, self.jsonmeth)), Button(stdmsgs.BUTTON_CANCEL, @@ -201,6 +205,6 @@ buttons = [Button(onclick=self.jsfunc % (jseid, self.jsonmeth)), Button(stdmsgs.BUTTON_CANCEL, - onclick=self.jsfunc % (jseid, ''))] + onclick=cancel_action)] form = self._cw.vreg['forms'].select('edition', self._cw, entity=newcomment, form_buttons=buttons) @@ -214,6 +218,7 @@ __regid__ = 'inlinecommentform' __select__ = match_kwargs('commented') # explicit call when it makes sense + jsfunc = "processComment(%s, '%s', true)" jsonmeth = 'add_comment' def call(self, commented): @@ -246,7 +251,13 @@ rset = req.execute(rql, {'x': eid}, 'x') if rset.rowcount: self.w(u'<h4>%s</h4>' % (req._('Comment_plural'))) + if rset.rowcount: + self.w(u'<ul class="comment">') + for i in xrange(rset.rowcount): + self.wview('tree', rset, row=i) + self.w(u'</ul>') + self.w(u'</div>') addcomment = self._cw.vreg['actions'].select_or_none('reply_comment', req, rset=self.cw_rset, row=row, col=col) if addcomment is not None: @@ -249,7 +260,8 @@ addcomment = self._cw.vreg['actions'].select_or_none('reply_comment', req, rset=self.cw_rset, row=row, col=col) if addcomment is not None: + self.w(u'<div id="comment%sHolder"></div>' % eid) url = req.build_ajax_replace_url( 'comment%sHolder' % eid, rql_for_eid(eid), 'inlinecomment') self.w(u' (<a href="%s" onclick="javascript:toggleVisibility(\'addCommentLinks\');">%s</a>)' % (url, req._(addcomment.title))) @@ -259,13 +271,6 @@ if req.cnx.anonymous_connection: self.w(u'<div id="addCommentLinks" class="hidden">%s %s</div>' % \ (_login_register_link(req), req._(u'to comment'))) - self.w(u'<div id="comment%sHolder"></div>' % eid) - if rset.rowcount: - self.w(u'<ul class="comment">') - for i in xrange(rset.rowcount): - self.wview('tree', rset, row=i, full=True) - self.w(u'</ul>') - self.w(u'</div>') # comment actions ############################################################# @@ -332,4 +337,5 @@ # add some comments related methods to the Jsoncontroller ##################### @monkeypatch(JSonController) +@jsonize def js_add_comment(self, commented, text, format): @@ -335,6 +341,6 @@ def js_add_comment(self, commented, text, format): - self._cw.execute('INSERT Comment C: C comments X, C content %(text)s, ' - 'C content_format %(format)s WHERE X eid %(x)s', - {'format' : format, 'text' : text, 'x' : commented}, 'x') + return self._cw.execute('INSERT Comment C: C comments X, C content %(text)s, ' + 'C content_format %(format)s WHERE X eid %(x)s', + {'format' : format, 'text' : text, 'x' : commented}, 'x')[0][0] @monkeypatch(JSonController) @@ -339,5 +345,6 @@ @monkeypatch(JSonController) +@jsonize def js_edit_comment(self, comment, text, format): self._cw.execute('SET C content %(text)s, C content_format %(format)s ' 'WHERE C eid %(x)s',