thredded 0.0.1

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 (104) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.mkdn +46 -0
  4. data/Rakefile +31 -0
  5. data/app/assets/javascripts/thredded/application.js +15 -0
  6. data/app/assets/stylesheets/thredded/application.css +13 -0
  7. data/app/controllers/thredded/application_controller.rb +50 -0
  8. data/app/controllers/thredded/messageboards_controller.rb +9 -0
  9. data/app/controllers/thredded/posts_controller.rb +86 -0
  10. data/app/controllers/thredded/preferences_controller.rb +20 -0
  11. data/app/controllers/thredded/private_topics_controller.rb +25 -0
  12. data/app/controllers/thredded/setups_controller.rb +39 -0
  13. data/app/controllers/thredded/topics_controller.rb +121 -0
  14. data/app/decorators/thredded/post_decorator.rb +52 -0
  15. data/app/decorators/thredded/topic_decorator.rb +96 -0
  16. data/app/helpers/thredded/application_helper.rb +9 -0
  17. data/app/helpers/thredded/messageboard_helper.rb +52 -0
  18. data/app/helpers/thredded/posts_helper.rb +7 -0
  19. data/app/helpers/thredded/topics_helper.rb +21 -0
  20. data/app/mailers/thredded/post_mailer.rb +28 -0
  21. data/app/mailers/thredded/topic_mailer.rb +29 -0
  22. data/app/models/thredded/ability.rb +50 -0
  23. data/app/models/thredded/attachment.rb +24 -0
  24. data/app/models/thredded/category.rb +10 -0
  25. data/app/models/thredded/image.rb +27 -0
  26. data/app/models/thredded/messageboard.rb +92 -0
  27. data/app/models/thredded/messageboard_decorator.rb +18 -0
  28. data/app/models/thredded/messageboard_preference.rb +15 -0
  29. data/app/models/thredded/null_preference.rb +9 -0
  30. data/app/models/thredded/null_topic_read.rb +13 -0
  31. data/app/models/thredded/null_user.rb +27 -0
  32. data/app/models/thredded/post.rb +75 -0
  33. data/app/models/thredded/post_notification.rb +6 -0
  34. data/app/models/thredded/private_topic.rb +33 -0
  35. data/app/models/thredded/private_user.rb +7 -0
  36. data/app/models/thredded/role.rb +18 -0
  37. data/app/models/thredded/topic.rb +145 -0
  38. data/app/models/thredded/topic_category.rb +7 -0
  39. data/app/models/thredded/user_detail.rb +6 -0
  40. data/app/models/thredded/user_extender.rb +27 -0
  41. data/app/models/thredded/user_preference.rb +6 -0
  42. data/app/models/thredded/user_topic_read.rb +67 -0
  43. data/app/uploaders/thredded/attachment_uploader.rb +30 -0
  44. data/app/uploaders/thredded/image_uploader.rb +17 -0
  45. data/app/views/thredded/messageboards/_messageboard.html.erb +17 -0
  46. data/app/views/thredded/messageboards/index.html.erb +15 -0
  47. data/app/views/thredded/post_mailer/at_notification.html.erb +17 -0
  48. data/app/views/thredded/post_mailer/at_notification.text.erb +13 -0
  49. data/app/views/thredded/posts/_form.html.erb +25 -0
  50. data/app/views/thredded/posts/_null_user.html.erb +0 -0
  51. data/app/views/thredded/posts/_post.html.erb +25 -0
  52. data/app/views/thredded/posts/_user.html.erb +7 -0
  53. data/app/views/thredded/posts/edit.html.erb +28 -0
  54. data/app/views/thredded/posts/index.html.erb +74 -0
  55. data/app/views/thredded/preferences/edit.html.erb +23 -0
  56. data/app/views/thredded/search/_form.html.erb +9 -0
  57. data/app/views/thredded/setups/_form.html.erb +35 -0
  58. data/app/views/thredded/setups/new.html.erb +5 -0
  59. data/app/views/thredded/shared/_currently_online.html.erb +10 -0
  60. data/app/views/thredded/shared/_error_msgs.html.erb +7 -0
  61. data/app/views/thredded/shared/_google_analytics.html.erb +10 -0
  62. data/app/views/thredded/shared/_topic_nav.html.erb +18 -0
  63. data/app/views/thredded/topic_mailer/message_notification.html.erb +17 -0
  64. data/app/views/thredded/topic_mailer/message_notification.text.erb +13 -0
  65. data/app/views/thredded/topics/_form.html.erb +6 -0
  66. data/app/views/thredded/topics/_topic_condensed.html.erb +20 -0
  67. data/app/views/thredded/topics/_topic_form.html.erb +27 -0
  68. data/app/views/thredded/topics/by_category.html.erb +51 -0
  69. data/app/views/thredded/topics/edit.html.erb +25 -0
  70. data/app/views/thredded/topics/index.html.erb +52 -0
  71. data/app/views/thredded/topics/menu/_new_topic.html.erb +3 -0
  72. data/app/views/thredded/topics/new.html.erb +18 -0
  73. data/app/views/thredded/topics/search.html.erb +45 -0
  74. data/config/routes.rb +26 -0
  75. data/db/migrate/20130425230852_create_thredded_tables.rb +143 -0
  76. data/lib/tasks/thredded_tasks.rake +4 -0
  77. data/lib/thredded.rb +38 -0
  78. data/lib/thredded/at_notification_extractor.rb +15 -0
  79. data/lib/thredded/at_notifier.rb +65 -0
  80. data/lib/thredded/at_users.rb +17 -0
  81. data/lib/thredded/email_processor.rb +74 -0
  82. data/lib/thredded/engine.rb +17 -0
  83. data/lib/thredded/filter.rb +4 -0
  84. data/lib/thredded/filter/at_notification.rb +11 -0
  85. data/lib/thredded/filter/attachment.rb +51 -0
  86. data/lib/thredded/filter/base.rb +13 -0
  87. data/lib/thredded/filter/bbcode.rb +75 -0
  88. data/lib/thredded/filter/emoji.rb +11 -0
  89. data/lib/thredded/filter/markdown.rb +25 -0
  90. data/lib/thredded/filter/syntax.rb +28 -0
  91. data/lib/thredded/filter/textile.rb +21 -0
  92. data/lib/thredded/messageboard_user_permissions.rb +27 -0
  93. data/lib/thredded/post_sql_builder.rb +34 -0
  94. data/lib/thredded/post_user_permissions.rb +37 -0
  95. data/lib/thredded/private_topic_notifier.rb +56 -0
  96. data/lib/thredded/private_topic_user_permissions.rb +32 -0
  97. data/lib/thredded/search_parser.rb +45 -0
  98. data/lib/thredded/search_sql_builder.rb +25 -0
  99. data/lib/thredded/setup_thredded.rb +7 -0
  100. data/lib/thredded/table_sql_builder.rb +105 -0
  101. data/lib/thredded/topic_sql_builder.rb +32 -0
  102. data/lib/thredded/topic_user_permissions.rb +51 -0
  103. data/lib/thredded/version.rb +3 -0
  104. metadata +537 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: dbf9d4e29edeea27a73efed02c03710eaa1f7530
4
+ data.tar.gz: a3c14e476dbbe413c60dc52fce7c712152cd5743
5
+ SHA512:
6
+ metadata.gz: 07f3f002cf28edd4d5a7a30b2f10651eac57327ce22a28481aa07167922a7ad89872df0e1b8b33693b1ce52b034830212f1a6677aed5bb61138c67914f2bba36
7
+ data.tar.gz: 3d03b68a94ecb9ce235ea772545c930c1b9ed62ea3e2bcbb353bf171a1dd8f63b13c79a46f65ab6d266b81ab6dd48a87478edeb5401fdd4422083140afe8d00a
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2013 YOURNAME
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.mkdn ADDED
@@ -0,0 +1,46 @@
1
+ # Thredded (the gem / rails engine)
2
+
3
+ ## Installation
4
+
5
+ Add the gem to your Gemfile:
6
+
7
+ ```ruby
8
+ gem 'thredded'
9
+ ```
10
+
11
+ Add an initializer to your app: `config/initializers/thredded.rb`
12
+
13
+ ```ruby
14
+ Thredded.user_class = 'User'
15
+ Thredded.email_incoming_host = 'incoming.example.com'
16
+ Thredded.email_from = 'no-reply@example.com'
17
+ Thredded.email_outgoing_prefix = '[Thredded] '
18
+ ```
19
+
20
+ Copy the migrations over to your parent application and migrate:
21
+
22
+ ```
23
+ rake thredded:install:migrations db:migrate db:test:prepare
24
+ ```
25
+
26
+ Mount the thredded engine in your routes file:
27
+
28
+ ```
29
+ mount Thredded::Engine => '/forum'
30
+ ```
31
+
32
+ ## Get Your App Ready
33
+
34
+ There are a few things you need in your app to get things looking just right.
35
+
36
+ 1. Add a to_s method to your user model. The following example assumes a column in my user model called `name`:
37
+
38
+ ```ruby
39
+ class User < ActiveRecord::Base
40
+ def to_s
41
+ name
42
+ end
43
+ end
44
+ ```
45
+
46
+ For more information see [the full-fledged rails app](https://github.com/jayroh/thredded_app)
data/Rakefile ADDED
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+
8
+ begin
9
+ require 'rdoc/task'
10
+ rescue LoadError
11
+ require 'rdoc/rdoc'
12
+ require 'rake/rdoctask'
13
+ RDoc::Task = Rake::RDocTask
14
+ end
15
+
16
+ RDoc::Task.new(:rdoc) do |rdoc|
17
+ rdoc.rdoc_dir = 'rdoc'
18
+ rdoc.title = 'Thredded'
19
+ rdoc.options << '--line-numbers'
20
+ rdoc.rdoc_files.include('README.rdoc')
21
+ rdoc.rdoc_files.include('lib/**/*.rb')
22
+ end
23
+
24
+ APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
25
+ load 'rails/tasks/engine.rake'
26
+
27
+ require 'rspec/core/rake_task'
28
+ RSpec::Core::RakeTask.new(:spec)
29
+ task default: :spec
30
+
31
+ Bundler::GemHelper.install_tasks
@@ -0,0 +1,15 @@
1
+ // This is a manifest file that'll be compiled into application.js, which will include all the files
2
+ // listed below.
3
+ //
4
+ // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5
+ // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
6
+ //
7
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8
+ // the compiled file.
9
+ //
10
+ // WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
11
+ // GO AFTER THE REQUIRES BELOW.
12
+ //
13
+ //= require jquery
14
+ //= require jquery_ujs
15
+ //= require_tree .
@@ -0,0 +1,13 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the top of the
9
+ * compiled file, but it's generally better to create a new file per style scope.
10
+ *
11
+ *= require_self
12
+ *= require_tree .
13
+ */
@@ -0,0 +1,50 @@
1
+ module Thredded
2
+ class ApplicationController < ::ApplicationController
3
+ helper Thredded::Engine.helpers
4
+ helper_method :messageboard, :topic, :preferences
5
+
6
+ rescue_from CanCan::AccessDenied do |exception|
7
+ flash[:error] = exception.message
8
+ redirect_to root_path
9
+ end
10
+
11
+ private
12
+
13
+ def current_ability
14
+ @current_ability ||= Ability.new(current_user)
15
+ end
16
+
17
+ def messageboard
18
+ if params.key? :messageboard_id
19
+ @messageboard ||= Messageboard.find(params[:messageboard_id])
20
+ end
21
+ end
22
+
23
+ def preferences
24
+ if current_user
25
+ @preferences ||= UserPreference.where(user_id: current_user.id).first
26
+ end
27
+ end
28
+
29
+ def topic
30
+ if messageboard
31
+ @topic ||= messageboard.topics.find(params[:topic_id])
32
+ end
33
+ end
34
+
35
+ def ensure_messageboard_exists
36
+ if messageboard.blank?
37
+ redirect_to thredded.root_path,
38
+ flash: { error: 'This messageboard does not exist.' }
39
+ end
40
+ end
41
+
42
+ def user_messageboard_preferences
43
+ @user_messageboard_preferences ||=
44
+ current_user
45
+ .thredded_messageboard_preferences
46
+ .where(messageboard_id: messageboard)
47
+ .first
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,9 @@
1
+ module Thredded
2
+ class MessageboardsController < ApplicationController
3
+ before_filter :messageboard, only: :show
4
+
5
+ def index
6
+ @messageboards = Messageboard.where(closed: false)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,86 @@
1
+ module Thredded
2
+ class PostsController < ApplicationController
3
+ include TopicsHelper
4
+ load_and_authorize_resource only: [:index, :show]
5
+ before_filter :ensure_topic_exists
6
+ before_filter :pad_post, only: :create
7
+ helper_method :messageboard, :topic
8
+
9
+ def index
10
+ authorize! :read, topic
11
+
12
+ @post = messageboard.posts.build(topic: topic, filter: post_filter)
13
+ @posts = Post.where(topic_id: topic).page(page)
14
+ @read_status = UserTopicRead
15
+ .find_or_create_by_user_and_topic(current_user, topic, page)
16
+
17
+ if not_inside_topic_and_in_an_old_page?
18
+ redirect_to_later_page and return
19
+ else
20
+ UserTopicRead.update_read_status!(current_user, topic, page)
21
+ end
22
+ end
23
+
24
+ def create
25
+ topic.posts.create(params[:post])
26
+ redirect_to :back
27
+ end
28
+
29
+ def edit
30
+ authorize! :manage, post
31
+ end
32
+
33
+ def update
34
+ post.update_attributes(params[:post])
35
+ redirect_to messageboard_topic_posts_url(messageboard, topic)
36
+ end
37
+
38
+ private
39
+
40
+ def topic
41
+ @topic ||= messageboard.topics.find(params[:topic_id])
42
+ end
43
+
44
+ def post
45
+ @post ||= topic.posts.find(params[:id])
46
+ end
47
+
48
+ def post_filter
49
+ user_messageboard_preferences.try(:filter) || :markdown
50
+ end
51
+
52
+ def not_inside_topic_and_in_an_old_page?
53
+ !internal_to_topic? && page == 1 && @read_status.page > 1
54
+ end
55
+
56
+ def page
57
+ params[:page].nil? ? 1 : params[:page].to_i
58
+ end
59
+
60
+ def extra_data
61
+ %Q{data-latest-read=#{@read_status.post_id || 0}} if @read_status
62
+ end
63
+
64
+ def internal_to_topic?
65
+ referer = request.referer || ''
66
+ referer.include?("#{topic.id}?page=") || (!topic.slug.nil? && referer.include?("#{topic.slug}?page="))
67
+ end
68
+
69
+ def redirect_to_later_page
70
+ redirect_to messageboard_topic_posts_path(messageboard, topic, page: @read_status.page)
71
+ end
72
+
73
+ def pad_post
74
+ params[:post][:ip] = request.remote_ip
75
+ params[:post][:user] = current_user
76
+ params[:post][:messageboard] = messageboard
77
+ end
78
+
79
+ def ensure_topic_exists
80
+ if topic.blank?
81
+ redirect_to default_home,
82
+ flash: { error: 'This topic does not exist.' }
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,20 @@
1
+ module Thredded
2
+ class PreferencesController < ApplicationController
3
+ helper_method :preference
4
+
5
+ def edit
6
+ end
7
+
8
+ def update
9
+ preference.update_attributes(params[:messageboard_preference])
10
+
11
+ redirect_to :back, flash: { notice: 'Your preferences are updated' }
12
+ end
13
+
14
+ def preference
15
+ @preference ||= MessageboardPreference
16
+ .where(messageboard_id: messageboard.id, user_id: current_user.id)
17
+ .first_or_create!
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,25 @@
1
+ module Thredded
2
+ class PrivateTopicsController < ApplicationController
3
+ before_filter :ensure_messageboard_exists
4
+
5
+ def new
6
+ @private_topic = messageboard.private_topics.build
7
+ @private_topic.posts.build(
8
+ filter: current_user.try(:post_filter)
9
+ )
10
+
11
+ unless can? :create, @private_topic
12
+ error = 'Sorry, you are not authorized to post on this messageboard.'
13
+ redirect_to messageboard_topics_url(messageboard),
14
+ flash: { error: error }
15
+ end
16
+ end
17
+
18
+ def create
19
+ params[:topic][:user_id] << current_user.id
20
+ merge_default_topics_params
21
+ @private_topic = PrivateTopic.create(params[:topic])
22
+ redirect_to messageboard_topics_url(messageboard)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,39 @@
1
+ module Thredded
2
+ class SetupsController < ApplicationController
3
+ def new
4
+ @messageboard = Messageboard.new
5
+ end
6
+
7
+ def create
8
+ messageboard_params = params[:messageboard]
9
+ @messageboard = Messageboard.create(messageboard_params)
10
+
11
+ if @messageboard.valid?
12
+ @messageboard.add_member(current_user, 'admin')
13
+ @messageboard.topics.create(topic_params)
14
+
15
+ redirect_to root_path
16
+ else
17
+ render action: :new
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def topic_params
24
+ {
25
+ user: current_user,
26
+ last_user: current_user,
27
+ title: "Welcome to your messageboard's very first thread",
28
+ posts_attributes: {
29
+ '0' => {
30
+ content: "There's not a whole lot here for now.",
31
+ user: current_user,
32
+ ip: '127.0.0.1',
33
+ messageboard: @messageboard
34
+ }
35
+ }
36
+ }
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,121 @@
1
+ module Thredded
2
+ class TopicsController < ApplicationController
3
+ before_filter :ensure_messageboard_exists
4
+
5
+ def index
6
+ if cannot? :read, messageboard
7
+ error = 'You are not authorized access to this messageboard.'
8
+ redirect_to default_home, flash: { error: error }
9
+ end
10
+
11
+ @sticky = get_sticky_topics
12
+ @topics = get_topics
13
+ @tracked_user_reads = UserTopicRead.statuses_for(current_user, @topics)
14
+ end
15
+
16
+ def search
17
+ @topics = get_search_results
18
+ @tracked_user_reads = UserTopicRead.statuses_for(current_user, @topics)
19
+
20
+ if @topics.empty?
21
+ error = 'No topics found for this search.'
22
+ redirect_to messageboard_topics_path(messageboard), flash: { error: error }
23
+ end
24
+ end
25
+
26
+ def new
27
+ @topic = messageboard.topics.build
28
+ @topic.posts.build( filter: user_messageboard_preferences.try(:filter) )
29
+
30
+ unless can? :create, @topic
31
+ error = 'Sorry, you are not authorized to post on this messageboard.'
32
+ redirect_to messageboard_topics_url(messageboard),
33
+ flash: { error: error }
34
+ end
35
+ end
36
+
37
+ def by_category
38
+ @sticky = get_sticky_topics
39
+ @topics = get_topics_by_category params[:category_id]
40
+ @category_name = Category.find(params[:category_id]).name
41
+ end
42
+
43
+ def create
44
+ @topic = messageboard.topics.create(topic_params)
45
+ redirect_to messageboard_topics_path(messageboard)
46
+ end
47
+
48
+ def edit
49
+ authorize! :update, topic
50
+ end
51
+
52
+ def update
53
+ params.deep_merge!({
54
+ topic: {
55
+ user: current_user,
56
+ last_user: current_user
57
+ }
58
+ })
59
+
60
+ topic.update_attributes(params[:topic])
61
+ redirect_to messageboard_topic_posts_url(messageboard, topic)
62
+ end
63
+
64
+ private
65
+
66
+ def topic
67
+ if messageboard
68
+ @topic ||= messageboard.topics.find(params[:id])
69
+ end
70
+ end
71
+
72
+ def topic_params
73
+ params[:topic].deep_merge!({
74
+ last_user: current_user,
75
+ user: current_user,
76
+ posts_attributes: {
77
+ '0' => {
78
+ messageboard: messageboard,
79
+ ip: request.remote_ip,
80
+ user: current_user,
81
+ }
82
+ }
83
+ })
84
+ end
85
+
86
+ def get_search_results
87
+ Topic.full_text_search(params[:q], messageboard)
88
+ end
89
+
90
+ def get_topics_by_category(category_id)
91
+ topics = Category.find(category_id)
92
+ .topics
93
+ .unstuck
94
+ .for_messageboard(messageboard)
95
+ .order_by_updated
96
+ .on_page(params[:page])
97
+ end
98
+
99
+ def get_topics
100
+ Topic
101
+ .unstuck
102
+ .for_messageboard(messageboard)
103
+ .order_by_updated.on_page(params[:page])
104
+ end
105
+
106
+ def get_sticky_topics
107
+ if on_first_topics_page?
108
+ Topic
109
+ .stuck
110
+ .for_messageboard(messageboard)
111
+ .order('id DESC')
112
+ else
113
+ []
114
+ end
115
+ end
116
+
117
+ def on_first_topics_page?
118
+ params[:page].nil? || params[:page] == '1'
119
+ end
120
+ end
121
+ end