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
@@ -0,0 +1,43 @@
1
+ # coding: utf-8
2
+ module Homeland
3
+ class Topic
4
+ include Mongoid::Document
5
+ include Mongoid::Timestamps
6
+ include Mongoid::Paranoia
7
+
8
+ field :title
9
+ field :body
10
+ field :replied_at , :type => DateTime
11
+ field :replies_count, :type => Integer, :default => 0
12
+ field :hits, :type => Integer, :default => 0
13
+
14
+ belongs_to :user, :inverse_of => :topics, :class_name => Homeland.user_class.to_s
15
+ belongs_to :node, :class_name => "Homeland::Node"
16
+ belongs_to :last_reply_user, :class_name => Homeland.user_class.to_s
17
+ has_many :replies, :class_name => "Homeland::Reply"
18
+
19
+ attr_protected :user_id
20
+ validates_presence_of :user_id, :title, :body, :node_id
21
+
22
+ index :replied_at
23
+ index :user_id
24
+ index :node_id
25
+
26
+ # scopes
27
+ scope :last_actived, desc(:replied_at).desc(:_id)
28
+ scope :recent, desc(:_id)
29
+ before_save :set_replied_at
30
+ def set_replied_at
31
+ self.replied_at = Time.now
32
+ end
33
+
34
+ def hit!
35
+ self.update_attribute(:hit,{ :inc => 1 })
36
+ end
37
+
38
+ def node_name
39
+ return "" if self.node.blank?
40
+ self.node.name
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,28 @@
1
+ <% content_for :breadcrumb do %>
2
+ <span class="current">修改回帖</span>
3
+ <% end %>
4
+ <div class="sidebar">
5
+ <div class="box">
6
+ 回帖所在文章:<br />
7
+ <a href="<%= topic_path(@reply.topic_id) %>"><%= @reply.topic.title %></a>
8
+ </div>
9
+ </div>
10
+ <div class="content">
11
+ <div class="box">
12
+ <%= form_for(@reply) do |f| %>
13
+ <fieldset>
14
+ <legend>修改回帖</legend>
15
+ <%= render "shared/error_messages", :target => @reply %>
16
+ <div class="clearfix">
17
+ <%= f.label :body %>
18
+ <div class="input">
19
+ <%= f.text_area :body, :class => "topic_body_text_area", :rows => "20" %>
20
+ </div>
21
+ </div>
22
+ <div class="actions">
23
+ <button class="btn primary">保存更新</button>
24
+ <a href="<%= topic_path(@reply.topic_id) %>" class="btn">取消</a>
25
+ </div>
26
+ <% end %>
27
+ </div>
28
+ </div>
@@ -0,0 +1,11 @@
1
+ <% if target.errors.any? %>
2
+ <div class="alert-message block-message error">
3
+ <a class="close" href="#">×</a>
4
+ <p><strong>有 <%= target.errors.count %> 处问题导至无法提交:</strong></p>
5
+ <ul>
6
+ <% target.errors.full_messages.each do |msg| %>
7
+ <li><%= msg %></li>
8
+ <% end %>
9
+ </ul>
10
+ </div>
11
+ <% end %>
@@ -0,0 +1,4 @@
1
+ <% content_for :breadcrumb do %>
2
+ <li><span class="divider">/</span></li>
3
+ <li><%= link_to "讨论区",root_path %> <span class="divider">/</span></li>
4
+ <% end %>
@@ -0,0 +1,23 @@
1
+ <%= form_for(@topic) do |f| %>
2
+ <fieldset>
3
+ <legend><%= @topic.id.blank? ? "新建帖子" : "修改帖子" %></legend>
4
+ <%= render "shared/error_messages", :target => @topic %>
5
+ <%= f.hidden_field :node_id %>
6
+ <div class="clearfix">
7
+ <%= f.label :title %>
8
+ <div class="input">
9
+ <%= f.text_field :title, :class => "span7" %>
10
+ </div>
11
+ </div>
12
+ <div class="clearfix">
13
+ <%= f.label :body %>
14
+ <div class="input">
15
+ <%= f.text_area :body, :class => "topic_body_text_area", :style => "height:200px" %>
16
+ </div>
17
+ </div>
18
+ <div class="actions">
19
+ <input type="submit" value="保存" class="btn primary">
20
+ <a href="<%= @topic.id.blank? ? topics_path : topic_path(@topic) %>" class="btn">取消</a>
21
+ </div>
22
+ </fieldset>
23
+ <% end %>
@@ -0,0 +1,65 @@
1
+ <div class="sidebar">
2
+ <% if ["show","new","edit","node"].index(params[:action]) %>
3
+ <div class="box">
4
+ <h2><a href="<%= node_topics_path(@node.id) %>"><%= @node.name %></a></h2>
5
+ <% if ["new"].index params[:action] %>
6
+ <p class="sumamry">
7
+ <%= @node.summary %>
8
+ </p>
9
+ <% end %>
10
+ <div class="content">
11
+ <a class="btn success" href="<%= new_topic_path(:node => @node.id) %>">发布新帖</a>
12
+ </div>
13
+ </div>
14
+ <% elsif params[:action] == "index" %>
15
+ <div class="box">
16
+ <h2>社区论坛</h2>
17
+ <p>
18
+ 发贴请先进入一个节点以后点击“发布新帖”。
19
+ </p>
20
+ <p style="text-align:center;">
21
+ <a href="<%= homeland.feed_topics_url %>" title="用RSS阅读器订阅<%= Setting.app_name %>论坛新贴">
22
+ <%= image_tag('feed.png', :alt => "订阅#{Setting.app_name}论坛新贴") %></a>
23
+ </p>
24
+ </div>
25
+ <% end %>
26
+ <%= cache("topic/init_list_sidebar/hot_nodes",:expires_in => 30.minutes) do %>
27
+ <% if not @hot_nodes.blank? %>
28
+ <div class="hot_nodes nodes box">
29
+ <h2>热门节点</h2>
30
+ <p>
31
+ <% @hot_nodes.each do |node| %>
32
+ <a href="<%= node_topics_path(:id => node.id) %>"><%= node.name %></a>
33
+ <% end %>
34
+ </p>
35
+ </div>
36
+ <% end %>
37
+ <% end %>
38
+ <% if current_user and (not @user_last_nodes.blank?) %>
39
+ <div class="user_last_nodes nodes box">
40
+ <h2>最近访问节点</h2>
41
+ <p>
42
+ <% @user_last_nodes.each do |node| %>
43
+ <a href="<%= node_topics_path(:id => node.id) %>"><%= node.name %></a>
44
+ <% end %>
45
+ </p>
46
+ </div>
47
+ <% end %>
48
+ <% if ["new"].index(params[:action]) %>
49
+ <div id="topic_new_tip" class="box">
50
+ <h2>发贴说明</h2>
51
+ <ol>
52
+ <li>发布后正文排版会自动安你在文本框里面输入的样子排列.如果里面带有 URL地址,将会自动转变为超级链接。</li>
53
+ </ol>
54
+ </div>
55
+ <% end %>
56
+ <% if params[:action] == "index" %>
57
+ <div class="totals box">
58
+ <h2>统计信息</h2>
59
+ <ul>
60
+ <li>贴子数: <%= Homeland::Topic.count %> 篇</li>
61
+ <li>回贴数: <%= Homeland::Reply.count %> 条</li>
62
+ </ul>
63
+ </div>
64
+ <% end %>
65
+ </div>
@@ -0,0 +1,26 @@
1
+ <div class="topic topic_line">
2
+ <div class="pull-left avatar">
3
+ <%= user_avatar_tag(topic.user) %>
4
+ </div>
5
+ <div class="right_info">
6
+ <div class="pull-right replies">
7
+ <a href="<%= topic_path(topic.id) %>#reply<%= topic.replies_count %>" class="count">
8
+ <%= topic.replies_count %>
9
+ </a>
10
+ </div>
11
+ <div class="infos">
12
+ <div class="title"><a href="<%= topic_path(topic) %>" title="<%= topic.title %>"><%= truncate(topic.title, :length => 100) %></a></div>
13
+ <div class="info leader">
14
+ 由 <%= user_name_tag(topic.user) %>
15
+ 在 <a href="<%= node_topics_path(topic.node_id) %>"><%= topic.node.name %></a> 中发起
16
+ </div>
17
+ <div class="info time">
18
+ <% if topic.last_reply_user.blank? %>
19
+ 发布于 <%= timeago(topic.created_at) %>
20
+ <% else %>
21
+ 最后由 <%= user_name_tag(topic.last_reply_user) %> 回复于 <%= timeago(topic.replied_at) %>
22
+ <% end %>
23
+ </div>
24
+ </div>
25
+ </div>
26
+ </div>
@@ -0,0 +1,11 @@
1
+ <%= render 'base' %>
2
+ <% content_for :breadcrumb do %>
3
+ <li class="active">修改</li>
4
+ <% end %>
5
+
6
+ <%= render 'sidebar' %>
7
+ <div class="content">
8
+ <div class="box">
9
+ <%= render 'form' %>
10
+ </div>
11
+ </div>
@@ -0,0 +1,20 @@
1
+
2
+ xml.instruct! :xml, :version=>"1.0"
3
+ xml.rss(:version=>"2.0"){
4
+ xml.channel{
5
+ xml.title "#{Setting.app_name}"
6
+ xml.link root_url
7
+ xml.description("#{Setting.app_name}最新发贴.")
8
+ xml.language('en-us')
9
+ for topic in @topics
10
+ xml.item do
11
+ xml.title topic.title
12
+ xml.description format_topic_body(topic.body)
13
+ xml.author topic.user.name
14
+ xml.pubDate(topic.created_at.strftime("%a, %d %b %Y %H:%M:%S %z"))
15
+ xml.link topic_url topic
16
+ xml.guid topic_url topic
17
+ end
18
+ end
19
+ }
20
+ }
@@ -0,0 +1,51 @@
1
+ <%= render 'base' %>
2
+ <% content_for :breadcrumb do %>
3
+ <% if @node.blank? %>
4
+ <li>首页</li>
5
+ <% else %>
6
+ <li><%= @node.name %></li>
7
+ <% end %>
8
+ <% end %>
9
+ <%= render 'sidebar' %>
10
+
11
+ <div class="content">
12
+ <div class="box">
13
+ <% if !@node.blank? %>
14
+ <div id="node_info">
15
+ <p>
16
+ <h1><%= @node.name %></h1>
17
+ <span class="total">共有 <%= @node.topics_count %> 个讨论主题</span>
18
+ </p>
19
+ </div>
20
+ <% else %>
21
+ <h1><%= Setting.app_name %></h1>
22
+ <% end %>
23
+ <div class="topics">
24
+ <%= render :partial => "topic", :collection => @topics %>
25
+ </div>
26
+
27
+ <% if params[:action] == "index" %>
28
+ <div class="more">
29
+ <a href="<%= recent_topics_path %>">查看更多最近发布的帖子...</a>
30
+ </div>
31
+ <% elsif params[:action] == "node" or params[:action] == "search" %>
32
+ <%= will_paginate @topics %>
33
+ <% end %>
34
+ </div>
35
+ <% if params[:action] == "index" %>
36
+ <div id="sections" class="box">
37
+ <h2>节点分类导航</h2>
38
+ <ul>
39
+ <% @sections.each do |section| %>
40
+ <li>
41
+ <label><%= section.name %></label>
42
+ <% section.nodes.each do |node| %>
43
+ <a href="<%= node_topics_path(node.id) %>" title="<%= node.name %>"><%= node.name %></a>
44
+ <% end %>
45
+ </li>
46
+ <% end %>
47
+ </ul>
48
+ </div>
49
+ <% end %>
50
+ </div>
51
+
@@ -0,0 +1,11 @@
1
+ <%= render 'base' %>
2
+ <% content_for :breadcrumb do %>
3
+ <li class="active">新建贴子</li>
4
+ <% end %>
5
+
6
+ <%= render 'sidebar' %>
7
+ <div class="content">
8
+ <div class="box">
9
+ <%= render 'form' %>
10
+ </div>
11
+ </div>
@@ -0,0 +1,88 @@
1
+ <%= render 'base' %>
2
+ <%= content_for :breadcrumb do %>
3
+ <li class="active">查看贴子</li>
4
+ <% end %>
5
+ <%= render 'sidebar' %>
6
+ <div class="content">
7
+ <div class="topics box">
8
+ <div class="topic">
9
+ <div class="pull-right avatar_large">
10
+ <%= user_avatar_tag(@topic.user) %>
11
+ </div>
12
+ <div class="infos">
13
+ <h1><%= truncate(@topic.title, :length => 100) %></h1>
14
+ <div class="info leader">
15
+ 由 <%= user_name_tag(@topic.user) %>
16
+ 在 <a href="<%= node_topics_path(@topic.node_id) %>"><%= @topic.node.name %></a> 节点中发起
17
+ </div>
18
+ <div class="info time">
19
+ <% if @topic.last_reply_user.blank? %>
20
+ 发布于 <%= timeago(@topic.created_at) %>
21
+ <% else %>
22
+ 最后由 <%= user_name_tag(@topic.last_reply_user) %> 回复于 <%= timeago(@topic.replied_at) %>
23
+ <% end %>
24
+ , <%= @topic.hits %> 次阅读
25
+ </div>
26
+ </div>
27
+ </div>
28
+ <div class="body">
29
+ <%= format_topic_body(@topic.body) %>
30
+ </div>
31
+ <div class="tools">
32
+ <% if owner?(@topic) %>
33
+ <a href="<%= edit_topic_path(@topic.id) %>">修改</a>
34
+ <%= link_to "删除", topic_path(@topic.id), :method => :delete, :confirm => "确定要删除么?" %>
35
+ <% end %>
36
+ </div>
37
+ </div>
38
+ <% if @replies.blank? %>
39
+ <div class="no_result">
40
+ 暂无回复。
41
+ </div>
42
+ <% else %>
43
+ <div id="replies" class="box">
44
+ <div class="total">
45
+ 截止 <%= l @topic.replied_at, :format => :long %>, 共收到 <%= @topic.replies_count %> 条回复
46
+ </div>
47
+ <% @replies.each_with_index do |reply,i| %>
48
+ <div class="reply" id="reply<%= i + 1 %>">
49
+ <div class="pull-left face"><%= user_avatar_tag(reply.user) %></div>
50
+ <div class="infos">
51
+ <div class="info clearfix">
52
+ <span class="name"><%= user_name_tag(reply.user) %></span>
53
+ <span class="time"><%= i + 1 %>楼, 回复于 <%= timeago(reply.created_at) %>
54
+ <% if owner?(reply) %>
55
+ <a href="<%= edit_reply_path(reply.id) %>">修改</a>
56
+ <% end %>
57
+ </span>
58
+ </div>
59
+ <div class="body">
60
+ <%= format_topic_body(reply.body) %>
61
+ </div>
62
+ </div>
63
+ </div>
64
+ <% end %>
65
+ </div>
66
+ <% end %>
67
+ <% if current_user %>
68
+ <div id="reply" class="form box">
69
+ <h4>回复</h4>
70
+ <%= form_for(Homeland::Reply.new,:url => reply_topic_path(params[:id])) do |f| %>
71
+ <div class="pull-right">
72
+ <button type="submit" class="btn primary">提交回复</button><br />
73
+ <span class="help-inline">Ctrl+Enter</span>
74
+ </div>
75
+ <div class="clearfix">
76
+ <%= f.text_area :body,:class => "span10", :rows => "4" %>
77
+ </div>
78
+
79
+ <% end %>
80
+ </div>
81
+ <% else %>
82
+ <div id="reply" class="form box">
83
+ <div style="padding:20px;">
84
+ 需要 <a href='/account/sign_in' class="btn primary">登录</a> 后回复方可回复, 如果你还没有账号 <a href="/account/sign_up" class="btn danger"> 点击这里注册</a>。
85
+ </div>
86
+ </div>
87
+ <% end %>
88
+ </div>
data/config/routes.rb ADDED
@@ -0,0 +1,16 @@
1
+ Homeland::Engine.routes.draw do
2
+ root :to => "topics#index"
3
+ match "topics/node_:id", :to => "topics#node", :as => :node_topics
4
+ resources :topics do
5
+ member do
6
+ post :reply
7
+ end
8
+ collection do
9
+ get :node
10
+ get :search
11
+ get :feed
12
+ get :recent
13
+ end
14
+ end
15
+ resources :replies
16
+ end
@@ -0,0 +1,8 @@
1
+ # Homeland.app_name = "<%= Homeland.app_name %>"
2
+ # Homeland.user_class = "<%= Homeland.user_class %>"
3
+ # Homeland.current_user_method = :<%= Homeland.current_user_method %>
4
+ # Homeland.require_user_method = :<%= Homeland.require_user_method %>
5
+ # Homeland.user_avatar_method = :<%= Homeland.user_avatar_method %>
6
+ # Homeland.user_path_method = :<%= Homeland.user_path_method %>
7
+ # Homeland.per_page = <%= Homeland.per_page.inspect %>
8
+
@@ -0,0 +1,99 @@
1
+ /*
2
+ * jQuery Hotkeys Plugin
3
+ * Copyright 2010, John Resig
4
+ * Dual licensed under the MIT or GPL Version 2 licenses.
5
+ *
6
+ * Based upon the plugin by Tzury Bar Yochay:
7
+ * http://github.com/tzuryby/hotkeys
8
+ *
9
+ * Original idea by:
10
+ * Binny V A, http://www.openjs.com/scripts/events/keyboard_shortcuts/
11
+ */
12
+
13
+ (function(jQuery){
14
+
15
+ jQuery.hotkeys = {
16
+ version: "0.8",
17
+
18
+ specialKeys: {
19
+ 8: "backspace", 9: "tab", 13: "return", 16: "shift", 17: "ctrl", 18: "alt", 19: "pause",
20
+ 20: "capslock", 27: "esc", 32: "space", 33: "pageup", 34: "pagedown", 35: "end", 36: "home",
21
+ 37: "left", 38: "up", 39: "right", 40: "down", 45: "insert", 46: "del",
22
+ 96: "0", 97: "1", 98: "2", 99: "3", 100: "4", 101: "5", 102: "6", 103: "7",
23
+ 104: "8", 105: "9", 106: "*", 107: "+", 109: "-", 110: ".", 111 : "/",
24
+ 112: "f1", 113: "f2", 114: "f3", 115: "f4", 116: "f5", 117: "f6", 118: "f7", 119: "f8",
25
+ 120: "f9", 121: "f10", 122: "f11", 123: "f12", 144: "numlock", 145: "scroll", 191: "/", 224: "meta"
26
+ },
27
+
28
+ shiftNums: {
29
+ "`": "~", "1": "!", "2": "@", "3": "#", "4": "$", "5": "%", "6": "^", "7": "&",
30
+ "8": "*", "9": "(", "0": ")", "-": "_", "=": "+", ";": ": ", "'": "\"", ",": "<",
31
+ ".": ">", "/": "?", "\\": "|"
32
+ }
33
+ };
34
+
35
+ function keyHandler( handleObj ) {
36
+ // Only care when a possible input has been specified
37
+ if ( typeof handleObj.data !== "string" ) {
38
+ return;
39
+ }
40
+
41
+ var origHandler = handleObj.handler,
42
+ keys = handleObj.data.toLowerCase().split(" ");
43
+
44
+ handleObj.handler = function( event ) {
45
+ // Don't fire in text-accepting inputs that we didn't directly bind to
46
+ if ( this !== event.target && (/textarea|select/i.test( event.target.nodeName ) ||
47
+ event.target.type === "text") ) {
48
+ return;
49
+ }
50
+
51
+ // Keypress represents characters, not special keys
52
+ var special = event.type !== "keypress" && jQuery.hotkeys.specialKeys[ event.which ],
53
+ character = String.fromCharCode( event.which ).toLowerCase(),
54
+ key, modif = "", possible = {};
55
+
56
+ // check combinations (alt|ctrl|shift+anything)
57
+ if ( event.altKey && special !== "alt" ) {
58
+ modif += "alt+";
59
+ }
60
+
61
+ if ( event.ctrlKey && special !== "ctrl" ) {
62
+ modif += "ctrl+";
63
+ }
64
+
65
+ // TODO: Need to make sure this works consistently across platforms
66
+ if ( event.metaKey && !event.ctrlKey && special !== "meta" ) {
67
+ modif += "meta+";
68
+ }
69
+
70
+ if ( event.shiftKey && special !== "shift" ) {
71
+ modif += "shift+";
72
+ }
73
+
74
+ if ( special ) {
75
+ possible[ modif + special ] = true;
76
+
77
+ } else {
78
+ possible[ modif + character ] = true;
79
+ possible[ modif + jQuery.hotkeys.shiftNums[ character ] ] = true;
80
+
81
+ // "$" can be triggered as "Shift+4" or "Shift+$" or just "$"
82
+ if ( modif === "shift+" ) {
83
+ possible[ jQuery.hotkeys.shiftNums[ character ] ] = true;
84
+ }
85
+ }
86
+
87
+ for ( var i = 0, l = keys.length; i < l; i++ ) {
88
+ if ( possible[ keys[i] ] ) {
89
+ return origHandler.apply( this, arguments );
90
+ }
91
+ }
92
+ };
93
+ }
94
+
95
+ jQuery.each([ "keydown", "keyup", "keypress" ], function() {
96
+ jQuery.event.special[ this ] = { add: keyHandler };
97
+ });
98
+
99
+ })( jQuery );