1 /***
2 * Copyright (C) 2005 The Java Community
3 *
4 * This program is free software; you can redistribute it and/or modify it under
5 * the terms of the GNU General Public License as published by the Free Software
6 * Foundation; either version 2 of the License, or (at your option) any later
7 * version.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
12 * details.
13 *
14 * You should have received a copy of the GNU General Public License along with
15 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
16 * Place, Suite 330, Boston, MA 02111-1307 USA.
17 *
18 */
19 package org.bejug.javacareers.common.view.jsf.comment;
20
21
22 import java.io.IOException;
23 import java.text.Format;
24 import java.text.SimpleDateFormat;
25 import java.util.ArrayList;
26 import java.util.Iterator;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Set;
30
31 import javax.faces.component.UIComponent;
32 import javax.faces.component.UIInput;
33 import javax.faces.context.ExternalContext;
34 import javax.faces.context.FacesContext;
35 import javax.faces.context.ResponseWriter;
36 import javax.servlet.http.HttpSession;
37
38 import org.apache.commons.logging.Log;
39 import org.apache.commons.logging.LogFactory;
40 import org.bejug.javacareers.common.view.jsf.constants.JSFConstants;
41 import org.bejug.javacareers.common.view.jsf.utils.HtmlRenderer;
42 import org.bejug.javacareers.common.view.jsf.utils.RendererHelper;
43 import org.bejug.javacareers.jobs.model.Comment;
44 import org.bejug.javacareers.jobs.model.Offer;
45 import org.bejug.javacareers.jobs.model.Item;
46 import org.bejug.javacareers.jobs.view.jsf.action.CommentAction;
47 import org.bejug.javacareers.jobs.view.jsf.util.Utils;
48 import org.springframework.util.StringUtils;
49
50 import uk.ltd.getahead.dwr.lang.StringEscapeUtils;
51
52
53 /***
54 * HTML renderer for the comments component.
55 *
56 * @author : Peter Symoens (Last modified by $Author: shally $)
57 * @version $Revision: 1.35 $ - $Date: 2005/12/08 14:53:46 $:
58 */
59 public class CommentRenderer extends HtmlRenderer {
60
61 /***
62 * The logger.
63 */
64 private static final Log LOG = LogFactory.getLog(CommentRenderer.class);
65
66 /***
67 * The initial action
68 */
69 private String action = "init";
70
71 /***
72 *The styleClass for the comment header
73 */
74 private String styleClass;
75
76 /***
77 * the bordercolor used.
78 */
79 private String borderColor = "#A0A0A0";
80
81 /***
82 * the borderstyle.
83 */
84 private String borderStyle = "border: 1px solid " + borderColor + ";";
85
86 /***
87 * the cellstyle for the replytable.
88 */
89 private String replyTableCellStyle =
90 "border: 1px solid " + borderColor + "; background-color: #FFFFCC;";
91
92 /***
93 * the css-class for errors.
94 */
95 private String errorStyleClass;
96
97 /***
98 * the tablewidth.
99 */
100 private int tableWidth;
101
102 /***
103 * the list of comments.
104 */
105 private List commentList = new ArrayList();
106
107 /***
108 * the id of the reply on a comment.
109 */
110 private Integer replyCommentId;
111
112 /***
113 * boolean indicating of initialisation has to be done.
114 */
115 private boolean init = false;
116
117 /***
118 * boolean indicating this renders a childreply or not.
119 */
120 private boolean childReply;
121
122 /***
123 * boolean indicating whether the content is null or not.
124 */
125 private boolean contentIsNull;
126
127 /***
128 * the responsewriter.
129 */
130 private ResponseWriter writer;
131
132 /***
133 * The action to process the requests
134 */
135 private CommentAction commentAction;
136
137
138 /***
139 * Overrides the parent's encodeBegin method. Renders the component to HTML.
140 *
141 * @param context the FacesContext.
142 * @param component the UIComponent.
143 * @throws java.io.IOException thrown when something goes wrong will encoding the collapsed comments.
144 */
145 public void encodeBegin(FacesContext context, UIComponent component) throws IOException {
146 ExternalContext externalContext = context.getExternalContext();
147 HttpSession httpSession = (HttpSession) externalContext.getSession(false);
148 commentAction = (CommentAction) component.getAttributes().get("actionListener");
149
150 if (Boolean.TRUE.equals(httpSession.getAttribute(JSFConstants.COLLAPSE_COMMENTS))
151 && !"expandComments".equals(action) && !"addComments".equals(action)) {
152 action = "collapseComments";
153 }else if ("collapseComments".equals(action)) {
154 httpSession.setAttribute( JSFConstants.COLLAPSE_COMMENTS, Boolean.TRUE);
155 }
156
157 writer = context.getResponseWriter();
158 super.setComponent(component);
159 super.setWriter(writer);
160 if (!init) {
161 styleClass = (String) component.getAttributes().get("styleClass");
162 errorStyleClass = (String) component.getAttributes().get("errorStyleClass");
163 tableWidth = ((Integer) component.getAttributes().get("tableWidth")).intValue();
164 init = true;
165 }
166 if ("init".equals(action) || "expandComments".equals(action) || "addComments".equals(action)) {
167 encodeExpandedComments(context, component);
168 httpSession.setAttribute( JSFConstants.COLLAPSE_COMMENTS, Boolean.FALSE);
169 }
170
171 if (action.equals("collapseComments")) {
172 encodeCollapsedComments(context, component);
173 }
174
175 reset();
176 }
177
178 /***
179 * Overrides the parent's decode method, processes the client's requests.
180 *
181 * @param context the FacesContext.
182 * @param component the UIComponent.
183 */
184 public void decode(FacesContext context, UIComponent component) {
185 Offer offer = (Offer) ((UIInput) component).getValue();
186 Map requestMap = context.getExternalContext().getRequestParameterMap();
187 Set keys = requestMap.keySet();
188 String replyValue = null;
189 for (Iterator iterator = keys.iterator(); iterator.hasNext();) {
190 String key = (String) iterator.next();
191 String value = (String) requestMap.get(key);
192 if (value.startsWith("replyTo_")) {
193 replyValue = value.substring(8);
194 }
195 }
196 if (requestMap.containsKey("collapse") && ((String) requestMap.get("collapse")).equals("collapse")) {
197 action = "collapseComments";
198 }
199 if (requestMap.containsKey("expandComments") && ((String) requestMap.get("expandComments")).equals("expandComments")) {
200 action = "expandComments";
201 }
202 if (requestMap.containsKey("addComments") && ((String) requestMap.get("addComments")).equals("addComments")) {
203 action = "addComments";
204 }
205 if (replyValue != null) {
206 try {
207 replyCommentId = Integer.decode(replyValue);
208 } catch (NumberFormatException e) {
209 LOG.error("Could not cast " + replyValue + " to an Integer");
210 }
211 action = "init";
212 }
213
214 if (requestMap.containsKey("saveComment")) {
215 String content = (String) requestMap.get("content");
216 if (validateContent(content)) {
217 contentIsNull = true;
218 } else {
219 commentAction.processPostAction(offer, content);
220 }
221 action = "init";
222 }
223
224 if (requestMap.containsKey("cancel")) {
225 action = "init";
226 replyCommentId = null;
227 }
228 if (requestMap.containsKey("saveReply")) {
229
230 String content = (String) requestMap.get("content");
231 if (validateContent(content)) {
232 contentIsNull = true;
233 action = "init";
234 } else {
235 commentAction.processPostResponse(offer, replyCommentId, content);
236 replyCommentId = null;
237 }
238 }
239 }
240
241 /***
242 * Encode's the add comment form.
243 *
244 * @param component UIComponent
245 * @param reply boolean
246 * @throws IOException e
247 */
248 private void encodeAddCommentForm(UIComponent component, boolean reply) throws IOException {
249 openTableWidth();
250 encodeContentField();
251 encodePostCommentButton(component, reply);
252 closeTable();
253 }
254
255 /***
256 * Encode's the subject field
257 *
258 * @param component UIComponent.
259 * @param context FacesContext.
260 * @param size the size.
261 * @throws IOException e
262 */
263 private void encodeCommentHeader(FacesContext context, UIComponent component,
264 int size)
265 throws IOException {
266 openTableRow();
267 writer.write("<td colspan='2'>");
268 String addCommentLabel = Utils.getMessage("label_comments");
269
270 StringBuffer buf = new StringBuffer(100);
271 buf.append("<span class ='");
272 buf.append(styleClass);
273 buf.append("'>");
274 buf.append(addCommentLabel);
275 buf.append("</span>");
276 writer.write(buf.toString());
277
278 if((size > 0 && !commentAction.isUserAuthenticated()) || (commentAction.isUserAuthenticated()) ){
279 writer.write(" ");
280
281 String style = "font-size: 12px; color:" + borderColor + ";";
282 encodeCommandLink(context, component, "collapse",
283 Utils.getMessage("label_hide_comments"), style);
284 }
285 closeTableCell();
286 closeTableRow();
287 }
288
289 /***
290 * @param context The FacesContext.
291 * @param component The UIComponent.
292 * @param command The action to executed when the link is clicked
293 * @param link The message for the html link.
294 * @param style The CSS style.
295 * @throws IOException e
296 */
297 private void encodeCommandLink(FacesContext context, UIComponent component,
298 String command, String link, String style)
299 throws IOException {
300
301 String formId = RendererHelper.getFormId(context, component);
302
303 StringBuffer buf = new StringBuffer(200);
304 buf.append("document.forms['");
305 buf.append(formId);
306 buf.append("']['");
307 buf.append(command);
308 buf.append("'].value='");
309 buf.append(command);
310 buf.append("';document.forms['");
311 buf.append(formId);
312 buf.append("'].submit();");
313
314 writer.startElement("a", component);
315 writer.writeAttribute("style", style, null);
316 writer.writeAttribute("href", "#", "href");
317 writer.writeAttribute("onclick", buf.toString(), null);
318 writer.write(link);
319 writer.endElement("a");
320 writer.startElement("input", component);
321 writer.writeAttribute("type", "hidden", null);
322 writer.writeAttribute("name", command, null);
323 writer.endElement("input");
324 }
325
326 /***
327 * Encodes the content field.
328 *
329 * @throws IOException e
330 */
331 private void encodeContentField() throws IOException {
332
333 openTableCell();
334
335
336 writer.write("<textarea name='content' style='width: 80%;' rows='5'>");
337 writer.write("</textarea>");
338 if (contentIsNull) {
339 writer.write("<br/>");
340 encodeContentValidatorMessage(writer);
341 }
342 closeTableCell();
343 closeTableRow();
344
345 }
346
347 /***
348 * Encode's the post comment and cancel buttons.
349 *
350 * @param component the UIComponent.
351 * @param reply a boolean indicating it should be possible or not to give a reply.
352 * @throws IOException e
353 */
354 private void encodePostCommentButton(UIComponent component, boolean reply)
355 throws IOException {
356 openTableRow();
357 writer.write("<td align='center'>");
358
359 writer.startElement("input", component);
360 writer.writeAttribute("type", "submit", null);
361 if (!reply) {
362 writer.writeAttribute("name", "saveComment", null);
363 } else {
364 writer.writeAttribute("name", "saveReply", null);
365 }
366 String post = Utils.getMessage("button_post_comment");
367 writer.writeAttribute("value", post, "post");
368 writer.endElement("input");
369
370 if (reply) {
371 writer.startElement("input", component);
372 writer.writeAttribute("type", "submit", null);
373 writer.writeAttribute("name", "cancel", null);
374 String cancel = Utils.getMessage("button_cancel");
375 writer.writeAttribute("value", cancel, null);
376 writer.endElement("input");
377 }
378 closeTableCell();
379 closeTableRow();
380 }
381
382 /***
383 * Prints the number of comments related to the offer, and an add comment link.
384 *
385 * @param context the FacesContext.
386 * @param component UIComponent.
387 * @throws IOException e
388 */
389 private void encodeCollapsedComments(FacesContext context, UIComponent component) throws IOException {
390 Item offer = (Item) ((UIInput) component).getValue();
391 CommentAction commentaction = (CommentAction) component.getAttributes().get("actionListener");
392
393 openTableWidth();
394 openTableRow();
395
396 StringBuffer buf = new StringBuffer(100);
397 buf.append("<td style='");
398 buf.append(borderStyle);
399 buf.append("'>");
400 writer.write(buf.toString());
401 encodeCommentImage();
402 String link = Utils.getMessage("label_comments").concat("(");
403 String commentCount = Integer.toString(commentaction.getCommentCount(offer));
404 link = link.concat(commentCount);
405 link = link.concat(")");
406 String style = "font-family : verdana, Geneva, Arial, Helvetica, sans-serif; font-size: 12";
407 encodeCommandLink(context, component, "expandComments", link, style);
408 if(commentaction.isUserAuthenticated()){
409 writer.write(" | ");
410 encodeCommentImage();
411 encodeCommandLink(context, component, "addComments", Utils.getMessage("label_add_comment"), style);
412 }
413 closeTableCell();
414 closeTableRow();
415 closeTable();
416 }
417
418
419 /***
420 * Lists the comments related to the current offer.
421 *
422 * @param context the FacesContext.
423 * @param component UIComponent.
424 * @throws IOException e
425 */
426 private void encodeExpandedComments(FacesContext context, UIComponent component) throws IOException {
427 Offer offer = (Offer) ((UIInput) component).getValue();
428 CommentAction commentaction = (CommentAction) component.getAttributes().get("actionListener");
429 commentList = commentaction.getComments(offer);
430 boolean reply = false;
431 childReply = false;
432 openTableWidth();
433 encodeCommentHeader(context, component, commentList.size());
434 for (Iterator iterator = commentList.iterator(); iterator.hasNext();) {
435 Comment comment = (Comment) iterator.next();
436 reply = false;
437 openTableRow();
438 openTableCell();
439 openTableWidth();
440
441 openTableRow();
442 String style = borderStyle;
443
444 if (replyCommentId != null && comment.getId().intValue() == replyCommentId.intValue()) {
445 reply = true;
446 style = replyTableCellStyle;
447 }
448
449 StringBuffer buf = new StringBuffer(100);
450 buf.append("<td colspan='2' style='");
451 buf.append(style);
452 buf.append("'>");
453 writer.write(buf.toString());
454
455 newLine();
456 String filteredContent = StringEscapeUtils.escapeHtml(comment.getContent());
457 filteredContent = filteredContent.replaceAll("\n", "<br>");
458 encodeCommentContent(filteredContent);
459 encodeCommentFooter(context, component, comment, reply);
460 if (reply) {
461 encodeAddCommentForm(component, true);
462 }
463 closeTableCell();
464 closeTableRow();
465 closeTable();
466 encodeCommentTree(comment, context, component);
467 }
468 closeTable();
469 if (!reply && !childReply && commentaction.isUserAuthenticated()) {
470 encodeAddCommentForm(component, false);
471 }
472 }
473
474 /***
475 * Prints recursively all child comments of the given comment.
476 *
477 * @param comment The child comment.
478 * @param context The FacesContext.
479 * @param component The UIComponent.
480 * @throws IOException thrown when the encoding of the comments failes.
481 */
482 private void encodeCommentTree(Comment comment, FacesContext context,
483 UIComponent component) throws IOException {
484
485 CommentAction commentaction = (CommentAction) component.getAttributes().get("actionListener");
486 childReply = false;
487 List childComments = commentaction.getChildComments(comment);
488 if (!childComments.isEmpty()) {
489 Comment childComment;
490
491 for (Iterator it = childComments.iterator(); it.hasNext();) {
492 childComment = (Comment) it.next();
493 childReply = false;
494 if (replyCommentId != null &&
495 childComment.getId().intValue() == replyCommentId.intValue()) {
496 childReply = true;
497 }
498 encodeChildComment(childComment, context, component);
499 encodeCommentTree(childComment, context, component);
500 }
501 }
502 }
503
504 /***
505 * Prints the given child comment.
506 *
507 * @param childComment The child comment.
508 * @param context The FacesContext.
509 * @param component The UIComponent.
510 * @throws IOException e
511 */
512 private void encodeChildComment(Comment childComment, FacesContext context,
513 UIComponent component) throws IOException {
514
515 int level = getIndentationLevel(childComment);
516
517 int indentation = (20 * level);
518 openTableRow();
519 openTableCell();
520 openTableWidth();
521 openTableRow();
522 writer.write("<td width='" + indentation + "'>");
523 writer.write(" ");
524 closeTableCell();
525 int width = tableWidth - indentation;
526 String style = borderStyle;
527 if (childReply) {
528 style = replyTableCellStyle;
529 }
530
531 StringBuffer buf = new StringBuffer(100);
532 buf.append("<td width='");
533 buf.append(width);
534 buf.append("' style='");
535 buf.append(style);
536 buf.append("'>");
537 writer.write(buf.toString());
538
539 String content = childComment.getContent().replaceAll("\n", "<br>");
540 encodeCommentContent(content);
541 encodeCommentFooter(context, component, childComment, childReply);
542 if (childReply) {
543 encodeAddCommentForm(component, true);
544 }
545 style = borderStyle;
546 closeTableCell();
547 closeTableRow();
548 closeTable();
549 closeTableCell();
550 closeTableRow();
551 }
552
553 /***
554 * returns the identationlevel of the comment.
555 *
556 * @param comment The child comment.
557 * @return level Returns the indentation level.
558 */
559 private int getIndentationLevel(Comment comment) {
560 Comment c = comment.getParentComment();
561 int level = 0;
562 do {
563 level++;
564 c = c.getParentComment();
565 } while (c != null);
566 return level;
567
568 }
569
570 /***
571 * Prints the comment.
572 *
573 * @param content The content of the comment.
574 * @throws IOException thrown whenever the writer failes to write.
575 */
576 private void encodeCommentContent(String content) throws IOException {
577 writer.write("<div><p>");
578 writer.write(content);
579 writer.write("</p></div>");
580 newLine();
581 }
582
583 /***
584 * Prints author and creation date of the comment as a table header.
585 *
586 * @param context the FacexContext.
587 * @param comment Comment.
588 * @param component the UIComponent.
589 * @param reply a boolean indicating to include the reply or not.
590 * @throws IOException e
591 */
592 private void encodeCommentFooter(FacesContext context,
593 UIComponent component,
594 Comment comment,
595 boolean reply)
596 throws IOException {
597 Format formatter = new SimpleDateFormat("MMM d yyyy ' " + Utils.getMessage("label_at_dateformat") + " ' hh:mm a");
598 newLine();
599 StringBuffer buf = new StringBuffer(100);
600 buf.append("<div style='color:");
601 buf.append(borderColor);
602 buf.append("'>");
603 writer.write(buf.toString());
604 encodeCommentImage();
605 writer.write(Utils.getMessage("label_posted_by") + " <i>" + comment.getUser().getUserName() + "</i>");
606 writer.write(" ");
607 writer.write(Utils.getMessage("label_posted_on") + " " + formatter.format(comment.getCreationDate()));
608 if (!reply && commentAction.isUserAuthenticated()) {
609 writer.write(" | ");
610 String command = "replyTo_".concat(comment.getId().toString());
611 encodeCommandLink(context, component, command,
612 Utils.getMessage("link_reply"), "");
613 }
614 writer.write("</div>");
615 newLine();
616 }
617
618 /***
619 * Encodes the comment image gif.
620 *
621 * @throws IOException e
622 */
623 private void encodeCommentImage() throws IOException {
624 newLine();
625 StringBuffer buf = new StringBuffer(100);
626 buf.append("<img src='");
627 buf.append(Utils.getServletContextRoot());
628 buf.append("/images/comment_16.gif'>");
629 writer.write(buf.toString());
630 newLine();
631 }
632
633 /***
634 * Encode's the html open table tag.
635 *
636 * @throws IOException e
637 */
638 private void openTableWidth() throws IOException {
639 newLine();
640 StringBuffer buf = new StringBuffer(100);
641 buf.append("<table width='");
642 buf.append(tableWidth);
643 buf.append("'>");
644 writer.write(buf.toString());
645 newLine();
646 }
647
648 /***
649 * Reset class variables.
650 */
651 private void reset() {
652 childReply = false;
653 contentIsNull = false;
654
655 action = "init";
656 }
657
658
659 /***
660 * Validator for the content field of the comment.
661 *
662 * @param content a String containing the content to validate.
663 * @return a boolean indicating wether the content is valid or not.
664 */
665 private boolean validateContent(String content) {
666
667 int count = StringUtils.countOccurrencesOf(content, "\n");
668 int contentLength = content.length() - (count * 2);
669 if (contentLength < 1) {
670 return true;
671 }
672 return false;
673 }
674
675 /***
676 * Encode content validator message.
677 *
678 * @param respwriter ResponseWriter
679 * @throws IOException e
680 */
681 private void encodeContentValidatorMessage(ResponseWriter respwriter)
682
683 throws IOException {
684
685 StringBuffer buf = new StringBuffer(100);
686 buf.append("<span class='");
687 buf.append(errorStyleClass);
688 buf.append("'>");
689 respwriter.write(buf.toString());
690
691 respwriter.write(Utils.getMessage("error_invalid_content"));
692 respwriter.write("</span>");
693 }
694 }
695
696 /***
697 * $Log: CommentRenderer.java,v $
698 * Revision 1.35 2005/12/08 14:53:46 shally
699 * Opkuis voor checkstyle.
700 *
701 * Revision 1.34 2005/09/30 14:38:07 bavo_jcs
702 * Fixed URL
703 *
704 * Revision 1.33 2005/09/19 15:53:31 bavo_jcs
705 * Refactored comments to use Items
706 *
707 * Revision 1.32 2005/09/06 13:22:50 schauwvliege
708 * Intro of interview
709 *
710 * Revision 1.31 2005/08/31 08:56:12 psong09
711 * I18zed labels
712 *
713 * Revision 1.30 2005/08/30 14:55:17 psong09
714 * cleanup
715 *
716 * Revision 1.29 2005/08/30 14:40:34 psong09
717 * added javadoc
718 *
719 * Revision 1.28 2005/08/30 13:07:46 psong09
720 * renamed author: psong09
721 *
722 * Revision 1.27 2005/08/24 13:04:26 psong09
723 * moved isAuthenticated method to CommentAction
724 *
725 * Revision 1.26 2005/08/24 12:32:11 psong09
726 * Added spinner component and renamed commentAction
727 *
728 * Revision 1.25 2005/08/24 11:56:44 bavo_jcs
729 * Fixed <BR> switch
730 *
731 * Revision 1.24 2005/08/19 13:27:01 bme_jcs
732 * checkstyle errors resolved
733 *
734 * Revision 1.23 2005/08/19 13:19:52 psong09
735 * renamed VariableConstants to JSFConstants
736 *
737 * Revision 1.22 2005/08/18 10:04:32 schauwvliege
738 * hide comments in session
739 *
740 * Revision 1.21 2005/08/17 16:38:30 psong09
741 * Refactored so that post- and reply functionality is rendered only when the user is logged in.
742 *
743 * Revision 1.20 2005/08/16 08:48:47 psong09
744 * Corrected inline css syntax
745 *
746 * Revision 1.19 2005/08/15 14:27:25 stephan_janssen
747 * Added StringBuffer's and change date format.
748 *
749 * Revision 1.18 2005/08/12 13:48:57 schauwvliege
750 * intercepted html in output
751 *
752 * Revision 1.17 2005/08/10 09:04:46 bavo_jcs
753 * Optimized imports according to checkstyle
754 *
755 * Revision 1.16 2005/08/09 12:59:53 bavo_jcs
756 * Optimized imports
757 *
758 * Revision 1.15 2005/08/08 12:31:38 bme_jcs
759 * resolved PMD errors
760 *
761 * Revision 1.14 2005/08/05 08:27:55 bme_jcs
762 * resolved checkstyle errors
763 *
764 * Revision 1.13 2005/07/13 08:02:06 psong09
765 * to have childcomments correctly sorted, now load childComments from database<br> instead of getting them from the parentcomment
766 *
767 * Revision 1.12 2005/07/12 16:01:22 psong09
768 * modified comments component, removed some css styles
769 *
770 * Revision 1.11 2005/07/11 15:35:27 psong09
771 * modified encodeExpandedComments method
772 *
773 * Revision 1.10 2005/07/08 15:47:18 psong09
774 * modified comment component, removed subject field
775 *
776 * Revision 1.9 2005/07/05 15:28:53 psong09
777 * added styleClasses, modified validation
778 *
779 * Revision 1.8 2005/07/04 12:51:51 psong09
780 * added validation and placed addCommentForm in HTML table
781 *
782 * Revision 1.7 2005/07/04 07:53:54 psong09
783 * update comment component: added count(*) query, user login check, javascript
784 *
785 * Revision 1.6 2005/06/30 15:20:54 psong09
786 * added dateformat
787 *
788 * Revision 1.5 2005/06/30 12:16:36 psong09
789 * update comment component
790 *
791 * Revision 1.4 2005/06/29 09:00:15 psong09
792 * comment component updated
793 *
794 * Revision 1.3 2005/06/28 07:38:22 psong09
795 * comment component updated
796 *
797 * Revision 1.2 2005/06/24 16:28:52 psong09
798 * comment component updated
799 *
800 * Revision 1.1 2005/06/23 15:12:40 psong09
801 * comment component updated
802 *
803 */