Files
AstroJS/botanjs/src/Astro/Blog/Components/Comment.js
T
2026-06-12 04:52:10 +08:00

626 lines
15 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 {typeof Dandelion} */
var Dand = __import( "Dandelion" );
/** @type {function(...?): 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;
var RECAPTCHA_SITE_KEY = null;
/** @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
, 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 ()
{
if( !nameField )
return;
// 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 the triangle in the top rigth corner
Dand.id( "ri_switch", true ).removeAttribute( "data-pointy" );
// 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;
hideSubmit();
Cycle.delay( function() {
closeInputPanel( function() {
cp.classList.add( "reply" );
reviveInputpanel.bind( c_reply.parentNode.parentNode )();
} );
}, 500);
}
, 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 ()
{
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 ()
{
submit_button || ( submit_button = Dand.id( "c_submit", true ) );
if(!fieldReady && contentHaveText && nameHaveText)
{
// 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();
Recaptcha.execute( RECAPTCHA_SITE_KEY, { "action": "submit" } )
.then(function( token )
{
p[ "token" ] = token;
// 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
Cycle.delay(function()
{
cp.parentNode.removeChild(cp);
// Pass panel to handler
if(handler) handler();
}, 500);
}
, 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 = "";
// Pop back up the pointy triangle
Dand.id( "ri_switch", true ).setAttribute( new DataKey( "pointy", "1" ) );
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)
{
Cycle.delay(showSubmit, 1500);
Cycle.delay( function()
{
this.innerHTML = "Recaptcha failure";
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
{
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)
, 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 );
}
, __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);
}
// 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;
}
, 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);
}
}
if( !loggedIn )
{
var limit = 5;
var i = 0;
var reUUID = Perf.uuid;
RECAPTCHA_SITE_KEY = Dand.id( "recaptcha-js" ).src.split( "?render=" )[1];
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
);
}
Cycle.next( nameInput );
Cycle.next( commentInput );
};
Bootstrap.regInit( init );
})();