forked from Botanical/BotanJS
722 lines
17 KiB
JavaScript
722 lines
17 KiB
JavaScript
(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, /^[\_]*([a-z0-9]+(\.|\_*)?)+@([a-z][a-z0-9\-]+(\.|\-*\.))+[a-z]{2,6}$/ );
|
|
};
|
|
|
|
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);
|
|
}
|
|
|
|
|
|
, 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") , wrapc("c_text", rObj.content) , c_info ]))
|
|
: wrapc("c_comm",
|
|
[
|
|
c_ind = wrapc("c_indicator")
|
|
, c_cont = wrapc("c_cont", [ cpole = wrapc("cpole") , wrapc("c_text", 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"]
|
|
, _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 );
|
|
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;
|
|
}
|
|
}
|
|
|
|
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.Blog.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 );
|
|
})();
|