To allow scrolling in Safari mobile, we set the content of text_view_comments frame in a jQuery UI layout.
So the automated scrolling operations in c_sync.js must be adjustable to the right part to scroll.
Also, if a comment have to be shown outside of the current viewport, we scroll the correct part to that viewport
and then set the comment top Y offset to juste what it needs to avoid the "Add comment" button after scrolling
operation.
If not in Safari mobile, we add an offset here to avoid comment to display under the "Add comment" button.
// gConf
IComments = function() {
// this class manages Comments interface (fetched comments <--> IComment.commentId != null)
this._c = [] ; // IComments instances // commentId == null means is connected to no comment anymore
this._a = [] ; // IComments animations to run
this._nbEndedAnim = 0 ;
this._topActiveCommentDbId = null ; // active
}
IComments.prototype = {
init : function (container) {
for (var i = 0 ; i < gConf['iCommentsInitAlloc']; i++) {
this._c.push(new IComment()) ;
}
},
getIComment : function (commentId) {
return CY.Array.find(this._c, function(iComment) { return (iComment.isfetched() && iComment.commentId == commentId) ;}) ;
},
insertAfter : function (previousComment, comment) {
var _cids = CY.Array.map(this._c, function(iComment) { return iComment.commentId ; }) ;
var index = CY.Array.indexOf(_cids, previousComment.id) ;
if (index != -1) {
this._c.splice(index + 1, 0, new IComment()) ; // will append when index + 1 == array length
this._c[index + 1].fetch(comment) ;
return this._c[index + 1] ;
}
return null ;
},
_remove : function (iComments) {
var toRemoveIds = CY.Array.map(iComments, function(comment) { return comment.commentId ; }) ;
for (var i = 0 ; i < this._c.length ; i++) { // starting at the bottom to be sure that last remove will be iComment
var iComment2 = this._c[i] ;
if (iComment2.isfetched() && CY.Array.indexOf(toRemoveIds, iComment2.commentId) != -1) {
iComment2.unfetch() ;
this._c.push(this._c.splice(i, 1)[0]) ;
i-- ;
}
}
},
// all children, comment's IComment included !
// model based (cf. gDb)
_getChildren : function (commentId) {
return CY.Array.filter(this._c, function(iComment) { return (iComment.isfetched() && gDb.isChild(iComment.commentId,commentId)) ; }) ;
},
_getInvisibleChildren : function (commentId) {
return CY.Array.filter(this._getChildren(commentId), function(iComment) { return (!iComment.isVisible()) ; }) ;
},
// REFRESH (readreplies link etc ?..)
refresh : function (commentId) {
var iComment = this.getIComment(commentId) ;
var invisibleChildrenIComments = this._getInvisibleChildren(commentId) ;
if (invisibleChildrenIComments.length > 0) //parentIComment is supposed to be visible
iComment.showReadRepliesLnk() ;
else
iComment.hideReadRepliesLnk() ;
},
remove : function (commentId) {
this._remove(this._getChildren(commentId)) ;
},
close : function (commentId) {
CY.Array.each(this._getChildren(commentId), function (iComment) { iComment.hide() ; }) ;
},
open : function (commentId) {
CY.Array.each(this._getChildren(commentId), function (iComment) { iComment.show() ; }) ;
},
fetch : function (comments) {
// fill
for (var i = 0 ; i < comments.length; i++) {
if (i == this._c.length)
this._c.push(new IComment()) ;
this._c[i].fetch(comments[i]) ;
}
// nullify others
for (var i = comments.length ; i < this._c.length ; i++) {
this._c[i].unfetch() ;
}
},
setPosition : function (xy) {
CY.each(this._c, function (iComment) { iComment.setPosition(xy) ; }) ;
},
show : function () {
CY.each(this._c, function (iComment) {
if (iComment.isfetched()) {
iComment.show();
}}) ;
},
hide : function () {
this.deactivate(); // to prevent a chain of activate / deactivate while hiding IComments one by one
CY.each(this._c, function (iComment) { if (iComment.commentId != null) iComment.hide(); }) ;
},
setWidth : function (colWidth) {
var nextY = null ;
for (var i = 0 ; i < this._c.length; i++) {
var iComment = this._c[i] ;
iComment.setWidth(colWidth) ;
if (iComment.commentId != null && iComment.isVisible()) {
var xy = iComment.getPosition() ;
if (nextY == null)
nextY = xy[1] ;
xy[1] = nextY ;
iComment.setPosition(xy) ;
nextY += iComment.getHeight() ;
}
}
},
getTopPosition:function() {
for (var i = 0 ; i < this._c.length; i++) {
var iComment = this._c[i] ;
if (iComment.commentId != null && iComment.isVisible())
return iComment.getPosition() ;
}
return null
},
getPosition:function(id) {
for (var i = 0 ; i < this._c.length; i++) {
var iComment = this._c[i] ;
if (iComment.commentId == id && iComment.isVisible())
return iComment.getPosition() ;
}
return null ;
},
setAnimationToPositions : function (y) {
this._initAnimations();
//CY.log(gPrefs.get('comments','threadpad')) ;
var lpad = (gPrefs.get('comments','threadpad') == '1') ? gConf['iCommentThreadPadding'] : 0 ; // gIThreadPad ... TODO 'configurize'
var nextY = y ;
for (var i = 0 ; i < this._c.length;i++) {
var iComment = this._c[i] ;
if (iComment.isfetched && iComment.isVisible()) {
var comment_path = gDb.getPath(gDb.getComment(iComment.commentId)) ;
var iCommentX = ((comment_path.length - 1) * lpad) + gConf['iCommentLeftPadding'] ;
if (nextY == null) {
var xy = iComment.getPosition() ;
nextY = xy[1] ;
}
this._a.push(iComment.setAnimationToPosition([iCommentX, nextY])) ;
nextY += iComment.getHeight() ;
}
}
},
setAnimationToPositionsAndFocus : function (y, focusCommentId, reply) {
this._initAnimations();
var lpad = (gPrefs.get('comments','threadpad') == '1') ? gConf['iCommentThreadPadding'] : 0 ;
var nextY = y ;
for (var i = 0 ; i < this._c.length;i++) {
var iComment = this._c[i] ;
if (iComment.isfetched && iComment.isVisible()) {
var comment_path = gDb.getPath(gDb.getComment(iComment.commentId)) ;
var iCommentX = ((comment_path.length - 1) * lpad) + gConf['iCommentLeftPadding'] ;
if (nextY == null) {
var xy = iComment.getPosition() ;
nextY = xy[1] ;
}
if (iComment.commentId >= focusCommentId)
this._a.push(iComment.setAnimationToPosition([iCommentX, nextY], focusCommentId, reply)) ;
else
this._a.push(iComment.setAnimationToPosition([iCommentX, nextY])) ;
nextY += iComment.getHeight() ;
}
}
},
// ANIMATION FUNCTIONS
_initAnimations : function () {
this._a = [] ;
this._nbEndedAnim = 0 ;
},
runAnimations : function () {
if (this._a.length == 0) // will occur when closing last displayed comment
gSync.resetAutoContinue("animationRun") ;
else
CY.each(this._a, function (animation) { animation.run() ; }) ;
},
whenAnimationsEnd : function () {
gSync.resume() ;
},
whenAnimationsEndFocus : function () {
gGETValues = CY.JSON.parse(sv_get_params);
if ("comment_id_key" in gGETValues) {
var id_key = gGETValues["comment_id_key"];
var focusComment = gDb.getCommentByIdKey(id_key);
if (focusComment != null)
gIComments.getIComment(focusComment.id).overlay.focus();
}
gSync.resume();
},
whenAnimationsEndReply : function () {
gGETValues = CY.JSON.parse(sv_get_params);
if ("comment_id_key" in gGETValues) {
var id_key = gGETValues["comment_id_key"];
var focusComment = gDb.getCommentByIdKey(id_key);
if (focusComment != null) {
gSync.showReplyForm(gIComments.getIComment(focusComment.id));
}
}
gSync.resume();
},
animationsEnded : function () {
return ((this._a.length == 0) || (this._a.length == this._nbEndedAnim)) ;
},
signalAnimationEnd : function () {
this._nbEndedAnim++ ;
},
// ACTIVE RELATED FUNCTIONS
// returns true only for the top iComment
isTopActive : function(commentDbId) {
return ((commentDbId != null) && (this._topActiveCommentDbId == commentDbId)) ;
},
isAnyActive : function() {
return (this._topActiveCommentDbId != null) ;
},
//warning : calling this function "focus" would make IE get mad
activate : function(commentDbId) {
if (this._topActiveCommentDbId != null) {// then deactivate current
this.deactivate() ;
}
// activate whole top parent thread
var comment = gDb.getComment(commentDbId) ;
var comment_path = gDb.getPath(comment) ;
var topParent = comment_path[comment_path.length - 1] ;
var iComments = this._getChildren(topParent.id) ;
CY.Array.each(iComments, function(iComment){iComment.activate();}) ;
this._topActiveCommentDbId = topParent.id ;
// update browser index
if (gLayout.isInFrame()) {
var indxDict = gDb.browsingIndex(this._topActiveCommentDbId) ;
parent.$("#browse_by option").each(function() {
var rank = 1 + indxDict[this.value] ;
parent.$("#c_browse_indx_"+this.value).html(''+ rank) ;
}) ;
}
showScope(topParent.id) ;
},
deactivate : function() {
if (this._topActiveCommentDbId != null) {
parent.$("#browse_by option").each(function() {
parent.$("#c_browse_indx_"+this.value).html('-') ;
}) ;
// scopes
hideScopeAnyway() ;
var iComments = this._getChildren(this._topActiveCommentDbId) ;
CY.Array.each(iComments, function(iComment){iComment.deactivate();}) ;
this._topActiveCommentDbId = null ;
}
},
// active next IComment among the displayed ones (try below and if couldn't try above)
// model based
activateVisibleNext : function() {
if (this._topActiveCommentDbId != null) {
for (var j = 0 ; j < 2 ; j++) {
var start = (j==0) ? 0 : this._c.length - 1 ;
var b = false ;
for (var i = start ; (i >= 0) && i <= (this._c.length - 1) ;) {
var iComment = this._c[i] ;
if (iComment.commentId != null && iComment.isVisible()) {
b = b || (gDb.isChild(iComment.commentId,this._topActiveCommentDbId)) ;
if (b && (!gDb.isChild(iComment.commentId,this._topActiveCommentDbId))) { // found the one below that doesn't have topActive as parent
this.activate(iComment.commentId) ;
return true ;
}
}
i = (j == 0) ? i + 1 : i - 1 ;
}
}
}
return false ;
},
browse : function(order, whereto) {
var wt = whereto ;
if ((whereto == "prev") && !this.isAnyActive()) {
wt = "last" ;
}
if ((whereto == "next") && !this.isAnyActive()) {
wt = "first" ;
}
return gDb.browse(order, wt, this._topActiveCommentDbId) ;
}
}