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.
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 );