homeland 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.markdown +32 -0
- data/app/assets/images/reply.png +0 -0
- data/app/assets/javascripts/homeland.coffee +48 -0
- data/app/assets/stylesheets/homeland.scss +84 -0
- data/app/controllers/homeland/application_controller.rb +14 -0
- data/app/controllers/homeland/replies_controller.rb +20 -0
- data/app/controllers/homeland/topics_controller.rb +124 -0
- data/app/helpers/homeland/application_helper.rb +27 -0
- data/app/helpers/homeland/topics_helper.rb +8 -0
- data/app/models/homeland/node.rb +24 -0
- data/app/models/homeland/reply.rb +29 -0
- data/app/models/homeland/section.rb +17 -0
- data/app/models/homeland/topic.rb +43 -0
- data/app/views/homeland/replies/edit.html.erb +28 -0
- data/app/views/homeland/shared/_error_messages.html.erb +11 -0
- data/app/views/homeland/topics/_base.html.erb +4 -0
- data/app/views/homeland/topics/_form.html.erb +23 -0
- data/app/views/homeland/topics/_sidebar.html.erb +65 -0
- data/app/views/homeland/topics/_topic.html.erb +26 -0
- data/app/views/homeland/topics/edit.html.erb +11 -0
- data/app/views/homeland/topics/feed.builder +20 -0
- data/app/views/homeland/topics/index.html.erb +51 -0
- data/app/views/homeland/topics/new.html.erb +11 -0
- data/app/views/homeland/topics/show.html.erb +88 -0
- data/config/routes.rb +16 -0
- data/lib/generators/homeland/install/templates/initializer.rb +8 -0
- data/lib/generators/homeland/install/templates/javascripts/jquery.hotkeys.js +99 -0
- data/lib/generators/homeland/install/templates/javascripts/jquery.timeago.js +147 -0
- data/lib/generators/homeland/install/templates/locales/homeland.zh-CN.yml +14 -0
- data/lib/generators/homeland/install_generator.rb +45 -0
- data/lib/generators/homeland/views_generator.rb +23 -0
- data/lib/homeland.rb +6 -0
- data/lib/homeland/engine.rb +32 -0
- data/lib/homeland/setting.rb +37 -0
- 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,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
|