penguin/AstroJS

Javascript framework for my blog

botanjs/src/Astro/Blog/Components/Comment.js

raw ยท 17944 bytes

(function(){
	var ns = __namespace( "Astro.Blog.Components.Comment" );

	/** @type {System.Cycle} */
	var Cycle                                  = __import( "System.Cycle" );
	/** @type {System.Debug} */
	var debug                                  = __import( "System.Debug" );
	/** @type {System.Cycle.Trigger} */
	var Trigger                                = __import( "System.Cycle.Trigger" );
	/** @type {System.utils.IKey} */
	var IKey                                   = __import( "System.utils.IKey" );
	/** @type {System.utils.EventKey} */
	var EventKey                               = __import( "System.utils.EventKey" );
	/** @type {System.utils.DataKey} */
	var DataKey                                = __import( "System.utils.DataKey" );
	/** @type {System.utils.Perf} */
	var Perf                                   = __import( "System.utils.Perf" );
	/** @type {Dandelion} */
	var Dand                                   = __import( "Dandelion" );
	/** @type {Dandelion.IDOMElement} */
	var IDOMElement                            = __import( "Dandelion.IDOMElement" );
	/** @type {Astro.Bootstrap} */
	var Bootstrap                              = __import( "Astro.Bootstrap" );
	/** @type {Astro.Blog.Config} */
	var Conf                                   = __import( "Astro.Blog.Config" );
	/** @type {Astro.Blog.Components.Bubble} */
	var Bubble                                 = __import( "Astro.Blog.Components.Bubble" );

	var postData                               = __import( "System.Net.postData" );

	/** @type {_3rdParty_.Recaptcha} */
	var Recaptcha;

	/** @type {_AstConf_.UserInfo} */
	var currentUser = Conf.get( "UserInfo" );
	/** @type {_AstConf_.Comment} */
	var Config = Conf.get( "Comment" );
	/** @type {_AstConf_.Article} */
	var article = Conf.get( "Article" );

	/*{{{ Validation Methods */
	var v_match = function( str, regEx )
	{
		// Optional string is empty
		if( !str ) return true;
		// Not empty, validate
		if( str.match( regEx ) != null ) return str;

		return false;
	};

	var validateMail = function ( str )
	{
		return v_match( str, /^(([^<>()\[\]\.,;:\s@\"]+(\.[^<>()\[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i );
	};

	var validateURL = function ( str )
	{
		return v_match( str, /^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/ );
	};
	/* End Validation Methods}}}*/

	var wrapc = Dand.wrapc;

	var init = function ()
	{
		// TODO: should add controls to be able to remove comments
		var contentHaveText = false
		, nameHaveText = false
		, fieldReady = false
		, releaseReplyLock
		, captcha_wrapper
		, submit_button
		, cBubble
		, button
		, cHeight = 0
		, elmH = "1.2em"

			, cp = Dand.id( "c_input_panel" )
			, contentField = Dand.id( "content_field" )
			, nameField = Dand.id( "name_field" )
			, c_body = Dand.id( "comment_body" )

		// If nameField exists, we assume logged in
		, loggedIn = !nameField


		, contentHadHaveText = false
		, commentInput = function ()
		{
			// trim whitespaces
			contentHaveText = contentField.value.trim();
			if( contentHaveText != contentHadHaveText ) {
				fieldReadyTrigger();
				contentHadHaveText = contentHaveText;
			}
		}

		, nameHadHaveText = false
		, nameInput = function ()
		{
			// trim whitespaces
			nameHaveText = nameField.value.trim();
			if( nameHadHaveText != nameHaveText ) {
				fieldReadyTrigger();
				nameHadHaveText = nameHaveText;
			}
		}


		, doReply = function ()
		{
			var c_reply = this;
			// This will ensure capcha_wrapper is set
			fieldReadyTrigger();
			// Set comment id
			submit_button.setAttribute( new DataKey( "cid", c_reply.getAttribute( "value" ) ) );

			// Close reply indicator
			Dand.id("ri_switch").style.top = "0";

			// If this button is not that button
			button && ( c_reply != button ) && releaseReplyButton();


			// Lock reply button
			c_reply.setAttribute("locked", "1");
			c_reply.onclick = null;

			c_reply.style.cursor = "default";
			c_reply.style.background = "Crimson";
			c_reply.style.width = "1.2em";
			c_reply.style.right = "-1.2em";

			// Configure release lock
			button = c_reply;

			var rf = Dand.id( "recaptcha_field" );
			if( rf && rf.hasChildNodes() )
			{
				// if capcha opened
				closeCaptcha();
				hideSubmit();
				Cycle.delay( function() {
					closeInputPanel( reviveInputpanel.bind( c_reply.parentNode.parentNode ) )
				}, 500);
			}
			else
			{
				Cycle.delay( function() {
					closeInputPanel( reviveInputpanel.bind( c_reply.parentNode.parentNode ) )
				}, 250);
			}
		}

		, releaseReplyButton = function ()
		{
			button.removeAttribute("locked");
			button.onclick = doReply.bind(button);
			button.style.cursor
				= button.style.background
				= button.style.width
				= button.style.right
				= "";
		}

		, fieldReadyTrigger = loggedIn
		? /* Overload */ function ()
			{
				// Get wrapper
				captcha_wrapper || (captcha_wrapper = Dand.id( "capcha_wrapper", true ));
				submit_button || ( submit_button = Dand.id( "c_submit", true ) );
				if(contentHaveText)
				{
					// show submit button
					showSubmit();
					fieldReady = true;
				}
				else
				{
					// hide submit button
					hideSubmit();
					fieldReady = false;
				}
			}
		: /* Overload */ function ()
			{
				captcha_wrapper || (captcha_wrapper = Dand.id( "capcha_wrapper", true ));
				submit_button || ( submit_button = Dand.id( "c_submit", true ) );
				// Create capcha
				if(!fieldReady && contentHaveText && nameHaveText)
				{
					createCaptcha();
					// show submit button
					showSubmit();
					fieldReady = true;
				}
				else if(!(contentHaveText || nameHaveText))
				{
					// hide submit button
					hideSubmit();
					fieldReady = false;
				}

			}

		, submitComment = loggedIn
		? /* Overload */ function ()
			{
				// Postdata
				var p = {
					"article_id": article.id
					, "name": currentUser.name
					, "website": currentUser.website
					, "avatar": currentUser.avatar
				}
				// pass-through obj
				, comment_id = submit_button.getDAttribute( "cid" );

				p.article_id = article.id;
				// Set comment id if available
				comment_id && ( p["comment_id"] = comment_id );
				hideSubmit();

				if( getFieldsValidated(p) )
				{
					popBubble();
					// Post the data, bind obj to Handler
					postData( Config.processor, p, commentSuccess.bind(p), commentFailed );
				}
				else
				{
					Cycle.delay( showSubmit, 1500 );
				}
			}
		: /* Overload */ function ()
			{
				var p = { "article_id": article.id }
					, comment_id = submit_button.getDAttribute( "cid" );

				// Set comment id if available
				comment_id && ( p["comment_id"] = comment_id );
				hideSubmit();
 
				if( getFieldsValidated( p ) )
				{
					popBubble();
					captcha_wrapper.readOnly = true;

					closeCaptcha();
					// Post the data, bind obj to Handler
					postData( Config.processor, p, commentSuccess.bind(p), commentFailed );

				}
				else
				{
					Cycle.delay( showSubmit, 1500 );
				}
			}

		, commentSuccess = function ( obj )
		{
			// Pass Color = Green
			cBubble.setColor("YellowGreen");

			// Popup Success message
			cBubble.pop("Success");

			// blurp after 1 sec
			Cycle.delay( function (){ cBubble.blurp(); }, 1500 );

			generateCommentStack( this, obj );
		}

		// Input panel control
		, closeInputPanel = function (handler) {
			cHeight = cp.clientHeight;

			cp.style.transition = "all 0s";

			cp.style.height = cHeight + "px";
			cp.style.overflow = "hidden";

			// Delay for 10 millisec for establishing transition properties
			Cycle.next(function() {
				this.style.transition = "";
			}.bind(cp));

			Trigger.transition(cp.style, "", function()
			{
				cp.style.padding = "0";
				cp.style.height = "0";
			});

			// After transition finished, remove input panel
			Trigger.height(cp, 0, function()
			{
				cp.parentNode.removeChild(cp);
				// Pass panel to handler
				if(handler) handler();
			});
		}

		, openInputPanel = function ()
		{
			cp.style.padding = "";
			cp.style.height = cHeight + "px";

			// Delay for 500 millisec waiting for transition finished
			Cycle.delay(function()
			{
				cp.style.transition = "all 0s";
				cp.style.height = "";
				cp.style.overflow = "";

				Dand.id("ri_switch").style.top = "";

				fieldReady = false;
				fieldReadyTrigger();
			}, 500);
		}


		, reviveInputpanel = function ()
		{
			this.appendChild(cp);
			Cycle.next(openInputPanel);
		}



		// Comments submission and handling

		, commentFailed = function (obj)
		{
			// Handle error here to prevent accidental catch by caller
			try
			{
				if(obj != null)
				{
					if(obj.reload)
					{
						// reload the recaptcha
						createCaptcha();
						Cycle.delay(showSubmit, 1500);

						// Wrong answer.
						var f = Dand.id("false_capcha");
						Cycle.delay( function()
						{
							this.innerHTML = "Wrong answer, please re-enter.";
							this.style.height = "1.2em";
						}.bind(f), 500 );

						Cycle.delay(function (){cBubble.blurp();}, 500);
					}
					else
					{
						// Reload not needed, serious error had occurred
						Cycle.delay(function (){cBubble.blurp();}, 500);
						Cycle.delay(function ()
						{
							cBubble.setColor("red");
							cBubble.pop("Server error");
						}, 1000);

					}
				}
				else
				{
					// reload the recaptcha
					createCaptcha();
					Cycle.delay(showSubmit, 1500);

					Cycle.delay(function (){cBubble.blurp();}, 500);
					Cycle.delay(function ()
					{
						cBubble.setColor("red");
						cBubble.pop("Cannot connect");
					}, 1000);
				}
			}
			catch(e)
			{
				debug.Error(e);
			}
		}

		, submitKey = new EventKey("Click", submitComment)
		// Enter key
		, captchaKey = new EventKey("KeyDown", function(e) { if(e.keyCode == 13) submitComment(); })


		, _showSubmit = function ()
		{
			// prevent multiple prop assign
			if( !submit_button.hasListener( submitKey ) )
			{
				submit_button.style.width = "1.2em";
				submit_button.style.right = "-1.2em";
				submit_button.style.cursor = "pointer";
			}
			submit_button.addEventListener( submitKey );
		}

		, _hideSubmit = function ()
		{
			// prevent multiple prop assign
			if( submit_button.hasListener( submitKey ) )
			{
				submit_button.style.cursor = "default";
				submit_button.style.width = "";
				submit_button.style.right = "";
			}
			submit_button.removeEventListener( submitKey );
		}

		, showSubmit = loggedIn
		? /* Overload */ _showSubmit
		: /* Overload */ function ()
			{
				_showSubmit();
				captcha_wrapper.addEventListener(captchaKey);
			}
		, hideSubmit = loggedIn
		? /* Overload */ _hideSubmit
		: /* Overload */ function ()
		{
			_hideSubmit();
			captcha_wrapper.removeEventListener(captchaKey);
		}

		, __createContentText = function( text )
		{
			var e = wrapc( "c_text", text );
			e.innerHTML = e.innerHTML.replace( /\n/g, "<br />" );
			return e;
		}

		, generateCommentStack = function ( rObj, obj )
		{

			var
			// Hold for transitions
			c_ind, c_cont, cpole
			// The info stack
			, _c_info = wrapc("c_info",
				[
					wrapc("fls", rObj.website ? Dand.wrapne( "a", rObj.name, IKey.quickDef( "target", "_blank", "href", rObj.website ) ) : rObj.name )
					, wrapc( "flsf", obj.time )
				])
			// loggedIn Overload
			, c_info = loggedIn
			? wrapc("cu_stack", [ _c_info
					, Dand.textNode(" ")
					, wrapc("cu_avatar"
						, Dand.wrapna( "img", IKey.quickDef( "src", rObj.avatar, "alt", "avatar" ) )
					)]
				)
			: _c_info
			// Generate comment stack
			, c_stack = rObj["comment_id"]
				// Reply Structure
				? wrapc("reply", wrapc("c_cont", [ c_ind = wrapc("r_indicator") , __createContentText( rObj.content ) , c_info ]))
				: wrapc("c_comm",
					[
						c_ind = wrapc("c_indicator")
						, c_cont = wrapc("c_cont", [ cpole = wrapc("cpole") , __createContentText( rObj.content ) , c_info ])
					]
				)
			;

			// Remove transition properties
			c_stack.style.transition = "all 0s";
			// Close Input panel
			closeInputPanel( null );

			// Remove false_fields
			for(var i in fields) {
				i = Dand.id("false_" + fields[i]);
				i.parentNode.removeChild(i);
			}
			c_stack.style.opacity = 0;
			cp.parentNode.insertBefore( c_stack, cp );

			c_stack.style.marginTop = ( -c_stack.clientHeight ) + "px";

			// if nocomment notice exist, remove it
			if( i = Dand.id("nocomment") )
			{
				i.style.transition = "all 0s";
				i.style.width = "658px";
				i.style.height = "22px";
				i.style.overflow = "hidden";
				// transition
				Cycle.next(function()
				{
					this.style.transition = "";
					this.style.margin = "0";
					this.style.width = "0";
					this.style.padding = "0";
					this.style.height = "0";
				}.bind(i));
				// Pending remove
				Trigger.height(i, 0, function(){ this.parentNode.removeChild(this); }.bind(i));
			}

			// Delay for 10 millisec for establishing transition properties
			Cycle.next( function() { this.style.transition = ""; }.bind( c_stack ) );

			Cycle.delay(function()
			{
				this.style.marginTop = "";
				this.style.opacity = 1;
			}.bind(c_stack), 600);
		}

		//// Captcha
		, createCaptcha = loggedIn
		? function() {}
		: function ()
		{
			// Remove capcha
			Dand.id( "recaptcha_field", true ).clear();
			try
			{
				Recaptcha.render( "recaptcha_field", {
					"theme": "light"
					, "sitekey": Config.siteKey
				} );
			}
			catch( ex ) { }

			openCaptcha();
		}

		, openCaptcha = loggedIn
		? function() {}
		:function ()
		{
			var rf = Dand.id( "recaptcha_field" );

			var k = function () {
				return ( 0 < this.a.clientHeight );
			}.bind({ a: rf });

			Trigger.register(
				k , function() {
					captcha_wrapper.style.height = rf.clientHeight + "px";
				}
			, 50 );
		}

		, closeCaptcha = loggedIn
		? function() {}
		:function ()
		{
			captcha_wrapper.style.height = "0";
		}



		// Validation
		, fields = loggedIn ? ["content"] : [ "content", "name", "email", "website", "email_notify" ]
		, _getFieldsValidated = function (obj)
		{
			var name, field, falsefield, f;

			for(var i in fields)
			{
				name = fields[i];
				field = name + "_field";
				falsefield = "false_" + name;

				f = Dand.id( falsefield );
				if( f ) f.style.height = 0;

				switch(name)
				{
					case "name":
						if(!( obj[name] = Dand.id(field).value.trim() ))
							Cycle.delay( function() { this.style.height = elmH; }.bind(f), 500 );
						break;
					case "content":
						if(!( obj[name] = Dand.id(field).value.trim() ))
							Cycle.delay( function() { this.style.height = elmH; }.bind(f), 500 );
						break;
					case "website":
						if(!( obj[name] = validateURL( Dand.id(field).value.trim() )))
						{
							obj[name] = false;
							Cycle.delay( function() { this.style.height = elmH; }.bind(f), 500 );
						}
						else
						{
							if(obj[name] == true)
								delete obj[ name ];
						}
						break;
					case "email":
						if(!( obj[name] = validateMail( Dand.id(field).value.trim() ) ))
						{
							obj[name] = false;
							Cycle.delay( function() { this.style.height = elmH; }.bind(f), 500 );
						}
						else
						{
							if( obj[ name ] == true )
								delete obj[ name ];
						}
						break;
					case "email_notify":
						if( obj[ name ] = Dand.id( field ).checked )
						{
							if( obj[ "email" ] == undefined )
							{
								obj[ name ] = false;
								Cycle.delay( function() { this.style.height = elmH; }.bind(f), 500 );
							}
						}
						else
						{
							delete obj[ name ];
						}
						break;
				}
			}

			for( var i in obj )
				if( !obj[i] ) return false;

			return true;
		}

		, getFieldsValidated = loggedIn
		? /* Overload */ _getFieldsValidated
		: /* Overload */ function(obj)
		{
			var f = Dand.id("false_capcha");
			f.style.height = 0;
			// get the response field
			if(!( obj["recaptcha_response_field"] = Recaptcha.getResponse().trim() ))
			{
				Cycle.delay(
					function()
					{
						this.innerHTML = "You need to prove that you are a human";
						this.style.height = elmH;
					}.bind(f)
				, 500 );
			}
			// get the challenge field
			// else obj["recaptcha_challenge_field"] = Recaptcha.get_challenge();

			return _getFieldsValidated(obj);
		}

		, showReplyButton = function ()
		{
			this.style.width = "1.2em";
			this.style.right = "-1.2em";
		}

		, hideReplyButton = function ()
		{
			if(!this.getAttribute("locked"))
				this.style.width = this.style.right = "";
		}

		, popBubble = function ()
		{
			if(cBubble == null)
			{
				// create a new bubble
				cBubble = new Bubble();
				document.body.appendChild( cBubble.init() );
				// pop message
				cBubble.setColor( "cornflowerblue" );
				Cycle.next( function () { cBubble.pop("Submitting"); } );
			}
		}
		;

		if( !loggedIn )
		{
			IDOMElement( nameField ).addEventListener( "Input", nameInput );
		}

		IDOMElement( contentField ).addEventListener( "Input", commentInput );

		var l = c_body.childNodes, c_id;

		for( var i in l )
		{
			if( l[i].nodeType == 1
				&& ( c_id = IDOMElement( l[i] ).getDAttribute("value") )
			)
			{
				var reply_button = Dand.id("c_reply_" + c_id);
				reply_button.onclick = doReply.bind(reply_button);

				l[i].onmouseover = showReplyButton.bind(reply_button);
				l[i].onmouseout = hideReplyButton.bind(reply_button);
			}
		}

		var stage = c_body.parentNode
		BotanJS.addEventListener( "Responsive"
			/** e @type {Astro.Starfall.Events.Responsive} */
			, function( e ) {
				e.data.ratio < 1
					? stage.style.width = "100%"
					: stage.style.width = ""
				;
			}
		);

		if( !loggedIn )
		{
			var limit = 5;
			var i = 0;
			var reUUID = Perf.uuid;
			Cycle.perma(
				reUUID, function()
				{
					if( limit < i ++ )
					{
						var mesg = Dand.glass( "c_so_sorry" );
						if( mesg.length ) mesg[0].style.display = "block";
					}

					if( Recaptcha = window["grecaptcha"] )
					{
						Cycle.permaRemove( reUUID );
						debug.Info( "Recaptcha instance is up" );
					}
				} , 500
			);
		}
	};

	Bootstrap.regInit( init );
})();