src/cm/media/js/client/c_scopes.js
changeset 0 40c8f766c9b8
child 103 61fd17f9ab78
equal deleted inserted replaced
-1:000000000000 0:40c8f766c9b8
       
     1 // DOM MANIPULATION TO DYNAMICALLY RENDER COMMENTS SCOPES IN TEXT 
       
     2 
       
     3 // cf. http://www.quirksmode.org/dom/w3c_core.html#nodemanipulation (informative!)
       
     4 
       
     5 // classes :
       
     6 // c-s is for 'textnode wrapper span'
       
     7 // c-c is a comment marker
       
     8 
       
     9 paintCommentScope = function(comment) {
       
    10 	if (comment.reply_to_id == null) {
       
    11 		var selection = { 	'start' : { 'elt' : document.getElementById("sv_"+comment['start_wrapper']), 'offset' : comment['start_offset'] },
       
    12 							'end' : { 'elt' : document.getElementById("sv_"+comment['end_wrapper']), 'offset' : comment['end_offset'] }
       
    13 						} ;
       
    14 		if (document.getElementById("sv_"+comment['start_wrapper'])== null)
       
    15 			warn_server({'from':'paintCommentScope', 'start_wrapper':comment['start_wrapper']}) ;
       
    16 		else {
       
    17 			if (document.getElementById("sv_"+comment['end_wrapper'])== null)
       
    18 				warn_server({'from':'paintCommentScope', 'end_wrapper':comment['end_wrapper']}) ;
       
    19 			else {
       
    20 			    selection['start'] = _convertSelectionFromCSToCC(selection['start']) ;
       
    21 			    selection['end'] = _convertSelectionFromCSToCC(selection['end']) ;
       
    22 			
       
    23 			    renderComment(selection, comment['id']) ;
       
    24 			}
       
    25 		}
       
    26 	}
       
    27 } ;
       
    28 
       
    29 getCommentIdsFromClasses = function (elt) {
       
    30 	var commentIds = [] ;
       
    31 	var classes = elt['className'].split(" ") ;
       
    32 	for (var i = 0, ilen = classes.length ; i < ilen ; i++) {
       
    33 		if (classes[i].indexOf('c-id-') == 0) {
       
    34 			commentIds.push(parseInt(classes[i].substring('c-id-'.length))) ;
       
    35 		}
       
    36 	}
       
    37 	return commentIds ;
       
    38 } ;
       
    39 
       
    40 renderComment = function (selection, commentId) {
       
    41 	var startOffset = selection['start']['offset'] ;
       
    42 	var endOffset = selection['end']['offset'] ;
       
    43 	var startElt = selection['start']['elt'] ;
       
    44 	var endElt = selection['end']['elt'] ;
       
    45 
       
    46 	
       
    47     if ((startElt != null) && (endElt != null) && _getTextNodeContent(startElt) != '' && _getTextNodeContent(endElt) != ''){
       
    48 // log('startElt.id : ' + startElt.id) ;
       
    49 // log('endElt.id : ' + endElt.id) ;
       
    50 //    	log('startElt.innerHTML : ' + startElt.innerHTML) ;
       
    51 //    	log('endElt.innerHTML : ' + endElt.innerHTML) ;
       
    52 	    markWholeNodesAsComments(startElt, endElt, commentId) ;
       
    53 	    markEndsAsComments(startElt, startOffset, endElt, endOffset, commentId) ;
       
    54 	}
       
    55 } ;
       
    56 
       
    57 markWholeNodesAsComments = function (startElt, endElt, commentId) {
       
    58     var commonAncestor = _findCommonAncestor(startElt, endElt) ;
       
    59     _dynSpanToAnc(startElt, commonAncestor, commentId, false) ;
       
    60     
       
    61     _dynSpanToAnc(endElt, commonAncestor, commentId, true) ;
       
    62 
       
    63     _dynSpanInBetween(commonAncestor, startElt, endElt, commentId) ;
       
    64 } ;
       
    65 
       
    66 _setTextNodeContent = function(txtNodeParent, contentString) {
       
    67     CY.DOM.setText(txtNodeParent, contentString);
       
    68 } ;
       
    69 
       
    70 _getTextNodeContent = function(txtNodeParent) {
       
    71     return CY.DOM.getText(txtNodeParent);
       
    72 } ;
       
    73 
       
    74 markEndsAsComments = function(startElt, startOffset, endElt, endOffset, commentId) {
       
    75 	
       
    76 //	alert('starting with: ' + startElt.childNodes.length + 'and a length of : ' + startElt.firstChild.data.length) ; 
       
    77 //	alert('2 and a length of : ' + CY.DOM.getText(startElt).length) ; 
       
    78 
       
    79 	var beforeStart = _getTextNodeContent(startElt).substring(0, startOffset) ; 
       
    80 	var afterStart = _getTextNodeContent(startElt).substring(startOffset) ;
       
    81 	var beforeEnd = _getTextNodeContent(endElt).substring(0, endOffset) ;
       
    82 	var afterEnd = _getTextNodeContent(endElt).substring(endOffset) ;
       
    83 
       
    84     var sameNode = (startElt === endElt) ;
       
    85     
       
    86 //    log('beforeStart : ' + beforeStart + ' , afterStart : ' + afterStart + ' , beforeEnd : ' + beforeEnd + ' , afterEnd : ' + afterEnd + ' , sameNode : ' + sameNode) ;
       
    87  
       
    88     // taking care of start node : (and 'sameNode' case when start and end lie
       
    89 	// on same node)
       
    90     if (afterStart != "") { // otherwise nothing to do on the start node
       
    91     	if (CY.DOM.hasClass(startElt, 'c-c')) {
       
    92     		var lastElt = null, afterStartElt = null, afterEndElt = null, beforeStartElt = null ;
       
    93     		
       
    94         	var btw = (sameNode) ? _getTextNodeContent(startElt).substring(startOffset, endOffset) : afterStart ;
       
    95         	        	
       
    96 
       
    97         	if (sameNode && (afterEnd != "")) {
       
    98 				afterEndElt = startElt ;
       
    99 				lastElt = afterEndElt ;
       
   100 			}
       
   101 	    	if (btw != "") {
       
   102 	    		if (lastElt == null) {
       
   103 	    			afterStartElt = startElt ;
       
   104 	    		}
       
   105 	    		else {
       
   106 	    			afterStartElt = _yuiCloneNode(startElt) ;
       
   107 	    			lastElt.parentNode.insertBefore(afterStartElt, lastElt) ;
       
   108 	    		}
       
   109 				lastElt = afterStartElt ;
       
   110 	    	}
       
   111 	    	if (beforeStart != "") {
       
   112 	    		if (lastElt == null) {
       
   113 	    			beforeStartElt = startElt ;
       
   114 	    		}
       
   115 	    		else {
       
   116 	    			beforeStartElt = _yuiCloneNode(startElt) ;
       
   117 	    			lastElt.parentNode.insertBefore(beforeStartElt, lastElt) ;
       
   118 	    		}
       
   119 				lastElt = beforeStartElt ;
       
   120 	    	}
       
   121 	    	
       
   122 			if (afterEndElt != null) {
       
   123 				_setTextNodeContent(afterEndElt, afterEnd) ;
       
   124 			}
       
   125 			
       
   126 	    	if (afterStartElt != null) {
       
   127 				_setTextNodeContent(afterStartElt, btw) ;
       
   128 	    		_addIdClass(afterStartElt, commentId) ;
       
   129 	    	}
       
   130 
       
   131 //	    	alert('beforeStartElt.firstChild.data.length: ' + beforeStartElt.firstChild.data.length);
       
   132 //	    	alert('beforeStartElt.childNodes.length: ' + beforeStartElt.childNodes.length);
       
   133 	    	if (beforeStartElt != null) {
       
   134 				_setTextNodeContent(beforeStartElt, beforeStart) ; 
       
   135 	    	}
       
   136 //	    	alert('beforeStartElt.childNodes.length: ' + beforeStartElt.childNodes.length);
       
   137 	    	//alert('typeof beforeStartElt: ' + typeof beforeStartElt);
       
   138 //	    	alert('beforeStartElt.lastChild ==  beforeStartElt.firstChild : ' + (beforeStartElt.lastChild == beforeStartElt.firstChild));
       
   139 	    	
       
   140 //	    	alert('beforeStartElt.firstChild.id : ' + beforeStartElt.firstChild.id);
       
   141 //	    	alert('beforeStartElt.lastChild.data : ' + beforeStartElt.lastChild.data);
       
   142 //	    	alert('beforeStartElt.firstChild.data : ' + beforeStartElt.firstChild.data);
       
   143 //	    	alert('afterStartElt.firstChild.data : ' + afterStartElt.firstChild.data);
       
   144 //	    	alert('afterEndElt.firstChild.data : ' + afterEndElt.firstChild.data);
       
   145 	    	
       
   146        	}
       
   147     }
       
   148     if ( ( !sameNode ) && ( beforeEnd != "" ) ) { // otherwise nothing to do
       
   149 													// on the end node
       
   150     	if (CY.DOM.hasClass(endElt, 'c-c')) {
       
   151     		var lastElt = null, beforeEndElt = null, afterEndElt = null ;
       
   152     		
       
   153 	    	if (afterEnd != "") {
       
   154 	    		afterEndElt = endElt ;
       
   155 				lastElt = endElt ;
       
   156 			}
       
   157 			
       
   158 	    	if (beforeEnd != "") {
       
   159 	    		if (lastElt == null)
       
   160 	    			beforeEndElt = endElt ;
       
   161 	    		else {
       
   162 	    			beforeEndElt = _yuiCloneNode(endElt) ;
       
   163 	    			lastElt.parentNode.insertBefore(beforeEndElt, lastElt) ;
       
   164 	    		}
       
   165 				lastElt = beforeEndElt ;
       
   166 	    	}
       
   167 	    	if (afterEndElt != null) {
       
   168 	    		_setTextNodeContent(afterEndElt, afterEnd) ;
       
   169 			}
       
   170 			
       
   171 	    	if (beforeEndElt != null) {
       
   172 	    		_addIdClass(beforeEndElt, commentId) ;
       
   173 	    		_setTextNodeContent(beforeEndElt, beforeEnd) ;
       
   174 	    	}
       
   175     	}
       
   176     }
       
   177 } ;
       
   178 
       
   179 // WARNING (200891108): had to use YUI cloneNode instead of the native cloneNode
       
   180 // (because of the _yuid that is cloned under IE, cf tests made in textYUIcloneNode.html)
       
   181 // so code like :
       
   182 // afterStartElt = startElt.cloneNode(true) ;
       
   183 // afterStartElt.id = CY.guid() ;
       
   184 // was replaced with :
       
   185 // afterStartElt = _yuiCloneNode(startElt) ;
       
   186 _yuiCloneNode = function (srcElt) {
       
   187 	var ret = CY.Node.getDOMNode(CY.get('#'+srcElt.id).cloneNode(true)) ;
       
   188 	ret.id = CY.guid();
       
   189 	return ret ;
       
   190 } ;
       
   191 
       
   192 // will wrap textNodes into c-c spans going up the DOM tree from elt to ancestor
       
   193 // textNodes impacted here will be those that are :
       
   194 // the same generation or older than elt (but elt is excluded)
       
   195 // AND not older than grand children of to
       
   196 // assumption :
       
   197 // 1 ancestor is an ancestor of elt (we'll be going up the DOM tree)
       
   198 // 2 elt is a textNode.
       
   199 // argumentss :
       
   200 // prev : will spannify previous siblings if true, next siblings otherwise
       
   201 _dynSpanToAnc = function (elt, ancestor, commentId, prev) {
       
   202 // log('in : _dynSpanToAnc, elt : ' + elt.id) ;
       
   203 	var c = elt ;
       
   204     while ((c != null) && (c !== ancestor) && (c.parentNode !== ancestor)) {
       
   205 		var c_sib = null ;
       
   206 		if (prev) {
       
   207 			c_sib = c.previousSibling ;
       
   208 		}
       
   209 		else {
       
   210 			c_sib = c.nextSibling ;
       
   211 		}
       
   212 		
       
   213 		if (c_sib == null) {
       
   214 			c = c.parentNode ;
       
   215 		}
       
   216 		else {
       
   217 			c = c_sib ;			
       
   218 			_recAddComment(c, commentId) ;
       
   219 		}
       
   220 	} ;
       
   221 } ;
       
   222 
       
   223 // between elt1 and elt2 (which are excluded)
       
   224 _dynSpanInBetween = function (anc, elt1, elt2, commentId) {
       
   225 	var a = elt1 ;
       
   226 	var elt1Anc = null ;
       
   227     while (a) {
       
   228         if (a.parentNode === anc) {
       
   229         	elt1Anc = a ;
       
   230         	break;
       
   231         }
       
   232         a = a.parentNode;
       
   233     }
       
   234 	if (elt1Anc != null) {
       
   235 		a = elt2 ;
       
   236 		var elt2Anc = null ;
       
   237 	    while (a) {
       
   238 	        if (a.parentNode === anc) {
       
   239 	        	elt2Anc = a ;
       
   240 	        	break;
       
   241 	        }
       
   242 	        a = a.parentNode;
       
   243 	    }
       
   244 		if (elt2Anc != null) { // found both ancestor, now doing the work
       
   245 			a = elt1Anc.nextSibling ;
       
   246 			while ((a != null) && (a !== elt2Anc)) {
       
   247 				_recAddComment(a, commentId) ;
       
   248 				a = a.nextSibling ;
       
   249 			}
       
   250 		}
       
   251 	}
       
   252 } ;
       
   253 
       
   254 // (copied from YUI dom-base)
       
   255 _bruteContains = function(element, needle) {
       
   256     while (needle) {
       
   257         if (element === needle) {
       
   258             return true;
       
   259         }
       
   260         needle = needle.parentNode;
       
   261     }
       
   262     return false;
       
   263 },
       
   264 
       
   265 //elt is supposed to be c-c classed
       
   266 _addIdClass = function (elt, commentId) {
       
   267 	CY.DOM.addClass(elt, 'c-id-' + commentId) ;
       
   268 	_updateCommentCounter(elt) ;
       
   269 } ;
       
   270 
       
   271 //elt is supposed to be c-c classed
       
   272 _removeIdClass = function (elt, commentId) {
       
   273 	CY.DOM.removeClass(elt, 'c-id-' + commentId) ;
       
   274 	_updateCommentCounter(elt) ;
       
   275 } ;
       
   276 
       
   277 //elt is supposed to be c-c classed
       
   278 _removeIdClasses = function (elt) {
       
   279 	var re = _cgetRegExp('(?:^|\\s+)c-id-(?:\\d+)', 'g');
       
   280 	elt['className'] = elt['className'].replace(re, " ") ;
       
   281 	_updateCommentCounter(elt) ;
       
   282 } ;
       
   283 
       
   284 _recAddComment = function (elt, commentId) {
       
   285 	if (CY.DOM.hasClass(elt, 'c-c')) {
       
   286 		_addIdClass(elt, commentId) ;
       
   287 	}
       
   288 	else {
       
   289 		var c = elt.firstChild ;
       
   290         while (c != null) {
       
   291         	_recAddComment(c, commentId) ;
       
   292 			c = c.nextSibling ;
       
   293         }
       
   294     }
       
   295 } ;
       
   296 
       
   297 // might be expensive ... (? maybe should use contains when available, instead
       
   298 // of custom _bruteContains)
       
   299 _findCommonAncestor = function (elt1, elt2) {
       
   300 	if (_bruteContains(elt1, elt2))
       
   301 		return elt1 ;
       
   302 	else {
       
   303 		var e = elt2 ;
       
   304 		while ((e != null) && !_bruteContains(e, elt1)) {
       
   305 			e = e.parentNode ; 
       
   306 		}
       
   307 		return e ;
       
   308 	}
       
   309 } ;
       
   310 
       
   311 _cregexCache = {} ;
       
   312 // inspired (copied) from dom-base-debug in yui
       
   313 _cgetRegExp = function(str, flags) {
       
   314     flags = flags || '';
       
   315     if (!_cregexCache[str + flags]) {
       
   316         _cregexCache[str + flags] = new RegExp(str, flags);
       
   317     }
       
   318     return _cregexCache[str + flags];
       
   319 } ;
       
   320 
       
   321 //c-c should be classed with a c-count-x where x is a number for color graduation
       
   322 //c-c should be classed with many c-id-xid where xid is the comment db id of comment that apply to it
       
   323 _updateCommentCounter = function (elt) {
       
   324 	var re = _cgetRegExp('(?:^|\\s+)c-id-(?:\\d+)', 'g');
       
   325 	var matches = elt['className'].match(re);
       
   326 	var countIds = (matches == null) ? 0 : matches.length ;
       
   327 	
       
   328 	re = _cgetRegExp('(?:^|\\s+)c-count-(?:\\d+)', 'g');
       
   329 	elt['className'] = elt['className'].replace(re, " ") ;
       
   330 	CY.DOM.addClass(elt, 'c-count-'+countIds+' ') ;
       
   331 } ;
       
   332 
       
   333 _convertSelectionFromCCToCS = function (sel) {
       
   334 	var offset = sel['offset'] ;
       
   335 	var elt = sel['elt'].parentNode ;
       
   336 	
       
   337 	var e = sel['elt'].previousSibling ;
       
   338 	while (e != null) {
       
   339 		offset += _getTextNodeContent(e).length ;
       
   340 		e = e.previousSibling ; // will be a c-c !!
       
   341 	}
       
   342 	
       
   343 	return  {'elt':elt, 'offset':offset} ;
       
   344 } ;
       
   345 
       
   346 _convertSelectionFromCSToCC = function (sel) {
       
   347 	var ret = {'elt':null, 'offset':-1} ;
       
   348 	
       
   349 	var cc = null ;
       
   350 	var ccElt = sel['elt'].firstChild ;
       
   351 	var length = 0 ;
       
   352 	while (ccElt != null) {
       
   353 		var prevLength = length ;
       
   354 		length += _getTextNodeContent(ccElt).length ;
       
   355 		if (length >= sel['offset']) {
       
   356 			ret['elt'] = ccElt ;
       
   357 			ret['offset'] = sel['offset'] - prevLength ;
       
   358 			break ;
       
   359 		}
       
   360 		ccElt = ccElt.nextSibling ; // will be a c-c !!
       
   361 	}
       
   362 	return ret ;
       
   363 } ;
       
   364 
       
   365 
       
   366 /*******************************************************************************/
       
   367 /* SCOPE REMOVAL */
       
   368 /*******************************************************************************/
       
   369 
       
   370 unpaintCommentScope = function(comment) {
       
   371     var dbId = comment.id;
       
   372 
       
   373 	var classeId = 'c-id-' + dbId ;
       
   374 	var toBeRemovedElts = [] ;
       
   375 
       
   376 	var cNodeList = CY.all("."+classeId) ;
       
   377 	if (cNodeList != null) { // null in case of a reply ...
       
   378 		for (var i = 0, ilen = cNodeList.size() ; i < ilen ; i++)  {
       
   379 			var c = cNodeList.item(i) ;
       
   380 			if (c.hasClass('c-c')) { // always will !!
       
   381 				var cElt = CY.Node.getDOMNode(c) ;
       
   382 				_removeIdClass(cElt, dbId) ;
       
   383 				
       
   384 				var cIds = getCommentIdsFromClasses(cElt) ;
       
   385 				quicksort(cIds) ;
       
   386 				
       
   387 				var p = c.get('previousSibling') ;
       
   388 				if (p != null) {
       
   389 					var pElt = CY.Node.getDOMNode(p) ;
       
   390 					var pIds = getCommentIdsFromClasses(pElt) ; 
       
   391 					quicksort(pIds) ;
       
   392 					if (areSortedArraysEqual(cIds, pIds)) {
       
   393 						_setTextNodeContent(cElt, _getTextNodeContent(pElt) + _getTextNodeContent(cElt)) ;						
       
   394 						toBeRemovedElts.push(pElt) ;
       
   395 					}
       
   396 				}
       
   397 				
       
   398 				var n = c.get('nextSibling') ;
       
   399 				if (n != null) {
       
   400 					var nElt = CY.Node.getDOMNode(n) ;
       
   401 					var nIds = getCommentIdsFromClasses(nElt) ; 
       
   402 					quicksort(nIds) ;
       
   403 					if (areSortedArraysEqual(cIds, nIds)) {
       
   404 						cElt.firstChild.data = cElt.firstChild.data + nElt.firstChild.data; 
       
   405 						toBeRemovedElts.push(nElt) ;
       
   406 					}
       
   407 				}
       
   408 			}
       
   409 			else {
       
   410 				alert('HAS NO c-c ? : ' + commentNode.get('id') + " , innerHTML :" + commentNode.get('innerHTML')) ;
       
   411 				return ;
       
   412 			}
       
   413 		}
       
   414 	}
       
   415 	for (var i = 0, ilen = toBeRemovedElts.length ; i < ilen ; i++)  {
       
   416 		toBeRemovedElts[i].parentNode.removeChild(toBeRemovedElts[i]) ;
       
   417 	}
       
   418 } ;
       
   419 
       
   420 // not related to the unpaintCommentScope function (faster)
       
   421 unpaintAllComments = function() {
       
   422 	var cNodeList= CY.all(".c-s") ;
       
   423 	var toBeRemovedElts = [] ;
       
   424 	for (var i = 0, ilen = cNodeList.size() ; i < ilen ; i++)  {
       
   425 		var c = cNodeList.item(i) ;
       
   426 		
       
   427 		// remove Classes
       
   428 		var fc = c.get('firstChild') ;
       
   429 		var fcElt = CY.Node.getDOMNode(c.get('firstChild')) ;
       
   430 		_removeIdClasses(fcElt) ;
       
   431 		
       
   432 		// merge nodes
       
   433 		var n = fc.get('nextSibling') ;
       
   434 		while (n != null)  {
       
   435 			var nElt = CY.Node.getDOMNode(n) ;
       
   436 			fcElt.firstChild.data = fcElt.firstChild.data + nElt.firstChild.data; 
       
   437 			toBeRemovedElts.push(nElt) ;
       
   438 			n = n.get('nextSibling') ;
       
   439 		}
       
   440 	}
       
   441 	for (var i = 0, ilen = toBeRemovedElts.length ; i < ilen ; i++)  {
       
   442 		toBeRemovedElts[i].parentNode.removeChild(toBeRemovedElts[i]) ;
       
   443 	}
       
   444 
       
   445 } ;
       
   446 
       
   447 showScope = function(commentDbId) {
       
   448 	var s = CY.all('.c-id-' + commentDbId); 
       
   449 	if (s != null)
       
   450 		s.addClass('c-scope') ;
       
   451 } ;
       
   452 
       
   453 hideScopeAnyway = function() {
       
   454 	var s = CY.all('.c-scope'); 
       
   455 	if (s != null)
       
   456 		s.removeClass('c-scope') ;
       
   457 }
       
   458