Index: lams_tool_forum/conf/language/lams/ApplicationResources.properties =================================================================== diff -u -rafbe868c8ad64170c12f0f9e8b01d7ab19c06c5f -r72a3c5dbc0b2b0f1b3304d50cdd8798d6616aef8 --- lams_tool_forum/conf/language/lams/ApplicationResources.properties (.../ApplicationResources.properties) (revision afbe868c8ad64170c12f0f9e8b01d7ab19c06c5f) +++ lams_tool_forum/conf/language/lams/ApplicationResources.properties (.../ApplicationResources.properties) (revision 72a3c5dbc0b2b0f1b3304d50cdd8798d6616aef8) @@ -236,6 +236,6 @@ label.latest.posting.date =Latest post on label.number.of.replies =Replies message.no.reflection.available =No notebook entry was added. - - +label.show.more.messages=More Posts +label.loading.messages=Loading more posts. #======= End labels: Exported 230 labels for en AU ===== Index: lams_tool_forum/conf/language/lams/ApplicationResources_en_AU.properties =================================================================== diff -u -rafbe868c8ad64170c12f0f9e8b01d7ab19c06c5f -r72a3c5dbc0b2b0f1b3304d50cdd8798d6616aef8 --- lams_tool_forum/conf/language/lams/ApplicationResources_en_AU.properties (.../ApplicationResources_en_AU.properties) (revision afbe868c8ad64170c12f0f9e8b01d7ab19c06c5f) +++ lams_tool_forum/conf/language/lams/ApplicationResources_en_AU.properties (.../ApplicationResources_en_AU.properties) (revision 72a3c5dbc0b2b0f1b3304d50cdd8798d6616aef8) @@ -236,6 +236,7 @@ label.latest.posting.date =Latest post on label.number.of.replies =Replies message.no.reflection.available =No notebook entry was added. +label.show.more.messages=More Posts +label.loading.messages=Loading more posts. - #======= End labels: Exported 230 labels for en AU ===== Index: lams_tool_forum/conf/xdoclet/struts-actions.xml =================================================================== diff -u -rbc12c2abaca628653b04c61059b63f9d45fb4090 -r72a3c5dbc0b2b0f1b3304d50cdd8798d6616aef8 --- lams_tool_forum/conf/xdoclet/struts-actions.xml (.../struts-actions.xml) (revision bc12c2abaca628653b04c61059b63f9d45fb4090) +++ lams_tool_forum/conf/xdoclet/struts-actions.xml (.../struts-actions.xml) (revision 72a3c5dbc0b2b0f1b3304d50cdd8798d6616aef8) @@ -177,6 +177,11 @@ parameter="viewTopic" scope="request"> + + + ? and message_level = 1"; + private static final String SQL_QUERY_FIND_NEXT_THREAD_MESSAGES = "from " + MessageSeq.class.getName() + + " where root_message_uid = ? and thread_message_uid = ? and message_level > 1"; private static final String SQL_QUERY_NUM_POSTS_BY_TOPIC = "select count(*) from " + MessageSeq.class.getName() + " ms where ms.message.createdBy.userId=? and ms.message.isAuthored = false and ms.rootMessage.uid=?"; private static final String SQL_QUERY_NUM_POSTS_BY_ROOT_MESSAGE_AND_DATE = "SELECT count(*) FROM " + MessageSeq.class.getName() + " seq WHERE seq.rootMessage.uid = ? AND seq.message.updated > ?"; - public List getTopicThread(Long rootTopicId) { - return this.getHibernateTemplate().find(SQL_QUERY_FIND_TOPIC_THREAD, rootTopicId); + public MessageSeq getById(Long messageSeqId) { + return (MessageSeq) getHibernateTemplate().get(MessageSeq.class, messageSeqId); } + public List getCompleteTopic(Long rootTopicId) { + return this.getHibernateTemplate().find(SQL_QUERY_FIND_COMPLETE_TOPIC, rootTopicId); + } + + public List getThreadByThreadId(final Long rootTopicId, final Long previousThreadMessageId) { + HibernateTemplate template = this.getHibernateTemplate(); + template.setMaxResults(1); + List list = template.find(SQL_QUERY_FIND_NEXT_THREAD_TOP, new Object[] { rootTopicId, previousThreadMessageId }); + template.setMaxResults(0); + if (list != null && list.size() > 0) { + MessageSeq threadTop = ((MessageSeq) list.get(0)); + List all = template.find(SQL_QUERY_FIND_NEXT_THREAD_MESSAGES, new Object[] { rootTopicId, threadTop.getMessage().getUid() }); + all.add(threadTop); + return all; + } + return list; + } + + // used for the recureive proc call - to be deleted later + public List getThreadIds(final Long rootTopicId, final Long previousThreadMessageId) { + + return (List) getHibernateTemplate().execute(new HibernateCallback() + { + public Object doInHibernate(Session session) throws HibernateException + { + String call = "CALL TL_LAFRUM11_GET_THREAD_MESSAGE_UIDS("+rootTopicId+","+previousThreadMessageId+")"; + return session.createSQLQuery(call).list(); + } + }); + } + +// Query query = session.createSQLQuery( +// "CALL GetStocks(:stockCode)") +// .addEntity(Stock.class) +// .setParameter("stockCode", "7277"); +// +// List result = query.list(); +// for(int i=0; i 1 ) { + threadSeq = messageSeqDao.getByTopicId(threadSeq.getMessage().getParent().getUid()); + } + msgSeq.setThreadMessage(threadSeq.getMessage()); + } + messageSeqDao.save(msgSeq); // update last reply date for root message @@ -381,15 +394,49 @@ @Override public List getTopicThread(Long rootTopicId) { + return getTopicThread(rootTopicId, null, null); + } + + @Override + public List getTopicThread(Long rootTopicId, Long afterSequenceId, Long pagingSize ) { - List unsortedThread = messageSeqDao.getTopicThread(rootTopicId); - Iterator iter = unsortedThread.iterator(); - MessageSeq msgSeq; + long lastThreadMessageUid = afterSequenceId != null ? afterSequenceId.longValue() : 0L; + long usePagingSize = pagingSize != null ? pagingSize.longValue() : ForumConstants.DEFAULT_PAGE_SIZE; + SortedMap map = new TreeMap(new TopicComparator()); - while (iter.hasNext()) { - msgSeq = (MessageSeq) iter.next(); + + // first time through we need to include the top topic message (level 0) + if ( lastThreadMessageUid == 0 ) { + MessageSeq msgSeq = messageSeqDao.getByTopicId(rootTopicId); map.put(msgSeq, msgSeq.getMessage()); } + + long count = 0; + boolean foundEnough = false; + + do { + + List msgSeqs = messageSeqDao.getThreadByThreadId(rootTopicId, lastThreadMessageUid); + if ( msgSeqs.size() == 0 ) { + // no more to come from db + foundEnough = true; + } else { + + Iterator iter = msgSeqs.iterator(); + while ( iter.hasNext() ) { + MessageSeq msgSeq = ( MessageSeq) iter.next(); + if ( msgSeq.getMessageLevel() == 1 ) { + lastThreadMessageUid = msgSeq.getMessage().getUid().longValue(); + } + map.put(msgSeq, msgSeq.getMessage()); + count++; + } + if ( usePagingSize >= 0 && count >= usePagingSize ) { + foundEnough = true; + } + } + } while ( ! foundEnough); + return getSortedMessageDTO(map); } @@ -631,6 +678,7 @@ return msgDtoList; } + /** * Process an uploaded file. * @@ -1005,8 +1053,10 @@ // also clone author created topic from this forum tool content!!! // this can avoid topic record information conflict when multiple sessions are against same tool content // for example, the reply number maybe various for different sessions. - ForumService.log.debug("Clone tool content [" + forum.getContentId() + "] topics for session [" + if ( log.isDebugEnabled() ) { + log.debug("Clone tool content [" + forum.getContentId() + "] topics for session [" + session.getSessionId() + "]"); + } Set contentTopics = forum.getMessages(); if (contentTopics != null && contentTopics.size() > 0) { for (Message msg : contentTopics) { @@ -1020,7 +1070,9 @@ session.setStatus(ForumConstants.STATUS_CONTENT_COPYED); forumToolSessionDao.saveOrUpdate(session); - ForumService.log.debug("tool session [" + session.getSessionId() + "] created."); + if ( log.isDebugEnabled() ) { + ForumService.log.debug("tool session [" + session.getSessionId() + "] created."); + } } public String leaveToolSession(Long toolSessionId, Long learnerId) throws DataMissingException, ToolException { Index: lams_tool_forum/src/java/org/lamsfoundation/lams/tool/forum/service/IForumService.java =================================================================== diff -u -rbc12c2abaca628653b04c61059b63f9d45fb4090 -r72a3c5dbc0b2b0f1b3304d50cdd8798d6616aef8 --- lams_tool_forum/src/java/org/lamsfoundation/lams/tool/forum/service/IForumService.java (.../IForumService.java) (revision bc12c2abaca628653b04c61059b63f9d45fb4090) +++ lams_tool_forum/src/java/org/lamsfoundation/lams/tool/forum/service/IForumService.java (.../IForumService.java) (revision 72a3c5dbc0b2b0f1b3304d50cdd8798d6616aef8) @@ -172,12 +172,23 @@ // *********************Get topic methods ********************** // ************************************************************************************ /** - * Get topic and its children list by given root topic ID. Note that the return type is DTO. + * Get a complete topic and its children list by given root topic ID. Note that the return type is DTO. * * @param rootTopicId * @return List of MessageDTO */ List getTopicThread(Long rootTopicId); + + /** + * Get topic and its children list by given root topic ID, starting from after the sequence number specified. + * Return the number of entries indicated by the paging number. Note that the return type is DTO. + * + * @param rootTopicId + * @param afterSequenceId + * @param pagingSize + * @return List of MessageDTO + */ + public List getTopicThread(Long rootTopicId, Long afterSequenceId, Long pagingSize ); /** * Get root topics by a given sessionID value. Simultanousely, it gets back topics, which author posted in authoring Index: lams_tool_forum/src/java/org/lamsfoundation/lams/tool/forum/util/ForumConstants.java =================================================================== diff -u -r2cb29f8bc144ad08d33c6efe3e4c96f814a9a571 -r72a3c5dbc0b2b0f1b3304d50cdd8798d6616aef8 --- lams_tool_forum/src/java/org/lamsfoundation/lams/tool/forum/util/ForumConstants.java (.../ForumConstants.java) (revision 2cb29f8bc144ad08d33c6efe3e4c96f814a9a571) +++ lams_tool_forum/src/java/org/lamsfoundation/lams/tool/forum/util/ForumConstants.java (.../ForumConstants.java) (revision 72a3c5dbc0b2b0f1b3304d50cdd8798d6616aef8) @@ -201,4 +201,9 @@ public static final int PATTERN_MATCHING_OPTIONS = Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE | Pattern.MULTILINE; + + // for paging long topics + public static final String PAGE_LAST_ID = "pageLastId"; + public static final String PAGE_SIZE = "size"; + public static final int DEFAULT_PAGE_SIZE = 50; } \ No newline at end of file Index: lams_tool_forum/src/java/org/lamsfoundation/lams/tool/forum/web/actions/LearningAction.java =================================================================== diff -u -r3fbb95f74569a2eab286873c39982ac3e0a11ce8 -r72a3c5dbc0b2b0f1b3304d50cdd8798d6616aef8 --- lams_tool_forum/src/java/org/lamsfoundation/lams/tool/forum/web/actions/LearningAction.java (.../LearningAction.java) (revision 3fbb95f74569a2eab286873c39982ac3e0a11ce8) +++ lams_tool_forum/src/java/org/lamsfoundation/lams/tool/forum/web/actions/LearningAction.java (.../LearningAction.java) (revision 72a3c5dbc0b2b0f1b3304d50cdd8798d6616aef8) @@ -109,7 +109,7 @@ } // --------------Topic Level ------------------ - if (param.equals("viewTopic")) { + if (param.equals("viewTopic") || param.equals("viewTopicNext")) { return viewTopic(mapping, form, request, response); } if (param.equals("newTopic")) { @@ -491,14 +491,21 @@ ForumUser forumUser = getCurrentUser(request, (Long) sessionMap.get(AttributeNames.PARAM_TOOL_SESSION_ID)); Forum forum = forumUser.getSession().getForum(); + Long lastMsgSeqId = WebUtil.readLongParam(request, ForumConstants.PAGE_LAST_ID, true); + Long pageSize = WebUtil.readLongParam(request, ForumConstants.PAGE_SIZE, true); + // get root topic list - List msgDtoList = forumService.getTopicThread(rootTopicId); + log.debug("viewTopic: Getting topic messages "+System.currentTimeMillis()); + List msgDtoList = forumService.getTopicThread(rootTopicId, lastMsgSeqId, pageSize); + log.debug("viewTopic: updateMesssageFlag "+System.currentTimeMillis()); updateMesssageFlag(msgDtoList); + log.debug("viewTopic: doneMesssageFlag "+System.currentTimeMillis()); request.setAttribute(ForumConstants.AUTHORING_TOPIC_THREAD, msgDtoList); - // check if we can still make posts in this topic - int numOfPosts = forumService.getNumOfPostsByTopic(forumUser.getUserId(), msgDtoList.get(0).getMessage() - .getUid()); + // check if we can still make posts in this topic - TODO fix this code. It currently depends on the first message being the topic +// int numOfPosts = forumService.getNumOfPostsByTopic(forumUser.getUserId(), msgDtoList.get(0).getMessage() +// .getUid()); + int numOfPosts = 1; boolean noMorePosts = forum.getMaximumReply() != 0 && numOfPosts >= forum.getMaximumReply() && !forum.isAllowNewTopic() ? Boolean.TRUE : Boolean.FALSE; request.setAttribute(ForumConstants.ATTR_NO_MORE_POSTS, noMorePosts); @@ -521,6 +528,7 @@ return mapping.findForward("success"); } + /** * Display empty page for a new topic in forum * Index: lams_tool_forum/web/WEB-INF/tiles-defs.xml =================================================================== diff -u -r827f3387ff5d20c38597d5947d0b68e54ed2387b -r72a3c5dbc0b2b0f1b3304d50cdd8798d6616aef8 --- lams_tool_forum/web/WEB-INF/tiles-defs.xml (.../tiles-defs.xml) (revision 827f3387ff5d20c38597d5947d0b68e54ed2387b) +++ lams_tool_forum/web/WEB-INF/tiles-defs.xml (.../tiles-defs.xml) (revision 72a3c5dbc0b2b0f1b3304d50cdd8798d6616aef8) @@ -75,6 +75,7 @@ + Index: lams_tool_forum/web/includes/javascript/jquery.jscroll.js =================================================================== diff -u --- lams_tool_forum/web/includes/javascript/jquery.jscroll.js (revision 0) +++ lams_tool_forum/web/includes/javascript/jquery.jscroll.js (revision 72a3c5dbc0b2b0f1b3304d50cdd8798d6616aef8) @@ -0,0 +1,209 @@ +/*! + * jScroll - jQuery Plugin for Infinite Scrolling / Auto-Paging - v2.2.4 + * http://jscroll.com/ + * + * Copyright 2011-2013, Philip Klauzinski + * http://klauzinski.com/ + * Dual licensed under the MIT and GPL Version 2 licenses. + * http://jscroll.com/#license + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl-2.0.html + * + * @author Philip Klauzinski + * @requires jQuery v1.4.3+ + */ +(function($) { + + // Define the jscroll namespace and default settings + $.jscroll = { + defaults: { + debug: false, + autoTrigger: true, + autoTriggerUntil: false, + loadingHtml: 'Loading...', + padding: 0, + nextSelector: 'a:last', + contentSelector: '', + pagingSelector: '', + callback: false + } + }; + + // Constructor + var jScroll = function($e, options) { + + // Private vars + var _data = $e.data('jscroll'), + _userOptions = (typeof options === 'function') ? { callback: options } : options, + _options = $.extend({}, $.jscroll.defaults, _userOptions, _data || {}), + _isWindow = ($e.css('overflow-y') === 'visible'), + _$next = $e.find(_options.nextSelector).first(), + _$window = $(window), + _$body = $('body'), + _$scroll = _isWindow ? _$window : $e, + _nextHref = $.trim(_$next.attr('href') + ' ' + _options.contentSelector); + + // Initialization + $e.data('jscroll', $.extend({}, _data, {initialized: true, waiting: false, nextHref: _nextHref})); + _wrapInnerContent(); + _preloadImage(); + _setBindings(); + + // Private methods + + // Check if a loading image is defined and preload + function _preloadImage() { + var src = $(_options.loadingHtml).filter('img').attr('src'); + if (src) { + var image = new Image(); + image.src = src; + } + } + + // Wrapper inner content, if it isn't already + function _wrapInnerContent() { + if (!$e.find('.jscroll-inner').length) { + $e.contents().wrapAll('
'); + } + } + + // Find the next link's parent, or add one, and hide it + function _nextWrap($next) { + if (_options.pagingSelector) { + var $parent = $next.closest(_options.pagingSelector).hide(); + } else { + var $parent = $next.parent().not('.jscroll-inner,.jscroll-added').addClass('jscroll-next-parent').hide(); + if (!$parent.length) { + $next.wrap('
').parent().hide(); + } + } + } + + // Remove the jscroll behavior and data from an element + function _destroy() { + return _$scroll.unbind('.jscroll') + .removeData('jscroll') + .find('.jscroll-inner').children().unwrap() + .filter('.jscroll-added').children().unwrap(); + } + + // Observe the scroll event for when to trigger the next load + function _observe() { + _wrapInnerContent(); + var $inner = $e.find('div.jscroll-inner').first(), + data = $e.data('jscroll'), + borderTopWidth = parseInt($e.css('borderTopWidth')), + borderTopWidthInt = isNaN(borderTopWidth) ? 0 : borderTopWidth, + iContainerTop = parseInt($e.css('paddingTop')) + borderTopWidthInt, + iTopHeight = _isWindow ? _$scroll.scrollTop() : $e.offset().top, + innerTop = $inner.length ? $inner.offset().top : 0, + iTotalHeight = Math.ceil(iTopHeight - innerTop + _$scroll.height() + iContainerTop); + + if (!data.waiting && iTotalHeight + _options.padding >= $inner.outerHeight()) { + //data.nextHref = $.trim(data.nextHref + ' ' + _options.contentSelector); + _debug('info', 'jScroll:', $inner.outerHeight() - iTotalHeight, 'from bottom. Loading next request...'); + return _load(); + } + } + + // Check if the href for the next set of content has been set + function _checkNextHref(data) { + data = data || $e.data('jscroll'); + if (!data || !data.nextHref) { + _debug('warn', 'jScroll: nextSelector not found - destroying'); + _destroy(); + return false; + } else { + _setBindings(); + return true; + } + } + + function _setBindings() { + var $next = $e.find(_options.nextSelector).first(); + if (_options.autoTrigger && (_options.autoTriggerUntil === false || _options.autoTriggerUntil > 0)) { + _nextWrap($next); + if (_$body.height() <= _$window.height()) { + _observe(); + } + _$scroll.unbind('.jscroll').bind('scroll.jscroll', function() { + return _observe(); + }); + if (_options.autoTriggerUntil > 0) { + _options.autoTriggerUntil--; + } + } else { + _$scroll.unbind('.jscroll'); + $next.bind('click.jscroll', function() { + _nextWrap($next); + _load(); + return false; + }); + } + } + + // Load the next set of content, if available + function _load() { + var $inner = $e.find('div.jscroll-inner').first(), + data = $e.data('jscroll'); + + data.waiting = true; + $inner.append('
') + .children('.jscroll-added').last() + .html('
' + _options.loadingHtml + '
'); + + return $e.animate({scrollTop: $inner.outerHeight()}, 0, function() { + $inner.find('div.jscroll-added').last().load(data.nextHref, function(r, status, xhr) { + if (status === 'error') { + return _destroy(); + } + var $next = $(this).find(_options.nextSelector).first(); + data.waiting = false; + data.nextHref = $next.attr('href') ? $.trim($next.attr('href') + ' ' + _options.contentSelector) : false; + $('.jscroll-next-parent', $e).remove(); // Remove the previous next link now that we have a new one + _checkNextHref(); + if (_options.callback) { + _options.callback.call(this); + } + _debug('dir', data); + }); + }); + } + + // Safe console debug - http://klauzinski.com/javascript/safe-firebug-console-in-javascript + function _debug(m) { + if (_options.debug && typeof console === 'object' && (typeof m === 'object' || typeof console[m] === 'function')) { + if (typeof m === 'object') { + var args = []; + for (var sMethod in m) { + if (typeof console[sMethod] === 'function') { + args = (m[sMethod].length) ? m[sMethod] : [m[sMethod]]; + console[sMethod].apply(console, args); + } else { + console.log.apply(console, args); + } + } + } else { + console[m].apply(console, Array.prototype.slice.call(arguments, 1)); + } + } + } + + // Expose API methods via the jQuery.jscroll namespace, e.g. $('sel').jscroll.method() + $.extend($e.jscroll, { + destroy: _destroy + }); + return $e; + }; + + // Define the jscroll plugin method and loop + $.fn.jscroll = function(m) { + return this.each(function() { + var $this = $(this), + data = $this.data('jscroll'); + // Instantiate jScroll on this element if it hasn't been already + if (data && data.initialized) return; + var jscroll = new jScroll($this, m); + }); + }; +})(jQuery); \ No newline at end of file Index: lams_tool_forum/web/jsps/learning/message/topiclist.jsp =================================================================== diff -u -raca750ab28a0096d278c65e5ba48fd41f705c0e9 -r72a3c5dbc0b2b0f1b3304d50cdd8798d6616aef8 --- lams_tool_forum/web/jsps/learning/message/topiclist.jsp (.../topiclist.jsp) (revision aca750ab28a0096d278c65e5ba48fd41f705c0e9) +++ lams_tool_forum/web/jsps/learning/message/topiclist.jsp (.../topiclist.jsp) (revision 72a3c5dbc0b2b0f1b3304d50cdd8798d6616aef8) @@ -1,7 +1,8 @@ +<%@ page import="org.lamsfoundation.lams.tool.forum.util.ForumConstants"%> <%@ include file="/common/taglibs.jsp"%> <%-- If you change this file, remember to update the copy made for CNG-28 --%> - + @@ -30,7 +31,7 @@
- + Index: lams_tool_forum/web/jsps/learning/message/topicview.jsp =================================================================== diff -u -rae9a047ca47b8f553103691a7015f37ac956c668 -r72a3c5dbc0b2b0f1b3304d50cdd8798d6616aef8 --- lams_tool_forum/web/jsps/learning/message/topicview.jsp (.../topicview.jsp) (revision ae9a047ca47b8f553103691a7015f37ac956c668) +++ lams_tool_forum/web/jsps/learning/message/topicview.jsp (.../topicview.jsp) (revision 72a3c5dbc0b2b0f1b3304d50cdd8798d6616aef8) @@ -1,10 +1,18 @@ +<%@ page import="org.lamsfoundation.lams.tool.forum.util.ForumConstants"%> <%@ include file="/common/taglibs.jsp"%> <%-- If you change this file, remember to update the copy made for CNG-28 --%> + + + + + + +
em;"> @@ -168,3 +176,10 @@ + + + + \ No newline at end of file Index: lams_tool_forum/web/jsps/learning/message/topicviewwrapper.jsp =================================================================== diff -u --- lams_tool_forum/web/jsps/learning/message/topicviewwrapper.jsp (revision 0) +++ lams_tool_forum/web/jsps/learning/message/topicviewwrapper.jsp (revision 72a3c5dbc0b2b0f1b3304d50cdd8798d6616aef8) @@ -0,0 +1,17 @@ + + + + +<%@ include file="/common/taglibs.jsp"%> + + + + + + + + + +<%@ include file="topicview.jsp"%> + + Index: lams_tool_forum/web/jsps/learning/viewtopic.jsp =================================================================== diff -u -rc4854959d539a3b2d18dc30be9a11f7c127050aa -r72a3c5dbc0b2b0f1b3304d50cdd8798d6616aef8 --- lams_tool_forum/web/jsps/learning/viewtopic.jsp (.../viewtopic.jsp) (revision c4854959d539a3b2d18dc30be9a11f7c127050aa) +++ lams_tool_forum/web/jsps/learning/viewtopic.jsp (.../viewtopic.jsp) (revision 72a3c5dbc0b2b0f1b3304d50cdd8798d6616aef8) @@ -1,6 +1,9 @@ +<%@ page import="org.lamsfoundation.lams.tool.forum.util.ForumConstants"%> + <%@ include file="/common/taglibs.jsp"%> + @@ -9,6 +12,7 @@ + @@ -36,6 +40,7 @@ + @@ -82,6 +87,15 @@
+ + + + + + + @@ -166,12 +180,21 @@
+
+
<%@ include file="message/topicview.jsp"%> +
+
+ + - -
- + +
-
+ +