homeland 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. data/README.markdown +32 -0
  2. data/app/assets/images/reply.png +0 -0
  3. data/app/assets/javascripts/homeland.coffee +48 -0
  4. data/app/assets/stylesheets/homeland.scss +84 -0
  5. data/app/controllers/homeland/application_controller.rb +14 -0
  6. data/app/controllers/homeland/replies_controller.rb +20 -0
  7. data/app/controllers/homeland/topics_controller.rb +124 -0
  8. data/app/helpers/homeland/application_helper.rb +27 -0
  9. data/app/helpers/homeland/topics_helper.rb +8 -0
  10. data/app/models/homeland/node.rb +24 -0
  11. data/app/models/homeland/reply.rb +29 -0
  12. data/app/models/homeland/section.rb +17 -0
  13. data/app/models/homeland/topic.rb +43 -0
  14. data/app/views/homeland/replies/edit.html.erb +28 -0
  15. data/app/views/homeland/shared/_error_messages.html.erb +11 -0
  16. data/app/views/homeland/topics/_base.html.erb +4 -0
  17. data/app/views/homeland/topics/_form.html.erb +23 -0
  18. data/app/views/homeland/topics/_sidebar.html.erb +65 -0
  19. data/app/views/homeland/topics/_topic.html.erb +26 -0
  20. data/app/views/homeland/topics/edit.html.erb +11 -0
  21. data/app/views/homeland/topics/feed.builder +20 -0
  22. data/app/views/homeland/topics/index.html.erb +51 -0
  23. data/app/views/homeland/topics/new.html.erb +11 -0
  24. data/app/views/homeland/topics/show.html.erb +88 -0
  25. data/config/routes.rb +16 -0
  26. data/lib/generators/homeland/install/templates/initializer.rb +8 -0
  27. data/lib/generators/homeland/install/templates/javascripts/jquery.hotkeys.js +99 -0
  28. data/lib/generators/homeland/install/templates/javascripts/jquery.timeago.js +147 -0
  29. data/lib/generators/homeland/install/templates/locales/homeland.zh-CN.yml +14 -0
  30. data/lib/generators/homeland/install_generator.rb +45 -0
  31. data/lib/generators/homeland/views_generator.rb +23 -0
  32. data/lib/homeland.rb +6 -0
  33. data/lib/homeland/engine.rb +32 -0
  34. data/lib/homeland/setting.rb +37 -0
  35. metadata +91 -0
data/README.markdown ADDED
@@ -0,0 +1,32 @@
1
+ A new style forum for small communitys as a Rails Engine. you can mount this in you any apps.
2
+
3
+ ## Install
4
+
5
+ * You need install *Ruby 1.9.2*, *Rubygems* and *Rails 3.1* and *Mongoid* first.
6
+
7
+ 1. in you Gemfile:
8
+
9
+ gem "homeland"
10
+
11
+ 1. Install thought this commands:
12
+
13
+ rails g homeland:install
14
+
15
+ 2. change you application.html.erb
16
+
17
+ before:
18
+
19
+ <%= link_to "Home", root_path %>
20
+ <%= link_to "Posts", posts_path %>
21
+
22
+ after:
23
+
24
+ <%= link_to "Home", main_app.root_path %>
25
+ <%= link_to "Home", main_app.posts_path %>
26
+
27
+
28
+ ## Generate views to custom
29
+
30
+ rails g homeland:views
31
+
32
+ Thanks [V2EX](http://v2ex.com) forum idea.
Binary file
@@ -0,0 +1,48 @@
1
+ #= require jquery.hotkeys
2
+ #= require jquery.timeago
3
+ #= require_self
4
+ # TopicsController 下所有页面的 JS 功能
5
+ window.Topics =
6
+ # 往话题编辑器里面插入图片代码
7
+ appendImageFromUpload : (srcs) ->
8
+ txtBox = $(".topic_body_text_area")
9
+ for src in srcs
10
+ txtBox.val("#{txtBox.val()}[img]#{src}[/img]\n")
11
+ txtBox.focus()
12
+ $("#add_image").jDialog.close()
13
+
14
+ # 上传图片
15
+ addImageClick : () ->
16
+ opts =
17
+ title:"插入图片"
18
+ width: 350
19
+ height: 145
20
+ content: '<iframe src="/photos/tiny_new" frameborder="0" style="width:330px; height:145px;"></iframe>',
21
+ close_on_body_click : false
22
+
23
+ $("#add_image").jDialog(opts)
24
+ return false
25
+
26
+ # 回复
27
+ reply : (floor,login) ->
28
+ reply_body = $("#reply_body")
29
+ new_text = "##{floor}楼 @#{login} "
30
+ if reply_body.val().trim().length == 0
31
+ new_text += ''
32
+ else
33
+ new_text = "\n#{new_text}"
34
+ reply_body.focus().val(reply_body.val() + new_text)
35
+ return false
36
+
37
+ # 高亮楼层
38
+ hightlightReply : (floor) ->
39
+ $("#replies .reply").removeClass("light")
40
+ $("#reply"+floor).addClass("light")
41
+
42
+ # Page.ready
43
+ $(document).ready ->
44
+ $("abbr.timeago").timeago()
45
+ $("textarea").bind "keydown","ctrl+return",(el) ->
46
+ if $(el.target).val().trim().length > 0
47
+ $("#reply form").submit()
48
+
@@ -0,0 +1,84 @@
1
+ .topics .topic .title a:link,
2
+ .topics .topic .title a:visited,
3
+ .node_topics .title a:link,
4
+ .node_topics .title a:visited {color:#2c64d7; text-decoration: none;}
5
+ .topics .topic .title a:hover,
6
+ .node_topics .title a:hover { text-decoration: underline; }
7
+
8
+ .sidebar p { margin-bottom:8px;}
9
+
10
+ .topics { }
11
+ .topics .topic { }
12
+ .topics .topic_line {border-top:1px solid #DDD; padding:10px 0; margin:0 -10px; vertical-align:top; }
13
+ #main .topics .topic h1 { font-size:22px; color:#333; margin-top:6px; text-align: left; line-height:100%;margin-bottom:8px;}
14
+ .topics .topic .avatar_large { width:80px; }
15
+ .topics .topic .avatar { width:56px; margin-left:10px; }
16
+ .topics .topic .infos { padding-right:90px;}
17
+ .topics .topic .right_info { padding-left:66px;}
18
+ .topics .topic .title { font-size:14px; }
19
+ .topics .topic .info { color:#999; margin-bottom:2px; }
20
+ .topics .topic .info a:link,
21
+ .topics .topic .info a:visited { color:#999; text-decoration: underline; }
22
+ .topics .body { line-height:160%; font-size:14px; margin-top:8px;}
23
+ .topics .body p { margin-bottom:10px; }
24
+ .topics .tools { text-align:right; }
25
+ .topics .tools a {margin-left:6px;}
26
+ .topics .topic .replies { width:70px; margin-right:10px; text-align:right; vertical-align:middle; }
27
+ .topics .topic .replies a.count,
28
+ .topics .topic .replies a.count:visited { line-height: 12px; font-weight: bold; color: white;
29
+ background-color: #1c7fdb; display: inline-block;
30
+ padding: 2px 10px 2px 10px;
31
+ -moz-border-radius: 12px; -webkit-border-radius: 12px;
32
+ margin: 18px 12px 0px 0px; text-decoration: none; }
33
+ .topics .topic .replies a.count:hover { color:write; background-color:#1F8AEE; }
34
+ .topics .topic .replies a.state0:link,
35
+ .topics .topic .replies a.state0:visited { background:#DDD; }
36
+ .topics .topic .replies a.state0:hover { background:#1dc115;}
37
+ .topics .topic .replies a.state2:link,
38
+ .topics .topic .replies a.state2:visited { background:#22D419; }
39
+ .topics .topic .replies a.state2:hover {background:#1dc115; }
40
+ .topics tr.topic td.replies { text-align:right; width:50px; font-size:14px; color:green;font-weight:bold; }
41
+ .more { padding:8px; border-top:1px solid #DDD; font-size:14px; text-align:right; }
42
+
43
+ /* topics list in node page */
44
+ .node_topics { margin-bottom:16px;}
45
+ .node_topics tr td { padding:8px; }
46
+ .node_topics tr.head td { padding-top:14px; color:#CCC; font-weight:bold; font-size:12px; }
47
+ .node_topics tr.topic td.title { font-size:14px; font-weight:bold; }
48
+ .node_topics tr.topic td.author { width:80px;}
49
+ .node_topics tr.topic td.author a { color:#666; font-weight:bold; }
50
+
51
+ .no_result { margin-bottom:20px; text-align:center; }
52
+
53
+ #node_info { padding:8px;margin-bottom:8px;padding-top:0px; }
54
+ #node_info h1 { font-size:18px; color:000; display:inline; margin-right:5px; }
55
+ #node_info p { margin-bottom:8px; }
56
+ #node_info p.summary { color:#999; }
57
+
58
+ #replies {}
59
+ #replies .total { padding-bottom:6px; color:#999; }
60
+ #replies .reply { border-top:1px solid #DDD; padding:8px 10px; margin:0 -10px;}
61
+ #replies .reply.light { background:#EFFCEE;}
62
+ #replies .reply .face { padding-right:8px; width:48px; margin-left:0; }
63
+ #replies .reply .infos { padding-left:58px;}
64
+ #replies .reply .info { margin-top:4px; color:#999;height:20px;}
65
+ #replies .reply .info a:link,
66
+ #replies .reply .info a:visited { color:#999; text-decoration: underline;}
67
+ #replies .reply .info .name { float:left; }
68
+ #replies .reply .info .time { float:right; text-align:right; }
69
+ #replies .reply .info a.reply_link img { vertical-align:middle;}
70
+ #replies .reply .body { line-height:180%; font-size:14px; text-overflow: ellipsis;}
71
+ #replies .reply .body img { max-width:622px;}
72
+
73
+ #reply.form textarea { width:578px; }
74
+
75
+ .edit_tools { text-align:right; margin-bottom:3px;}
76
+
77
+ .totals ul li { line-height:150%;}
78
+
79
+ #topic_new_tip li { margin-bottom:6px;}
80
+
81
+ .body a.at_floor { color:#376B43; }
82
+ .body a.at_user { }
83
+
84
+ .topic_body_text_area { width:600px;}
@@ -0,0 +1,14 @@
1
+ # coding: utf-8
2
+ class Homeland::ApplicationController < ApplicationController
3
+ helper_method :current_user
4
+
5
+ alias_method :origin_current_user, Homeland.current_user_method.to_sym
6
+ def current_user
7
+ origin_current_user
8
+ end
9
+
10
+ def homeland_require_user
11
+ self.send(Homeland.require_user_method)
12
+ end
13
+
14
+ end
@@ -0,0 +1,20 @@
1
+ # coding: utf-8
2
+ module Homeland
3
+ class RepliesController < Homeland::ApplicationController
4
+ before_filter :homeland_require_user
5
+
6
+ def edit
7
+ @reply = current_user.replies.find(params[:id])
8
+ end
9
+
10
+ def update
11
+ @reply = current_user.replies.find(params[:id])
12
+
13
+ if @reply.update_attributes(params[:reply])
14
+ redirect_to(topic_path(@reply.topic_id), :notice => '回帖删除成功.')
15
+ else
16
+ render :action => "edit"
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,124 @@
1
+ # coding: utf-8
2
+ module Homeland
3
+ class TopicsController < Homeland::ApplicationController
4
+ before_filter :homeland_require_user, :only => [:new,:edit,:create,:update,:destroy,:reply]
5
+ before_filter :init_list_sidebar, :only => [:index,:recent,:show,:cate,:search]
6
+ caches_page :feed, :expires_in => 1.hours
7
+
8
+ private
9
+ def init_list_sidebar
10
+ if !fragment_exist? "topic/init_list_sidebar/hot_nodes"
11
+ @hot_nodes = Node.hots.limit(20)
12
+ end
13
+ end
14
+
15
+ public
16
+ # GET /topics
17
+ # GET /topics.xml
18
+ def index
19
+ @topics = Topic.last_actived.limit(10)
20
+ @sections = Section.all
21
+
22
+ end
23
+
24
+ def feed
25
+ @topics = Topic.recent.limit(20)
26
+ response.headers['Content-Type'] = 'application/rss+xml'
27
+ render :layout => false
28
+ end
29
+
30
+ def node
31
+ @node = Node.find(params[:id])
32
+ @topics = @node.topics.last_actived.paginate(:page => params[:page],:per_page => 50)
33
+
34
+ render :action => "index"
35
+ end
36
+
37
+ def recent
38
+ @topics = Topic.recent.limit(50)
39
+
40
+ render :action => "index"
41
+ end
42
+
43
+ def search
44
+ result = Redis::Search.query("Topic", params[:key], :limit => 500)
45
+ ids = result.collect { |r| r["id"] }
46
+ @topics = Topic.find(ids).paginate(:page => params[:page], :per_page => 20)
47
+
48
+ render :action => "index"
49
+ end
50
+
51
+ def show
52
+ @topic = Topic.find(params[:id])
53
+ @topic.hit!
54
+ @node = @topic.node
55
+ @replies = @topic.replies.all
56
+
57
+ end
58
+
59
+ # GET /topics/new
60
+ # GET /topics/new.xml
61
+ def new
62
+ @topic = Topic.new
63
+ @topic.node_id = params[:node]
64
+ @node = Node.find(params[:node])
65
+ end
66
+
67
+ def reply
68
+ @topic = Topic.find(params[:id])
69
+ @reply = @topic.replies.build(params[:reply])
70
+ @reply.user_id = current_user.id
71
+ if @reply.save
72
+ flash[:notice] = "回复成功。"
73
+ else
74
+ flash[:notice] = @reply.errors.full_messages.join("<br />")
75
+ end
76
+ redirect_to topic_path(params[:id],:anchor => 'reply')
77
+ end
78
+
79
+ # GET /topics/1/edit
80
+ def edit
81
+ @topic = current_user.topics.find(params[:id])
82
+ @node = @topic.node
83
+ end
84
+
85
+ # POST /topics
86
+ # POST /topics.xml
87
+ def create
88
+ pt = params[:topic]
89
+ @topic = Topic.new(pt)
90
+ @topic.user_id = current_user.id
91
+ @topic.node_id = params[:node] || pt[:node_id]
92
+
93
+ if @topic.save
94
+ redirect_to(topic_path(@topic.id), :notice => '帖子创建成功.')
95
+ else
96
+ render :action => "new"
97
+ end
98
+ end
99
+
100
+ # PUT /topics/1
101
+ # PUT /topics/1.xml
102
+ def update
103
+ @topic = current_user.topics.find(params[:id])
104
+ pt = params[:topic]
105
+ @topic.node_id = pt[:node_id]
106
+ @topic.title = pt[:title]
107
+ @topic.body = pt[:body]
108
+
109
+ if @topic.save
110
+ redirect_to(topic_path(@topic.id), :notice => '帖子修改成功.')
111
+ else
112
+ render :action => "edit"
113
+ end
114
+ end
115
+
116
+ # DELETE /topics/1
117
+ # DELETE /topics/1.xml
118
+ def destroy
119
+ @topic = current_user.topics.find(params[:id])
120
+ @topic.destroy
121
+ redirect_to(topics_path, :notice => '帖子删除成功.')
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ module Homeland
3
+ module ApplicationHelper
4
+ def owner?(item)
5
+ return false if item.blank?
6
+ return if current_user.blank?
7
+ item.user_id == current_user.id
8
+ end
9
+
10
+ def timeago(time, options = {})
11
+ options[:class] ||= "timeago"
12
+ content_tag(:abbr, time.to_s, options.merge(:title => time.getutc.iso8601)) if time
13
+ end
14
+
15
+ def user_name_tag(user,options = {})
16
+ return "" if user.blank?
17
+ link_to_if(Homeland.user_path_method, user.name, [main_app, user])
18
+ end
19
+
20
+ def user_avatar_tag(user)
21
+ return "" if user.blank?
22
+ img = image_tag(user.try(Homeland.user_avatar_method.to_sym))
23
+ link_to_if(Homeland.user_path_method, raw(img), [main_app, user])
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,8 @@
1
+ # coding: utf-8
2
+ module Homeland
3
+ module TopicsHelper
4
+ def format_topic_body(text,title = "",allow_image = true)
5
+ auto_link(simple_format(text),:all, :target => '_blank', :rel => "nofollow")
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ module Homeland
3
+ class Node
4
+ include Mongoid::Document
5
+ include Mongoid::Timestamps
6
+
7
+ field :name
8
+ field :summary
9
+ field :sort, :type => Integer, :default => 0
10
+ field :topics_count, :type => Integer, :default => 0
11
+
12
+ has_many :topics, :class_name => "Homeland::Topic"
13
+ belongs_to :section, :class_name => "Homeland::Section"
14
+
15
+ validates_presence_of :name, :summary
16
+ validates_uniqueness_of :name
17
+
18
+ index :topics_count
19
+ index :sort
20
+
21
+ scope :hots, desc(:topics_count)
22
+ scope :sorted, desc(:sort)
23
+ end
24
+ end
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ module Homeland
3
+ class Reply
4
+ include Mongoid::Document
5
+ include Mongoid::Timestamps
6
+ include Mongoid::Paranoia
7
+
8
+ field :body
9
+
10
+ belongs_to :user, :inverse_of => :replies, :class_name => Homeland.user_class.to_s
11
+ belongs_to :topic, :inverse_of => :replies, :class_name => "Homeland::Topic"
12
+
13
+ attr_protected :user_id, :topic_id
14
+
15
+ index :topic_id
16
+ index :user_id
17
+
18
+ validates_presence_of :body
19
+ scope :recent, desc(:_id)
20
+
21
+ after_create :update_parent_last_replied
22
+ def update_parent_last_replied
23
+ self.topic.replied_at = Time.now
24
+ self.topic.last_reply_user_id = self.user_id
25
+ self.topic.replies_count = self.topic.replies.count
26
+ self.topic.save
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,17 @@
1
+ # coding: utf-8
2
+ module Homeland
3
+ class Section
4
+ include Mongoid::Document
5
+ include Mongoid::Timestamps
6
+
7
+ field :name
8
+ field :sort, :type => Integer, :default => 0
9
+ has_many :nodes, :dependent => :destroy, :class_name => "Homeland::Node"
10
+
11
+ validates_presence_of :name
12
+ validates_uniqueness_of :name
13
+
14
+
15
+ default_scope desc(:sort)
16
+ end
17
+ end