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
@@ -0,0 +1,15 @@
1
+ module Thredded
2
+ class AtNotificationExtractor
3
+ def initialize(content)
4
+ @content = content
5
+ end
6
+
7
+ def extract
8
+ scanned_names = @content.scan(/@([\w]+)(\W)?/)
9
+ scanned_names += @content.scan(/@"([\w\ ]+)"(\W)?/)
10
+ scanned_names
11
+ .map(&:first)
12
+ .uniq
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,65 @@
1
+ require 'thredded/at_notification_extractor'
2
+
3
+ module Thredded
4
+ class AtNotifier
5
+ def initialize(post)
6
+ @post = post
7
+ end
8
+
9
+ def notifications_for_at_users
10
+ members = at_notifiable_members
11
+
12
+ if members.present?
13
+ user_emails = members.map(&:email)
14
+ Thredded::PostMailer.at_notification(post.id, user_emails).deliver
15
+ mark_notified(members)
16
+ end
17
+ end
18
+
19
+ def at_notifiable_members
20
+ at_names = Thredded::AtNotificationExtractor.new(post.content).extract
21
+ members = post.messageboard.members_from_list(at_names).all
22
+
23
+ members.delete post.user
24
+ members = exclude_previously_notified(members)
25
+ members = exclude_those_that_are_not_private(members)
26
+ members = exclude_those_opting_out_of_at_notifications(members)
27
+
28
+ members
29
+ end
30
+
31
+ private
32
+
33
+ attr_reader :post
34
+
35
+ def exclude_those_opting_out_of_at_notifications(members)
36
+ members.reject do |member|
37
+ !Thredded::MessageboardPreference
38
+ .for(member)
39
+ .in(post.messageboard)
40
+ .first
41
+ .try(:notify_on_mention?)
42
+ end
43
+ end
44
+
45
+ def exclude_those_that_are_not_private(members)
46
+ members.reject do |member|
47
+ post.topic.private? && post.topic.users.exclude?(member)
48
+ end
49
+ end
50
+
51
+ def exclude_previously_notified(members)
52
+ emails_notified = post.post_notifications.map(&:email)
53
+
54
+ members.reject do |member|
55
+ emails_notified.include? member.email
56
+ end
57
+ end
58
+
59
+ def mark_notified(members)
60
+ members.each do |member|
61
+ post.post_notifications.create(email: member.email)
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,17 @@
1
+ require 'thredded/at_notification_extractor'
2
+
3
+ module Thredded
4
+ class AtUsers
5
+ def self.render(content, messageboard)
6
+ at_names = AtNotificationExtractor.new(content).extract
7
+ members = messageboard.members_from_list(at_names)
8
+
9
+ members.each do |member|
10
+ content.gsub!(/@#{member.name}/i,
11
+ %Q{<a href="/users/#{member.name}">@#{member.name}</a>})
12
+ end
13
+
14
+ content
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,74 @@
1
+ class EmailProcessor
2
+ attr_accessor :email, :messageboard, :user
3
+
4
+ def initialize(email)
5
+ @email = email
6
+ @user = find_user
7
+ @messageboard = find_messageboard
8
+ end
9
+
10
+ def self.process(email)
11
+ processor = self.new(email)
12
+ processor.create_or_update_topic
13
+ end
14
+
15
+ def create_or_update_topic
16
+ if can_post_to_topic?
17
+ topic = find_or_build_topic
18
+ post = topic.posts.build(
19
+ user: user,
20
+ content: email.body,
21
+ source: 'email',
22
+ messageboard: messageboard,
23
+ attachments_attributes: attachment_params,
24
+ )
25
+ post.user_email = user.email
26
+
27
+ topic.save
28
+ else
29
+ return false
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def attachment_params
36
+ @attachment_params = {}
37
+
38
+ email.attachments.each_with_index do |attachment, i|
39
+ @attachment_params[i.to_s] = { 'attachment' => attachment }
40
+ end
41
+
42
+ @attachment_params
43
+ end
44
+
45
+ def can_post_to_topic?
46
+ user && messageboard &&
47
+ Ability.new(user).can?(:create, messageboard.topics.new)
48
+ end
49
+
50
+ def find_or_build_topic
51
+ topic = find_topic
52
+
53
+ if topic.nil?
54
+ topic = messageboard.topics.build(title: email.subject)
55
+ topic.user = user
56
+ topic.state = 'pending'
57
+ end
58
+
59
+ topic.last_user = user
60
+ topic
61
+ end
62
+
63
+ def find_topic
64
+ Topic.where(hash_id: email.to).first
65
+ end
66
+
67
+ def find_messageboard
68
+ Messageboard.where(name: email.to).first || find_topic.try(:messageboard)
69
+ end
70
+
71
+ def find_user
72
+ User.where(email: email.from).first
73
+ end
74
+ end
@@ -0,0 +1,17 @@
1
+ module Thredded
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace Thredded
4
+
5
+ config.autoload_paths << File.expand_path('../../../app/decorators', __FILE__)
6
+
7
+ config.generators do |g|
8
+ g.test_framework :rspec, fixture: true
9
+ g.fixture_replacement :factory_girl, dir: 'spec/factories'
10
+ g.helper false
11
+ end
12
+
13
+ config.after_initialize do
14
+ Thredded.user_class.send(:include, Thredded::UserExtender)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,4 @@
1
+ module Thredded
2
+ module Filter
3
+ end
4
+ end
@@ -0,0 +1,11 @@
1
+ require 'thredded/at_users'
2
+
3
+ module Thredded
4
+ module Filter
5
+ module AtNotification
6
+ def filtered_content
7
+ @filtered_content = Thredded::AtUsers.render(super, messageboard).html_safe
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,51 @@
1
+ module Thredded
2
+ module Filter
3
+ module Attachment
4
+ def filtered_content
5
+ content = super
6
+ matches = content.scan(/(?<full>\[t:(?<tag>\w+)=?(?<img_nmb>\d+)? ?(?<attribs>[^\]]+)?\])/)
7
+
8
+ matches.each do |match|
9
+ str_buff = ''
10
+ full = match[0]
11
+ tag = match[1]
12
+ img_number = match[2] ? match[2].to_i - 1 : 0 # default to first attachment
13
+ attribs = match[3]
14
+
15
+ if(tag != 'img' || !self.attachments[img_number])
16
+ next
17
+ end
18
+
19
+ # start with img tag
20
+ str_buff += '<img '
21
+
22
+ # get attachment object at spot img_number - 1
23
+ attachment = self.attachments[img_number]
24
+ str_buff += 'src="' + attachment.attachment.to_s + '" '
25
+
26
+ # do attribute stuff, left right first
27
+ if !attribs.nil?
28
+ if align = attribs.match(/(left|right)/)
29
+ str_buff += 'class="align_' + align[0] + '" '
30
+ end
31
+
32
+ # attributes, width x height
33
+ if wxh = attribs.match(/(\d+)x?(\d+)?/)
34
+ str_buff += 'width="' + wxh[1] + '" '
35
+ height = wxh[2].nil? ? wxh[1] : wxh[2]
36
+ str_buff += 'height="' + height + '" '
37
+ end
38
+ end
39
+
40
+ # end img tag
41
+ str_buff += '/>'
42
+
43
+ # replace in post content
44
+ content = content.sub(full, str_buff)
45
+ end
46
+
47
+ content.html_safe
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,13 @@
1
+ module Thredded
2
+ module Filter
3
+ module Base
4
+ Filters = []
5
+
6
+ def filters; Filters; end
7
+
8
+ def filtered_content
9
+ self.content
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,75 @@
1
+ module Thredded
2
+ module Filter
3
+ module Bbcode
4
+ require 'bb-ruby'
5
+
6
+ BB = {
7
+ 'Spoilers' => [
8
+ /\[spoiler\](.*?)\[\/spoiler\1?\]/mi,
9
+ '<blockquote class="spoiler">\1</blockquote>',
10
+ 'Spoiler Text',
11
+ '[spoiler]Dumbledore dies[/spoiler]',
12
+ :spoiler],
13
+ 'YouTube' => [
14
+ /\[youtube\]https?\:\/\/(www\.)?youtube.com\/((watch)?\?vi?=|embed\/)(.*?)\[\/youtube\1?\]/i,
15
+ '<iframe class="youtube" width="560" height="315" src="//www.youtube.com/embed/\4?&rel=0&theme=light&showinfo=0&hd=1&autohide=1&color=white" frameborder="0" allowfullscreen="allowfullscreen"></iframe>',
16
+ 'Youtube Video',
17
+ :video],
18
+ 'Link (Legacy)' => [
19
+ /\[link=(?:&quot;)?(.*?)(?:&quot;)?\](.*?)\[\/link\]/mi,
20
+ '<a href="\1">\2</a>',
21
+ 'Hyperlink to somewhere else',
22
+ 'Maybe try looking on [link=http://google.com]Google[/link]?',
23
+ :link],
24
+ 'Link (Legacy Implied)' => [
25
+ /\[link\](.*?)\[\/link\]/mi,
26
+ '<a href="\1">\1</a>',
27
+ 'Hyperlink (legacy implied)',
28
+ "Maybe try looking on [link]http://google.com[/link]",
29
+ :link],
30
+ }
31
+
32
+ def self.included(base)
33
+ base.class_eval do
34
+ Thredded::Post::Filters << :bbcode
35
+ end
36
+ end
37
+
38
+ def filtered_content
39
+ if filter.to_sym == :bbcode
40
+ content = super
41
+ content = replace_code_tags(content)
42
+ content = replace_quote_tags(content)
43
+ content = content.bbcode_to_html(BB)
44
+ content = remove_empty_p_tags(content)
45
+ content = CGI.unescapeHTML(content)
46
+
47
+ content.html_safe
48
+ else
49
+ super
50
+ end
51
+ end
52
+
53
+ def replace_code_tags(content)
54
+ content.gsub!(/\[code\]/, '<pre><code>')
55
+ content.gsub!(/\[code:(\D+?)\]/, '<pre><code class="language-\1" lang="\1">')
56
+ content.gsub!(/\[\/code\]/, '</code></pre>')
57
+ content.html_safe
58
+ end
59
+
60
+ def replace_quote_tags(content)
61
+ content.gsub!(/\[quote(:.*)?=(?:&quot;)?(.*?)(?:&quot;)?\]/,
62
+ '</p><fieldset><legend>\2</legend><blockquote><p>')
63
+ content.gsub!(/\[quote(:.*?)?\]/,
64
+ '</p><fieldset><blockquote><p>')
65
+ content.gsub!(/\[\/quote\]/,
66
+ '</p></blockquote></fieldset><p>')
67
+ content.html_safe
68
+ end
69
+
70
+ def remove_empty_p_tags(content)
71
+ content.gsub(/&lt;p&gt;\s*?&lt;\/p&gt;/, '')
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,11 @@
1
+ require 'rails_emoji'
2
+
3
+ module Thredded
4
+ module Filter
5
+ module Emoji
6
+ def filtered_content
7
+ @filtered_content = RailsEmoji.render(super, size: 26).html_safe
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,25 @@
1
+ require 'redcarpet'
2
+
3
+ module Thredded
4
+ module Filter
5
+ module Markdown
6
+ def self.included(base)
7
+ base.class_eval do
8
+ Thredded::Post::Filters << :markdown
9
+ end
10
+ end
11
+
12
+ def filtered_content
13
+ if filter.to_sym == :markdown
14
+ renderer = Redcarpet::Render::HTML.new(hard_wrap: true, filter_html: true)
15
+ markdown = Redcarpet::Markdown.new(renderer, autolink: true,
16
+ space_after_headers: true, no_intraemphasis: true,
17
+ fenced_code: true, gh_blockcode: true)
18
+ @filtered_content = markdown.render(super).html_safe
19
+ else
20
+ super
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,28 @@
1
+ require 'coderay'
2
+ require 'htmlentities'
3
+
4
+ module Thredded
5
+ module Filter
6
+ module Syntax
7
+ def filtered_content
8
+ content = String.new(super)
9
+ content = HTMLEntities.new.decode(content)
10
+
11
+ content = content.to_s
12
+ .gsub(/\<pre\>\<code( lang="(.+?)")?\>(.+?)\<\/code\>\<\/pre\>/m) do
13
+ filter = $2.nil? ? :ruby : $2.to_sym
14
+ temp_code = $3.gsub(/&quot;/, '"').
15
+ gsub(/&#39;/,"'").
16
+ gsub(/&amp;/, "&").
17
+ gsub(/&gt;/, ">").
18
+ gsub(/&lt;/, "<").
19
+ gsub(/\<br \/\>/, "")
20
+
21
+ ::CodeRay.scan(temp_code, filter).div(css: :class)
22
+ end
23
+
24
+ content.html_safe
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,21 @@
1
+ require 'redcloth'
2
+
3
+ module Thredded
4
+ module Filter
5
+ module Textile
6
+ def self.included(base)
7
+ base.class_eval do
8
+ Thredded::Post::Filters << :textile
9
+ end
10
+ end
11
+
12
+ def filtered_content
13
+ if filter.to_sym == :textile
14
+ @filtered_content = RedCloth.new(super).to_html.html_safe
15
+ else
16
+ @filtered_content = super
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end