honmaple 8 лет назад
Родитель
Сommit
2f3659dc6d

+ 4 - 3
forums/__init__.py

@@ -6,16 +6,17 @@
 # Author: jianglin
 # Email: xiyang0807@gmail.com
 # Created: 2017-01-25 20:10:50 (CST)
-# Last Update:星期六 2017-3-25 18:24:12 (CST)
+# Last Update:星期一 2017-3-27 19:41:1 (CST)
 #          By:
 # Description:
 # **************************************************************************
+import os
 from flask import Flask
 from flask_maple.lazy import LazyExtension
 from forums.admin.urls import admin
-from .logs import register_logging
+
 from .filters import register_jinja2
-import os
+from .logs import register_logging
 
 
 def create_app(config):

+ 93 - 2
forums/api/follow/views.py

@@ -6,13 +6,16 @@
 # Author: jianglin
 # Email: xiyang0807@gmail.com
 # Created: 2016-12-22 21:49:05 (CST)
-# Last Update:星期六 2017-3-25 20:27:38 (CST)
+# Last Update:星期一 2017-3-27 22:0:17 (CST)
 #          By:
 # Description:
 # **************************************************************************
-from flask import (request, render_template)
+from flask import render_template, request
+
 from forums.api.tag.models import Tags
 from forums.api.topic.models import Collect, Topic
+from forums.api.user.models import User
+from forums.common.response import HTTPResponse
 from forums.common.views import IsAuthMethodView as MethodView
 
 
@@ -25,6 +28,28 @@ class FollowingTagsView(MethodView):
         data = {'tags': tags}
         return render_template('follow/following_tags.html', **data)
 
+    def post(self):
+        user = request.user
+        post_data = request.data
+        tag_id = post_data.pop('tagId', None)
+        if tag_id is not None and not User.query.filter_by(
+                following_tags__id=tag_id).exists():
+            tag = Tags.query.filter_by(id=tag_id).first_or_404()
+            user.following_tags.append(tag)
+            user.save()
+        return HTTPResponse(HTTPResponse.NORMAL_STATUS).to_response()
+
+    def delete(self):
+        user = request.user
+        post_data = request.data
+        tag_id = post_data.pop('tagId', None)
+        if tag_id is not None and User.query.filter_by(
+                following_tags__id=tag_id).exists():
+            tag = Tags.query.filter_by(id=tag_id).first_or_404()
+            user.following_tags.remove(tag)
+            user.save()
+        return HTTPResponse(HTTPResponse.NORMAL_STATUS).to_response()
+
 
 class FollowingTopicsView(MethodView):
     def get(self):
@@ -36,6 +61,28 @@ class FollowingTopicsView(MethodView):
         data = {'topics': topics}
         return render_template('follow/following_topics.html', **data)
 
+    def post(self):
+        user = request.user
+        post_data = request.data
+        topic_id = post_data.pop('topicId', None)
+        if topic_id is not None and not User.query.filter_by(
+                following_topics__id=topic_id).exists():
+            topic = Topic.query.filter_by(id=topic_id).first_or_404()
+            user.following_topics.append(topic)
+            user.save()
+        return HTTPResponse(HTTPResponse.NORMAL_STATUS).to_response()
+
+    def delete(self):
+        user = request.user
+        post_data = request.data
+        topic_id = post_data.pop('topicId', None)
+        if topic_id is not None and User.query.filter_by(
+                following_topics__id=topic_id).exists():
+            topic = Topic.query.filter_by(id=topic_id).first_or_404()
+            user.following_topics.remove(topic)
+            user.save()
+        return HTTPResponse(HTTPResponse.NORMAL_STATUS).to_response()
+
 
 class FollowingUsersView(MethodView):
     def get(self):
@@ -45,6 +92,28 @@ class FollowingUsersView(MethodView):
         data = {'users': users}
         return render_template('follow/following_users.html', **data)
 
+    def post(self):
+        user = request.user
+        post_data = request.data
+        user_id = post_data.pop('userId', None)
+        if user_id is not None and not User.query.filter_by(
+                following_users__id=user_id).exists():
+            f_user = User.query.filter_by(id=user_id).first_or_404()
+            user.following_users.append(f_user)
+            user.save()
+        return HTTPResponse(HTTPResponse.NORMAL_STATUS).to_response()
+
+    def delete(self):
+        user = request.user
+        post_data = request.data
+        user_id = post_data.pop('userId', None)
+        if user_id is not None and User.query.filter_by(
+                following_users__id=user_id).exists():
+            f_user = User.query.filter_by(id=user_id).first_or_404()
+            user.following_users.remove(f_user)
+            user.save()
+        return HTTPResponse(HTTPResponse.NORMAL_STATUS).to_response()
+
 
 class FollowingCollectsView(MethodView):
     def get(self):
@@ -56,3 +125,25 @@ class FollowingCollectsView(MethodView):
 
         data = {'collects': collects}
         return render_template('follow/following_collects.html', **data)
+
+    def post(self):
+        user = request.user
+        post_data = request.data
+        collect_id = post_data.pop('collectId', None)
+        if collect_id is not None and not User.query.filter_by(
+                following_collects__id=collect_id).exists():
+            collect = Collect.query.filter_by(id=collect_id).first_or_404()
+            user.following_collects.append(collect)
+            user.save()
+        return HTTPResponse(HTTPResponse.NORMAL_STATUS).to_response()
+
+    def delete(self):
+        user = request.user
+        post_data = request.data
+        collect_id = post_data.pop('collectId', None)
+        if collect_id is not None and User.query.filter_by(
+                following_collects__id=collect_id).exists():
+            collect = Collect.query.filter_by(id=collect_id).first_or_404()
+            user.following_collects.remove(collect)
+            user.save()
+        return HTTPResponse(HTTPResponse.NORMAL_STATUS).to_response()

+ 3 - 2
forums/api/forums/views.py

@@ -6,7 +6,7 @@
 # Author: jianglin
 # Email: xiyang0807@gmail.com
 # Created: 2016-12-17 20:45:08 (CST)
-# Last Update:星期六 2017-3-25 19:2:23 (CST)
+# Last Update:星期一 2017-3-27 19:47:49 (CST)
 #          By:
 # Description:
 # **************************************************************************
@@ -53,9 +53,10 @@ class BoardListView(MethodView):
     def get(self):
         query_dict = request.data
         page, number = self.page_info
-        keys = ['name', 'description']
+        keys = ['name']
         order_by = gen_order_by(query_dict, keys)
         filter_dict = gen_filter_dict(query_dict, keys)
+        filter_dict.update(parent_id=None)
         boards = Board.query.filter_by(
             **filter_dict).order_by(*order_by).paginate(page, number, True)
         data = {'title': 'Board', 'boards': boards}

+ 5 - 2
forums/api/tag/views.py

@@ -6,7 +6,7 @@
 # Author: jianglin
 # Email: xiyang0807@gmail.com
 # Created: 2016-12-15 22:07:04 (CST)
-# Last Update:星期六 2017-3-25 21:27:38 (CST)
+# Last Update:星期一 2017-3-27 21:40:52 (CST)
 #          By:
 # Description:
 # **************************************************************************
@@ -39,8 +39,11 @@ class TagsListView(MethodView):
 
 class TagsView(MethodView):
     def get(self, name):
+        page, number = self.page_info
         tag = Tags.query.filter_by(name=name).first_or_404()
-        data = {'title': tag.name, 'tag': tag}
+        topics = Topic.query.filter_by(tags__id=tag.id).paginate(page, number,
+                                                                 True)
+        data = {'title': tag.name, 'tag': tag, 'topics': topics}
         return render_template('tag/tag.html', **data)
 
 

+ 4 - 2
forums/api/topic/urls.py

@@ -6,7 +6,7 @@
 # Author: jianglin
 # Email: xiyang0807@gmail.com
 # Created: 2016-12-15 22:15:34 (CST)
-# Last Update:星期六 2017-3-25 19:36:21 (CST)
+# Last Update:星期一 2017-3-27 22:20:52 (CST)
 #          By:
 # Description:
 # **************************************************************************
@@ -14,7 +14,7 @@ from flask import Blueprint
 
 from .views import (CollectListView, CollectView, LikeView, ReplyListView,
                     ReplyView, TopicAskView, TopicEditView, TopicListView,
-                    TopicPreviewView, TopicView)
+                    TopicPreviewView, TopicView, AddToCollectView)
 
 site = Blueprint('topic', __name__)
 
@@ -28,6 +28,7 @@ preview_view = TopicPreviewView.as_view('preview')
 
 collect_list = CollectListView.as_view('collectlist')
 collect = CollectView.as_view('collect')
+add_to_collect = AddToCollectView.as_view('add_to_collect')
 
 reply_list = ReplyListView.as_view('reply_list')
 reply = ReplyView.as_view('reply')
@@ -46,3 +47,4 @@ site.add_url_rule('/replies/<int:replyId>/like', view_func=like_view)
 
 site.add_url_rule('/collect', view_func=collect_list)
 site.add_url_rule('/collect/<int:collectId>', view_func=collect)
+site.add_url_rule('/topic/<int:topicId>/collect', view_func=add_to_collect)

+ 20 - 5
forums/api/topic/views.py

@@ -6,25 +6,25 @@
 # Author: jianglin
 # Email: xiyang0807@gmail.com
 # Created: 2016-12-15 22:07:39 (CST)
-# Last Update:星期六 2017-3-25 22:47:59 (CST)
+# Last Update:星期一 2017-3-27 22:21:2 (CST)
 #          By:
 # Description:
 # **************************************************************************
 from flask import redirect, render_template, request, url_for
 from flask_babelex import gettext as _
 from flask_login import current_user
+
 from flask_maple.auth.forms import form_validate
 from flask_maple.response import HTTPResponse
-
 from forums.api.forums.models import Board
 from forums.api.tag.models import Tags
 from forums.common.serializer import Serializer
+from forums.common.utils import gen_filter_dict, gen_order_by
 from forums.common.views import BaseMethodView as MethodView
-from forums.common.utils import (gen_filter_dict, gen_order_by)
 
 from .forms import (CollectForm, ReplyForm, TopicForm, collect_error_callback,
                     error_callback, form_board)
-from .models import Collect, Topic, Reply
+from .models import Collect, Reply, Topic
 
 
 class TopicAskView(MethodView):
@@ -53,7 +53,7 @@ class TopicPreviewView(MethodView):
     def post(self):
         choice = request.values.get('choice')
         content = request.values.get('content')
-        return
+        return ''
 
 
 class TopicListView(MethodView):
@@ -189,6 +189,21 @@ class CollectView(MethodView):
                             **serializer.data).to_response()
 
 
+class AddToCollectView(MethodView):
+    def post(self, topicId):
+        user = request.user
+        form = request.form.getlist('add-to-collect')
+        topic = Topic.query.filter_by(id=topicId).first_or_404()
+        for cid in form:
+            '''This has a problem'''
+            collect = Collect.query.filter_by(id=cid).first_or_404()
+            if not Collect.query.filter_by(
+                    topics__id=topic.id, author_id=user.id).exists():
+                collect.topics.append(topic)
+                collect.save()
+        return redirect(url_for('topic.topic', topicId=topic.id))
+
+
 class ReplyListView(MethodView):
     @form_validate(ReplyForm, error=error_callback, f='')
     def post(self, topicId):

+ 2 - 2
forums/extension.py

@@ -6,7 +6,7 @@
 # Author: jianglin
 # Email: xiyang0807@gmail.com
 # Created: 2016-10-25 21:57:10 (CST)
-# Last Update:星期六 2017-3-25 18:15:28 (CST)
+# Last Update:星期一 2017-3-27 20:33:3 (CST)
 #          By:
 # Description:
 # **************************************************************************
@@ -87,7 +87,7 @@ csrf = CsrfProtect()
 bootstrap = Bootstrap(
     css=('styles/monokai.css', 'styles/mine.css',
          'tags/css/bootstrap-tokenfield.css', 'select2/css/select2.min.css'),
-    js=('styles/upload.js', 'styles/forums.js', 'styles/mine.js',
+    js=('styles/upload.js', 'styles/forums.js', 'styles/following.js',
         'styles/topic.js', 'tags/bootstrap-tokenfield.min.js',
         'select2/js/select2.min.js'),
     use_auth=True)

+ 14 - 1
forums/filters.py

@@ -6,7 +6,7 @@
 # Author: jianglin
 # Email: xiyang0807@gmail.com
 # Created: 2016-11-07 21:00:32 (CST)
-# Last Update:星期六 2017-3-25 22:3:3 (CST)
+# Last Update:星期一 2017-3-27 21:45:50 (CST)
 #          By:
 # Description:
 # **************************************************************************
@@ -130,6 +130,16 @@ def topic_is_followed(topicId):
         followers__id=current_user.id, id=topicId).exists()
 
 
+def tag_is_followed(pk):
+    from forums.api.tag.models import Tags
+    return Tags.query.filter_by(followers__id=current_user.id, id=pk).exists()
+
+
+def user_is_followed(pk):
+    from forums.api.user.models import User
+    return User.query.filter_by(followers__id=current_user.id, id=pk).exists()
+
+
 def is_collected(topicId):
     from forums.api.topic.models import Collect
     return Collect.query.filter_by(
@@ -167,3 +177,6 @@ def register_jinja2(app):
     app.jinja_env.filters['is_collected'] = is_collected
     app.jinja_env.filters['is_online'] = is_online
     app.jinja_env.filters['is_liked'] = is_liked
+    app.jinja_env.filters['topic_is_followed'] = topic_is_followed
+    app.jinja_env.filters['tag_is_followed'] = tag_is_followed
+    app.jinja_env.filters['user_is_followed'] = tag_is_followed

BIN
forums/test.db


+ 9 - 8
static/assets/home.js

@@ -10,19 +10,20 @@ else
 {$("#showerror").show();$("#changeCode").attr("src",url.captcha+"?code="+Math.random());$("#captcha").val("");if(response.description!==""){$("#error").text(response.description);}
 else{$("#error").text(response.message);}}}
 $('button#login').click(function(){$.ajax({type:"POST",url:url.login,data:JSON.stringify({username:$('#username').val(),password:$('#password').val(),captcha:$("#captcha").val(),remember:$("#remember").is(':checked')}),contentType:'application/json;charset=UTF-8',success:function(response){return AuthCallBack(response);}});});$('button#register').click(function(){$.ajax({type:"POST",url:url.register,data:JSON.stringify({username:$('#username').val(),email:$('#email').val(),password:$('#password').val(),captcha:$("#captcha").val()}),contentType:'application/json;charset=UTF-8',success:function(response){return AuthCallBack(response);}});});$('button#forget').click(function(){$.ajax({type:"POST",url:url.forget,data:JSON.stringify({email:$('#email').val(),captcha:$("#captcha").val()}),contentType:'application/json;charset=UTF-8',success:function(response){return AuthCallBack(response);}});});});function loadFile(event){var _file=document.getElementById("avatar");var i=_file.value.lastIndexOf('.');var len=_file.value.length;var extEndName=_file.value.substring(i+1,len);var extName="JPG,PNG";if(extName.indexOf(extEndName.toUpperCase())==-1){alert("您只能上传"+extName+"格式的文件");$('#avatar').val('');}else{var reader=new FileReader();reader.onload=function(){var icon='<i class="icon-exchange"></i>'+'\n';var img='<img src="'+reader.result+'" title="avatar" class="avatar img-circle">';$("#show-avatar").html(icon+img);};reader.readAsDataURL(event.target.files[0]);}}
-function SortFuntion(){var data=JSON.stringify({display:$('#display').val(),sort:$('#sort').val(),st:$('#st').val(),type:sortData.type,uid:sortData.uid,page:sortData.page});$.ajax({type:"POST",url:"/order",data:data,contentType:'application/json;charset=UTF-8',success:function(result){$('div.topiclist').html(result);}});}
+function SortFuntion(){var display=$('#display').val();var sort=$('#sort').val();var st=$('#st').val();var params={};if(display!='0'){params.within=display;}
+if(sort!='0'){params.orderby=sort;}
+if(st!='0'){params.desc=st;}
+window.location.href=window.location.pathname+'?'+$.param(params);}
 $(document).ready(function(){$('#display').change(function(){SortFuntion();});$('#sort').change(function(){SortFuntion();});$('#st').change(function(){SortFuntion();});$('span#email-confirm').click(function(){$.ajax({type:"POST",url:"/confirm-email",data:JSON.stringify({}),contentType:'application/json;charset=UTF-8',success:function(result){if(result.judge===true)
 {alert(result.error);}else
 {alert(result.error);}}});});});function dispatch(){var q=document.getElementById("search");if(q.value!==""){var url='https://www.google.com/search?q=site:forums.honmaple.org%20'+q.value;if(navigator.userAgent.indexOf('iPad')>-1||navigator.userAgent.indexOf('iPod')>-1||navigator.userAgent.indexOf('iPhone')>-1){location.href=url;}else{window.open(url,"_blank");}
 return false;}else{return false;}}
-function Follow(obj,data,url){if(obj.hasClass('active'))
-{$.ajax({type:"DELETE",url:url,data:data,contentType:'application/json;charset=UTF-8',success:function(result){if(result.judge===true)
+function Follow(obj,data,url){if(obj.hasClass('active')){$.ajax({type:"DELETE",url:url,data:data,contentType:'application/json;charset=UTF-8',success:function(response){if(response.status==='200')
 {obj.text('关注').removeClass('active');}else
-{alert('fail');}}});}else
-{$.ajax({type:"POST",url:url,data:data,contentType:'application/json;charset=UTF-8',success:function(result){if(result.judge===true)
+{alert('fail');}}});}else{$.ajax({type:"POST",url:url,data:data,contentType:'application/json;charset=UTF-8',success:function(response){if(response.status==='200')
 {obj.text('取消关注').addClass('active');}else
 {alert('fail');}}});}}
-$(document).ready(function(){$('button.tagfollow').click(function(){var _$this=$(this);var url="/user/follow/tag";var data=JSON.stringify({id:_$this.attr("id"),});Follow(_$this,data,url);});$('button.topicfollow').click(function(){var _$this=$(this);var url="/user/follow/topic";var data=JSON.stringify({id:_$this.attr("id"),});Follow(_$this,data,url);});$('button.collectfollow').click(function(){var _$this=$(this);var url="/user/follow/collect";var data=JSON.stringify({id:_$this.attr("id"),});Follow(_$this,data,url);});$('button.userfollow').click(function(){var _$this=$(this);var url="/user/follow/user";var data=JSON.stringify({id:_$this.attr("id"),});Follow(_$this,data,url);});});function DoCollect(collectData){$(document).ready(function(){$('button#edit-collect-form').click(function(){var data=JSON.stringify({name:$('#name').val(),description:$('#description').val(),is_privacy:$("input[name='is_privacy']:checked").val()});$.ajax({type:"PUT",url:collectData.collect_action_url,data:data,contentType:'application/json;charset=UTF-8',success:function(result){if(result.judge===true)
+$(document).ready(function(){$('button.topic-following').click(function(){var _$this=$(this);var url="/user/following/topics";var data=JSON.stringify({topicId:_$this.attr("data-id"),});Follow(_$this,data,url);});$('button.tag-following').click(function(){var _$this=$(this);var url="/user/following/tags";var data=JSON.stringify({tagId:_$this.attr("data-id"),});Follow(_$this,data,url);});$('button.user-following').click(function(){var _$this=$(this);var url="/user/following/users";var data=JSON.stringify({userId:_$this.attr("data-id"),});Follow(_$this,data,url);});$('button.collect-following').click(function(){var _$this=$(this);var url="/user/following/collects";var data=JSON.stringify({collectId:_$this.attr("data-id"),});Follow(_$this,data,url);});});function DoCollect(collectData){$(document).ready(function(){$('button#edit-collect-form').click(function(){var data=JSON.stringify({name:$('#name').val(),description:$('#description').val(),is_privacy:$("input[name='is_privacy']:checked").val()});$.ajax({type:"PUT",url:collectData.collect_action_url,data:data,contentType:'application/json;charset=UTF-8',success:function(result){if(result.judge===true)
 {window.location=collectData.collect_action_url;}}});});$('button#delete-collect-form').click(function(){$.ajax({type:"DELETE",url:collectData.collect_action_url,data:JSON.stringify(),contentType:'application/json;charset=UTF-8',success:function(result){if(result.judge===true)
 {window.location=collectData.collect_url;}}});});$('#delete-from-collect').click(function(){var _$this=$(this);var topicId=_$this.attr('data-id');var data=JSON.stringify({topicId:topicId});$.ajax({type:"DELETE",url:collectData.delete_detail_action_url,data:data,contentType:'application/json;charset=UTF-8',success:function(result){if(result.judge===true)
 {_$this.parent().remove();}}});});});}
@@ -30,9 +31,9 @@ $(document).ready(function(){$('.like-reply').click(function(){var _$this=$(this
 {_$this.attr("title","赞");_$this.removeClass("like-active");_$this.addClass("like-no-active");}else
 {window.location.href=response.data.url;}}});}else{$.ajax({type:"POST",url:like_url,data:data,contentType:'application/json;charset=UTF-8',success:function(response){if(response.status==='200')
 {_$this.attr("title","取消赞");_$this.removeClass("like-no-active");_$this.addClass("like-active");}else
-{window.location.href=response.url;}}});}});$('.reply-author').click(function(){var _$this=$(this);var author=_$this.attr('data-id');$('#content').focus();$('#content').val('@'+author+' ');});});function DoVote(voteData){$(document).ready(function(){$('#topic-up-vote').click(function(){var data=JSON.stringify({});$.ajax({type:"POST",url:voteData.vote_url,data:data,contentType:'application/json;charset=UTF-8',success:function(result){if(result.judge)
+{window.location.href=response.url;}}});}});$('.reply-author').click(function(){var _$this=$(this);var author=_$this.attr('data-id');$('#content').focus();$('#content').val('@'+author+' ');});});function DoVote(voteData){$(document).ready(function(){$('#topic-up-vote').click(function(){var data=JSON.stringify({});$.ajax({type:"POST",url:voteData.vote_url,data:data,contentType:'application/json;charset=UTF-8',success:function(response){if(response.status==='200')
 {$('.votes').html(result.html);}else
-{window.location.href=result.url;}}});});$('#topic-down-vote').click(function(){var data=JSON.stringify({});$.ajax({type:"DELETE",url:voteData.vote_url,data:data,contentType:'application/json;charset=UTF-8',success:function(result){if(result.judge)
+{window.location.href=result.url;}}});});$('#topic-down-vote').click(function(){var data=JSON.stringify({});$.ajax({type:"DELETE",url:voteData.vote_url,data:data,contentType:'application/json;charset=UTF-8',success:function(response){if(response.status==='200')
 {$('.votes').html(result.html);}else
 {window.location.href=result.url;}}});});});}
 function PreviewTopic(pre_url){$('#topic-preview').click(function(){var content=$('#content').val();$.post(pre_url,{content:$("#content").val(),choice:$("#choice").val()},function(data){$("#show-preview").html(data);});});}

+ 118 - 0
static/styles/following.js

@@ -0,0 +1,118 @@
+function Follow(obj,data,url){
+  if(obj.hasClass('active')) {
+    $.ajax ({
+      type : "DELETE",
+      url : url,
+      data:data,
+      contentType: 'application/json;charset=UTF-8',
+      success: function(response) {
+        if (response.status === '200')
+        {
+          obj.text('关注').removeClass('active');
+        } else
+        {alert('fail');}}});
+  }else {
+    $.ajax ({
+      type : "POST",
+      url : url,
+      data:data,
+      contentType: 'application/json;charset=UTF-8',
+      success: function(response) {
+        if (response.status === '200')
+        {
+          obj.text('取消关注').addClass('active');
+        } else
+        {alert('fail');}
+      }});
+  }
+}
+$(document).ready(function(){
+  $('button.topic-following').click(function(){
+    var _$this = $(this);
+    var url = "/user/following/topics";
+    var data = JSON.stringify({
+      topicId:_$this.attr("data-id"),
+    });
+    Follow(_$this,data,url);
+  });
+  $('button.tag-following').click(function(){
+    var _$this = $(this);
+    var url = "/user/following/tags";
+    var data = JSON.stringify({
+      tagId:_$this.attr("data-id"),
+    });
+    Follow(_$this,data,url);
+  });
+  $('button.user-following').click(function(){
+    var _$this = $(this);
+    var url = "/user/following/users";
+    var data = JSON.stringify({
+      userId:_$this.attr("data-id"),
+    });
+    Follow(_$this,data,url);
+  });
+  $('button.collect-following').click(function(){
+    var _$this = $(this);
+    var url = "/user/following/collects";
+    var data = JSON.stringify({
+      collectId:_$this.attr("data-id"),
+    });
+    Follow(_$this,data,url);
+  });
+});
+function DoCollect(collectData) {
+  $(document).ready(function(){
+    $('button#edit-collect-form').click(function() {
+      var data = JSON.stringify({
+        name:$('#name').val(),
+        description:$('#description').val(),
+        is_privacy:$("input[name='is_privacy']:checked").val()
+      });
+      $.ajax ({
+        type : "PUT",
+        url : collectData.collect_action_url,
+        data:data,
+        contentType: 'application/json;charset=UTF-8',
+        success: function(result) {
+          if (result.judge === true)
+          {
+            window.location =collectData.collect_action_url ;
+          }
+        }
+      });
+    });
+    $('button#delete-collect-form').click(function() {
+      $.ajax ({
+        type : "DELETE",
+        url : collectData.collect_action_url,
+        data:JSON.stringify(),
+        contentType: 'application/json;charset=UTF-8',
+        success: function(result) {
+          if (result.judge === true)
+          {
+            window.location = collectData.collect_url;
+          }
+        }
+      });
+    });
+    $('#delete-from-collect').click(function() {
+      var _$this = $(this);
+      var topicId = _$this.attr('data-id');
+      var data = JSON.stringify({
+        topicId:topicId
+      });
+      $.ajax ({
+        type : "DELETE",
+        url : collectData.delete_detail_action_url,
+        data:data,
+        contentType: 'application/json;charset=UTF-8',
+        success: function(result) {
+          if (result.judge === true)
+          {
+            _$this.parent().remove();
+          }
+        }
+      });
+    });
+  });
+}

+ 14 - 17
static/styles/forums.js

@@ -1,21 +1,18 @@
 function SortFuntion(){
-  var data = JSON.stringify({
-    display: $('#display').val(),
-    sort: $('#sort').val(),
-    st: $('#st').val(),
-    type:sortData.type,
-    uid:sortData.uid,
-    page:sortData.page
-  });
-  $.ajax ({
-    type : "POST",
-    url : "/order",
-    data:data,
-    contentType: 'application/json;charset=UTF-8',
-    success: function(result) {
-      $('div.topiclist').html(result);
-    }
-  });
+  var display = $('#display').val();
+  var sort =  $('#sort').val();
+  var st =  $('#st').val();
+  var params = {};
+  if (display != '0'){
+    params.within = display;
+  }
+  if (sort != '0'){
+    params.orderby = sort;
+  }
+  if (st != '0'){
+    params.desc = st;
+  }
+  window.location.href = window.location.pathname + '?' + $.param(params);
 }
 $(document).ready(function(){
   $('#display').change(function() {

+ 1 - 1
templates/base/base.html

@@ -7,7 +7,7 @@
 {% block script -%}
 {{ super() }}
 <script type="text/javascript" src="{{ url_for('static',filename='assets/home.js')}}"></script>
-<script src="http://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
+<!-- <script src="http://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"></script> -->
 {%- endblock script %}
 
 {% block style -%}

+ 25 - 25
templates/board/_macro.html

@@ -1,30 +1,30 @@
-{% macro body(board) -%}
+{% macro board_body(board) -%}
 {% import 'base/link.html' as link %}
 <div class="panel-body" style="border-bottom:1px solid #eea">
-    <div class="row" style="padding:0;margin:0;">
-        <div class="col-md-2">
-            <dl style="margin:0;">
-                <dd>{{ link.board(board) }}</dd>
-                <dd style="font-size:12px;">
-                    <span style="color:#999;">{{_('Topics:')}}</span>
-                    <span>1</span>
-                    <span style="color:#999;">{{ _('Posts:')}}</span>
-                    <span>2</span>
-                </dd>
-            </dl>
-        </div>
-        <div class="col-md-offset-8 col-md-2" style="font-size:12px">
-            {% set last_topic = board.topics.first() %}
-            {% if last_topic  %}
-            <dl style="margin:0;">
-                <dt style="word-wrap: break-word;word-break: normal;">{{ link.topic(last_topic)}}</dt>
-                <dd>{{ _('published by %(author)s',author=link.user(last_topic.author.username)) }}</dd>
-                <dd>{{ last_topic.created_at | timesince }}</dd>
-            </dl>
-            {% else %}
-            {{_('No Topic')}}
-            {% endif %}
-        </div>
+  <div class="row" style="padding:0;margin:0;">
+    <div class="col-md-2">
+      <dl style="margin:0;">
+        <dd>{{ link.board(board) }}</dd>
+        <dd style="font-size:12px;">
+          <span style="color:#999;">{{_('Topics:')}}</span>
+          <span>1</span>
+          <span style="color:#999;">{{ _('Posts:')}}</span>
+          <span>2</span>
+        </dd>
+      </dl>
     </div>
+    <div class="col-md-offset-8 col-md-2" style="font-size:12px">
+      {% set last_topic = board.topics.first() %}
+      {% if last_topic  %}
+      <dl style="margin:0;">
+        <dt style="word-wrap: break-word;word-break: normal;">{{ link.topic(last_topic)}}</dt>
+        <dd>{{ _('published by %(author)s',author=link.user(last_topic.author.username)) }}</dd>
+        <dd>{{ last_topic.created_at | timesince }}</dd>
+      </dl>
+      {% else %}
+      {{_('No Topic')}}
+      {% endif %}
+    </div>
+  </div>
 </div>
 {%- endmacro %}

+ 20 - 22
templates/board/board.html

@@ -1,29 +1,27 @@
 {% extends 'base/base.html' %}
 {% block content %}
+{% if board.parent %}
+{{ breadcrumb(hrefs={board.parent.name:url_for('forums.board',boardId=board.parent_id)},active=board.name) }}
+{% else %}
 {{ breadcrumb(active=board.name) }}
-{% from 'board/_macro.html' import body %}
-{% from 'topic/_list_macro.html' import form as topic_form %}
-{% from 'topic/_list_macro.html' import body as topic_body %}
-{% from 'topic/_list_macro.html' import no_topics %}
+{% endif %}
+{% from 'board/_macro.html' import board_body %}
 <div class="row">
-    <div class="col-md-9">
-        <div class="panel panel-primary">
-            <div class="panel-heading">
-                <a href="{{ url_for('forums.board',boardId=board.id) }}" style="color:#fff"> {{ board.name }} </a>
-            </div>
-            {{ body(board) }}
-        </div>
-        <div class="panel panel-default">
-            {{ topic_form() }}
-            {% for topic in topics.items %}
-            {{ topic_body(topic) }}
-            {% else %}
-            {{ no_topics() }}
-            {% endfor %}
-        </div>
-    </div>
-    <div class="col-md-3" style="padding-left:0">
-        {{ panel_base.board() }}
+  <div class="col-md-9">
+    <div class="panel panel-primary">
+      <div class="panel-heading">
+        <a href="{{ url_for('forums.board',boardId=board.id) }}" style="color:#fff"> {{ board.name }} </a>
+      </div>
+      {% for child in board.children -%}
+      {{ board_body(child) }}
+      {% else %}
+      {{ board_body(board) }}
+      {%- endfor %}
     </div>
+    {% include "board/topic.html" %}
+  </div>
+  <div class="col-md-3" style="padding-left:0">
+    {{ panel_base.board() }}
+  </div>
 </div>
 {% endblock %}

+ 3 - 3
templates/board/board_list.html

@@ -1,6 +1,6 @@
 {% extends 'base/base.html' %}
 {% block content %}
-{% from 'board/_macro.html' import body %}
+{% from 'board/_macro.html' import board_body %}
 {{ breadcrumb(active=_('Board')) }}
 {% for board in boards.items %}
 {% if not board.parents %}
@@ -9,9 +9,9 @@
     <a href="{{ url_for('forums.board',boardId=board.id) }}" style="color:#fff"> {{ board.name }} </a>
   </div>
   {% for child in board.children -%}
-  {{ body(child) }}
+  {{ board_body(child) }}
   {% else %}
-  {{ body(board) }}
+  {{ board_body(board) }}
   {%- endfor %}
 </div>
 {% endif %}

+ 11 - 0
templates/board/topic.html

@@ -0,0 +1,11 @@
+{% from 'topic/_list_macro.html' import form as topic_form %}
+{% from 'topic/_list_macro.html' import body as topic_body %}
+{% from 'topic/_list_macro.html' import no_topics %}
+<div class="panel panel-default">
+  {{ topic_form() }}
+  {% for topic in topics.items %}
+  {{ topic_body(topic) }}
+  {% else %}
+  {{ no_topics() }}
+  {% endfor %}
+</div>

+ 26 - 26
templates/tag/_macro.html

@@ -1,30 +1,30 @@
 {% macro title(tag) -%}
 <div class="media">
-    <div class="media-left">
-        <a href="{{ request.path }}">
-            <img class="media-object img-circle" src="{{ url_for('avatar',text=tag.name)}}" alt="avatar" style="width:56px;height:56px">
-        </a>
-    </div>
-    <div class="media-body">
-        <h4 class="media-heading"><strong class="text-capitalize">{{ tag.name }}</strong></h4>
-        {% set description = tag.description %}
-        {% if description -%}
-        {{ description }}
-        {%- endif %}
-    </div>
-    <div class="media-right">
-        <span class="rss">
-            <a href="{{ url_for('tag.feed',name=tag.name)}}">
-                <i class="icon-rss" style="padding:2px;white-space:nowrap;">Rss</i>
-            </a>
-        </span>
-        {% if g.user.is_authenticated %}
-        {% if current_user in tag.followers %}
-        <button class="btn btn-sm btn-default tagfollow active" id="{{ tag.id}}" style="padding:0 5px">取消关注</button>
-        {% else %}
-        <button class="btn btn-sm btn-default tagfollow" id="{{ tag.id}}" style="padding:0 5px">关注</button>
-        {% endif %}
-        {% endif %}
-    </div>
+  <div class="media-left">
+    <a href="{{ request.path }}">
+      <img class="media-object img-circle" src="{{ url_for('avatar',text=tag.name)}}" alt="avatar" style="width:56px;height:56px">
+    </a>
+  </div>
+  <div class="media-body">
+    <h4 class="media-heading"><strong class="text-capitalize">{{ tag.name }}</strong></h4>
+    {% set description = tag.description %}
+    {% if description -%}
+    {{ description }}
+    {%- endif %}
+  </div>
+  <div class="media-right">
+    <span class="rss">
+      <a href="{{ url_for('tag.feed',name=tag.name)}}">
+        <i class="icon-rss" style="padding:2px;white-space:nowrap;">Rss</i>
+      </a>
+    </span>
+    {% if g.user.is_authenticated %}
+    {% if tag.id | tag_is_followed %}
+    <button class="btn btn-sm btn-default tag-following active" data-id="{{ tag.id}}" style="padding:0 5px">取消关注</button>
+    {% else %}
+    <button class="btn btn-sm btn-default tag-following" data-id="{{ tag.id}}" style="padding:0 5px">关注</button>
+    {% endif %}
+    {% endif %}
+  </div>
 </div>
 {%- endmacro %}

+ 7 - 7
templates/tag/panel.html

@@ -7,15 +7,15 @@
 {% macro tag(tag) -%}
 {{ base() }}
 <div class="panel panel-default hidden-xs" style="font-size:13px;">
-    {{ parents(tag) }}
-    {{ children(tag) }}
+  {{ parents(tag) }}
+  {{ children(tag) }}
 </div>
 {{ count() }}
 {%- endmacro %}
 
 {% macro parents(tag) -%}
 <div class="panel-body" style="padding:10;margin:0;">
-    <strong>父节点</strong>
+  <strong>父节点</strong>
 </div>
 {% if tag.parents -%}
 {% for parent in tag.parents -%}
@@ -32,7 +32,7 @@
 {% macro children(tag) -%}
 {% if tag.children -%}
 <div class="panel-body" style="padding:10;margin:0;">
-    <strong>子节点</strong>
+  <strong>子节点</strong>
 </div>
 {% for child in tag.children -%}
 {{ link(child.name) }}
@@ -44,7 +44,7 @@
 {% set parent = tag.parents[0] %}
 {% if parent.children -%}
 <div class="panel-body" style="padding:10;margin:0;">
-    <strong>相关节点</strong>
+  <strong>相关节点</strong>
 </div>
 {% for child in parent.children if child.name != tag.name -%}
 {{ link(child.name) }}
@@ -54,7 +54,7 @@
 
 {% macro link(name) -%}
 <div class="panel-body" style="padding:10px;margin-top:0;padding-top:0">
-    <img alt="" src="{{ url_for('avatar',text=name)}}" style="width:24px;"/>
-    <a class="text-capitalize" href="{{ url_for('tag.tag',name=name)}}" style="color:#555;">{{ name }}</a>
+  <img alt="" src="{{ url_for('avatar',text=name)}}" style="width:24px;"/>
+  <a class="text-capitalize" href="{{ url_for('tag.tag',name=name)}}" style="color:#555;">{{ name }}</a>
 </div>
 {%- endmacro %}

+ 11 - 19
templates/tag/tag.html

@@ -1,27 +1,19 @@
 {% extends 'base/base.html' %}
 {% block content %}
 {% from 'tag/_macro.html' import title %}
-{% from 'topic/_list_macro.html' import form as topic_form %}
-{% from 'topic/_list_macro.html' import body as topic_body %}
-{% from 'topic/_list_macro.html' import no_topics as topic_no_topics %}
 {{ breadcrumb(hrefs={_('All Tags'):url_for('tag.list')},active=tag.name)}}
 <div class="row">
-    <div class="col-md-9">
-        <div class="panel panel-default">
-            <div class="panel-body" style="border-bottom: 1px solid #ddd;">
-                {{ title(tag) }}
-            </div>
-            {{ topic_form() }}
-            {% for topic in tag.topics %}
-            {{ topic_body(topic) }}
-            {% else %}
-            {{ topic_no_topics() }}
-            {% endfor %}
-        </div>
-    </div>
-    <div class="col-md-3" style="padding-left:0">
-        {% from 'tag/panel.html' import tag as tag_panel %}
-        {{ tag_panel(tag) }}
+  <div class="col-md-9">
+    <div class="panel panel-default">
+      <div class="panel-body" style="border-bottom: 1px solid #ddd;">
+        {{ title(tag) }}
+      </div>
+      {% include "board/topic.html" %}
     </div>
+  </div>
+  <div class="col-md-3" style="padding-left:0">
+    {% from 'tag/panel.html' import tag as tag_panel %}
+    {{ tag_panel(tag) }}
+  </div>
 </div>
 {% endblock %}

+ 24 - 24
templates/topic/ask/_macro.html

@@ -1,43 +1,43 @@
 {% macro title() -%}
 <span style="font-size:12px;color:#999">
-    (请用简短的话语描述你的问题)
+  (请用简短的话语描述你的问题)
 </span>
 {%- endmacro %}
 
 {% macro category() -%}
 <span style="font-size:12px;color:#999">
-    (请输入分类)
+  (请输入分类)
 </span>
 {%- endmacro %}
 
 {% macro tags() -%}
 <span style="font-size:12px;color:#999">
-    (请输入节点)
+  (请输入节点)
 </span>
 {%- endmacro %}
 
 {% macro content() -%}
 <table style="font-size:12px;color:#999">
-    <tr>
-        <th colspan="2">默认语法支持部分html标签</th>
-    </tr>
-    <tr>
-        <td>b:</td>
-        <td>加粗</td>
-    </tr>
-    <tr>
-        <td>i:</td>
-        <td>倾斜</td>
-    </tr>
-    <tr>
-        <td>br:</td>
-        <td>换行</td>
-    </tr>
-    <tr>
-        <td valign="top">font:</td>
-        <td> 设置字体(目前仅允许设置字体颜色)
-            <p>注:字体颜色请勿设置成白色或与白色相近的颜色</p>
-        </td>
-    </tr>
+  <tr>
+    <th colspan="2">默认语法支持部分html标签</th>
+  </tr>
+  <tr>
+    <td>b:</td>
+    <td>加粗</td>
+  </tr>
+  <tr>
+    <td>i:</td>
+    <td>倾斜</td>
+  </tr>
+  <tr>
+    <td>br:</td>
+    <td>换行</td>
+  </tr>
+  <tr>
+    <td valign="top">font:</td>
+    <td> 设置字体(目前仅允许设置字体颜色)
+      <p>注:字体颜色请勿设置成白色或与白色相近的颜色</p>
+    </td>
+  </tr>
 </table>
 {%- endmacro %}

+ 23 - 17
templates/topic/ask/form.html

@@ -1,32 +1,38 @@
 {% import 'topic/ask/_macro.html' as place %}
 <div class="row" style="padding:0;margin:0;">
-    <div class="col-sm-2">
-        {{ form.hidden_tag() }}
-        {{ form.title.label(class="control-label") }}
-        {{ place.title() }}
-    </div>
-    <div class="col-sm-10" style="margin-bottom:8px;">
-        {{ form.title(class="form-control") }}
-    </div>
+  <div class="col-sm-2">
+    {{ form.hidden_tag() }}
+    {{ form.title.label(class="control-label") }}
+    {{ place.title() }}
+  </div>
+  <div class="col-sm-10" style="margin-bottom:8px;">
+    {{ form.title(class="form-control") }}
+  </div>
 </div>
-<div class="col-sm-2">
+<div class="row" style="padding:0;margin:0;">
+  <div class="col-sm-2">
     {{ form.category.label(class="control-label") }}
     {{ place.category() }}
-</div>
-<div class="col-sm-10" style="margin-bottom:8px;">
+  </div>
+  <div class="col-sm-10" style="margin-bottom:8px;">
     {{ form.category(class="form-control") }}
+  </div>
 </div>
-<div class="col-sm-2">
+<div class="row" style="padding:0;margin:0;">
+  <div class="col-sm-2">
     {{ form.tags.label(class="control-label") }}
     {{ place.tags() }}
-</div>
-<div class="col-sm-10" style="margin-bottom:8px;">
+  </div>
+  <div class="col-sm-10" style="margin-bottom:8px;">
     {{ form.tags(class="form-control",id="tokenfield",placeholder="节点请以英文逗号隔开.请勿输入超过4个节点") }}
+  </div>
 </div>
-<div class="col-sm-2">
+<div class="row" style="padding:0;margin:0;">
+  <div class="col-sm-2">
     {{ form.content.label(class="control-label") }}
     {{ place.content() }}
-</div>
-<div class="col-sm-10" style="margin-bottom:8px;">
+  </div>
+  <div class="col-sm-10" style="margin-bottom:8px;">
     {{ form.content(class="form-control",rows="8",onchange="preview()",placeholder="请输入问题描述") }}
+  </div>
 </div>

+ 36 - 0
templates/topic/collect.html

@@ -0,0 +1,36 @@
+<div class="modal fade" id="sidecollect" tabindex="-1" role="dialog" aria-labelledby="sidecollectLabel">
+  <div class="modal-dialog" role="document">
+    <div class="modal-content">
+      <div class="modal-header">
+        <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
+        <h4 class="modal-title" id="sidecollectLabel">收藏夹</h4>
+      </div>
+      {% if not current_user.collects.all() %}
+      <div class="modal-body">
+        <a href="{{ url_for('topic.collectlist') }}" style="text-decoration:none">
+          <i class="fa fa-plus"></i> 创建
+        </a>
+      </div>
+      {% else %}
+      <form action="{{ url_for('topic.add_to_collect',topicId=topic.id) }}" method="POST">
+        <div class="modal-body">
+          <p> 添加到收藏夹 </p>
+          {% for collect in current_user.collects %}
+          <label data-id="{{ collect.id }}"><input name="add-to-collect" type="checkbox" value="{{ collect.id}}" />{{ collect.name }}</label>
+          {% if collect.is_privacy %}
+          <span class="label label-default">私密</span>
+          {% else %}
+          <span class="label label-default">公开</span>
+          {% endif %}
+          <br/>
+          {% endfor %}
+        </div>
+        <div class="modal-footer" style="padding-top:5px;padding-bottom:5px;">
+          <button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
+          <button type="submit" class="btn btn-primary">提交</button>
+        </div>
+      </form>
+      {% endif %}
+    </div>
+  </div>
+</div>

+ 5 - 41
templates/topic/panel.html

@@ -1,11 +1,11 @@
 {% if g.user.is_authenticated %}
 <ul class="list-group">
-  {% if topic in current_user.following_topics %}
-  <button type="button" class="btn btn-info btn-block topicfollow active" id="{{ topic.id }}">取消关注</button>
+  {% if topic.id | topic_is_followed %}
+  <button type="button" class="btn btn-info btn-block topic-following active" data-id="{{topic.id}}">取消关注</button>
   {% else %}
-  <button type="button" class="btn btn-info btn-block topicfollow" id="{{ topic.id }}">关注问题</button>
+  <button type="button" class="btn btn-info btn-block topic-following" data-id="{{topic.id}}">关注问题</button>
   {% endif %}
-  {% if current_user.is_authenticated and current_user == topic.author %}
+  {% if g.user == topic.author %}
   <a class="btn btn-success btn-block" href="{{ url_for('topic.edit',topicId=topic.id)}}">
     编辑问题
   </a>
@@ -14,43 +14,7 @@
   <span class="btn btn-default btn-block">已收藏</span>
   {% else %}
   <span class="btn btn-default btn-block" data-toggle="modal" data-target="#sidecollect">收藏问题</span>
-  <div class="modal fade" id="sidecollect" tabindex="-1" role="dialog" aria-labelledby="sidecollectLabel">
-    <div class="modal-dialog" role="document">
-      <div class="modal-content">
-        <div class="modal-header">
-          <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
-          <h4 class="modal-title" id="sidecollectLabel">收藏夹</h4>
-        </div>
-        {% if not current_user.collects.all() %}
-        <div class="modal-body">
-          <a href="{{ url_for('mine.collect') }}" style="text-decoration:none">
-            <i class="icon icon-plus"></i> 创建
-          </a>
-        </div>
-        {% else %}
-        <form action="" method="POST">
-          {{ form.hidden_tag() }}
-          <div class="modal-body">
-            <p> 添加到收藏夹 </p>
-            {% for collect in current_user.collects %}
-            <label data-id="{{ collect.id }}"><input name="add-to-collect" type="checkbox" value="{{ collect.id}}" />{{ collect.name }}</label>
-            {% if collect.is_privacy %}
-            <span class="label label-default">私密</span>
-            {% else %}
-            <span class="label label-default">公开</span>
-            {% endif %}
-            <br/>
-            {% endfor %}
-          </div>
-          <div class="modal-footer" style="padding-top:5px;padding-bottom:5px;">
-            <button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
-            <button type="submit" class="btn btn-primary">提交</button>
-          </div>
-        </form>
-        {% endif %}
-      </div>
-    </div>
-  </div>
+  {% include "topic/collect.html" %}
   {% endif %}
 </ul>
 {% endif %}

+ 3 - 3
templates/user/info.html

@@ -2,10 +2,10 @@
 {% if g.user.is_authenticated and current_user.username != g.user_url %}
 <div class="list-group-item">
   <span class="text-right" style="display:block">
-    {% if user in current_user.following_users %}
-    <button class="btn btn-sm btn-default userfollow active" id="{{ user.id}}">取消关注</button>
+    {% if user.id | user_is_followed %}
+    <button class="btn btn-sm btn-default user-following active" data-id="{{ user.id}}">取消关注</button>
     {% else %}
-    <button class="btn btn-sm btn-default userfollow" id="{{ user.id}}">关注他</button>
+    <button class="btn btn-sm btn-default user-following" data-id="{{ user.id}}">关注他</button>
     {% endif %}
     <button class="btn btn-sm btn-default" id="{{ user.id}}" title="私信" data-toggle="modal" data-target="#send-message"><i class="fa fa-comments" style="font-size:16px;"></i></button>
   </span>