thredded 0.1.0 → 0.2.2
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.
- checksums.yaml +4 -4
- data/CHANGELOG.mkdn +202 -0
- data/MIT-LICENSE +20 -0
- data/README.mkdn +391 -0
- data/app/assets/images/thredded/breadcrumb-chevron.svg +1 -0
- data/app/assets/javascripts/thredded/currently_online.es6 +25 -0
- data/app/assets/javascripts/thredded/post_form.es6 +20 -0
- data/app/assets/javascripts/thredded/time_stamps.es6 +8 -0
- data/app/assets/javascripts/thredded/topic_form.es6 +55 -0
- data/app/assets/javascripts/thredded/users_select.es6 +5 -0
- data/app/assets/javascripts/thredded.es6 +10 -0
- data/app/assets/stylesheets/thredded/_base.scss +11 -0
- data/app/assets/stylesheets/thredded/_dependencies.scss +1 -0
- data/app/assets/stylesheets/thredded/_thredded.scss +27 -0
- data/app/assets/stylesheets/thredded/base/_alerts.scss +40 -0
- data/app/assets/stylesheets/thredded/base/_buttons.scss +39 -0
- data/app/assets/stylesheets/thredded/base/_forms.scss +79 -0
- data/app/assets/stylesheets/thredded/base/_grid.scss +19 -0
- data/app/assets/stylesheets/thredded/base/_lists.scss +31 -0
- data/app/assets/stylesheets/thredded/base/_tables.scss +25 -0
- data/app/assets/stylesheets/thredded/base/_typography.scss +36 -0
- data/app/assets/stylesheets/thredded/base/_variables.scss +54 -0
- data/app/assets/stylesheets/thredded/components/_base.scss +13 -0
- data/app/assets/stylesheets/thredded/components/_currently-online.scss +48 -0
- data/app/assets/stylesheets/thredded/components/_empty.scss +11 -0
- data/app/assets/stylesheets/thredded/components/_flash-message.scss +19 -0
- data/app/assets/stylesheets/thredded/components/_form-list.scss +35 -0
- data/app/assets/stylesheets/thredded/components/_main-section.scss +3 -0
- data/app/assets/stylesheets/thredded/components/_messageboard.scss +55 -0
- data/app/assets/stylesheets/thredded/components/_pagination.scss +26 -0
- data/app/assets/stylesheets/thredded/components/_post-form.scss +21 -0
- data/app/assets/stylesheets/thredded/components/_post.scss +66 -0
- data/app/assets/stylesheets/thredded/components/_preferences.scss +6 -0
- data/app/assets/stylesheets/thredded/components/_select2.scss +42 -0
- data/app/assets/stylesheets/thredded/components/_topic-delete.scss +9 -0
- data/app/assets/stylesheets/thredded/components/_topic-header.scss +20 -0
- data/app/assets/stylesheets/thredded/components/_topics.scss +113 -0
- data/app/assets/stylesheets/thredded/layout/_main-container.scss +15 -0
- data/app/assets/stylesheets/thredded/layout/_main-navigation.scss +45 -0
- data/app/assets/stylesheets/thredded/layout/_topic-navigation.scss +53 -0
- data/app/assets/stylesheets/thredded/layout/_user-navigation.scss +87 -0
- data/app/assets/stylesheets/thredded/utilities/_is-compact.scss +26 -0
- data/app/assets/stylesheets/thredded/utilities/_is-expanded.scss +16 -0
- data/app/assets/stylesheets/thredded.scss +3 -0
- data/app/commands/thredded/at_notification_extractor.rb +15 -0
- data/app/commands/thredded/members_marked_notified.rb +18 -0
- data/app/commands/thredded/messageboard_destroyer.rb +60 -0
- data/app/commands/thredded/notify_mentioned_users.rb +68 -0
- data/app/commands/thredded/notify_private_topic_users.rb +51 -0
- data/app/commands/thredded/user_reads_private_topic.rb +22 -0
- data/app/commands/thredded/user_resets_private_topic_to_unread.rb +23 -0
- data/app/controllers/thredded/application_controller.rb +105 -0
- data/app/controllers/thredded/messageboards_controller.rb +58 -0
- data/app/controllers/thredded/posts_controller.rb +78 -0
- data/app/controllers/thredded/preferences_controller.rb +28 -0
- data/app/controllers/thredded/private_topics_controller.rb +65 -0
- data/app/controllers/thredded/setups_controller.rb +53 -0
- data/app/controllers/thredded/theme_previews_controller.rb +59 -0
- data/app/controllers/thredded/topics_controller.rb +153 -0
- data/app/decorators/thredded/base_topic_decorator.rb +14 -0
- data/app/decorators/thredded/base_user_topic_decorator.rb +63 -0
- data/app/decorators/thredded/messageboard_decorator.rb +41 -0
- data/app/decorators/thredded/post_decorator.rb +40 -0
- data/app/decorators/thredded/private_topic_decorator.rb +23 -0
- data/app/decorators/thredded/topic_decorator.rb +25 -0
- data/app/decorators/thredded/topic_email_decorator.rb +24 -0
- data/app/decorators/thredded/user_private_topic_decorator.rb +13 -0
- data/app/decorators/thredded/user_topic_decorator.rb +37 -0
- data/app/forms/thredded/private_topic_form.rb +126 -0
- data/app/forms/thredded/topic_form.rb +94 -0
- data/app/helpers/thredded/application_helper.rb +41 -0
- data/app/jobs/thredded/activity_updater_job.rb +20 -0
- data/app/jobs/thredded/at_notifier_job.rb +11 -0
- data/app/jobs/thredded/notify_private_topic_users_job.rb +11 -0
- data/app/mailers/thredded/base_mailer.rb +4 -0
- data/app/mailers/thredded/post_mailer.rb +15 -0
- data/app/mailers/thredded/private_post_mailer.rb +15 -0
- data/app/mailers/thredded/private_topic_mailer.rb +15 -0
- data/app/models/concerns/thredded/post_common.rb +105 -0
- data/app/models/concerns/thredded/topic_common.rb +48 -0
- data/app/models/thredded/ability.rb +60 -0
- data/app/models/thredded/category.rb +12 -0
- data/app/models/thredded/messageboard.rb +50 -0
- data/app/models/thredded/messageboard_user.rb +12 -0
- data/app/models/thredded/notification_preference.rb +17 -0
- data/app/models/thredded/null_preference.rb +11 -0
- data/app/models/thredded/null_topic.rb +15 -0
- data/app/models/thredded/null_topic_read.rb +19 -0
- data/app/models/thredded/null_user.rb +51 -0
- data/app/models/thredded/post.rb +37 -0
- data/app/models/thredded/post_notification.rb +17 -0
- data/app/models/thredded/private_post.rb +25 -0
- data/app/models/thredded/private_topic.rb +60 -0
- data/app/models/thredded/private_user.rb +6 -0
- data/app/models/thredded/stats.rb +37 -0
- data/app/models/thredded/topic.rb +104 -0
- data/app/models/thredded/topic_category.rb +6 -0
- data/app/models/thredded/user_detail.rb +26 -0
- data/app/models/thredded/user_extender.rb +33 -0
- data/app/models/thredded/user_permissions/admin/if_admin_column_true.rb +12 -0
- data/app/models/thredded/user_permissions/admin/none.rb +12 -0
- data/app/models/thredded/user_permissions/message/readers_of_writeable_boards.rb +13 -0
- data/app/models/thredded/user_permissions/moderate/if_moderator_column_true.rb +25 -0
- data/app/models/thredded/user_permissions/moderate/none.rb +25 -0
- data/app/models/thredded/user_permissions/read/all.rb +25 -0
- data/app/models/thredded/user_permissions/write/all.rb +25 -0
- data/app/models/thredded/user_permissions/write/none.rb +25 -0
- data/app/models/thredded/user_preference.rb +6 -0
- data/app/models/thredded/user_topic_read.rb +10 -0
- data/app/views/layouts/thredded/application.html.erb +15 -0
- data/app/views/thredded/categories/_category.html.erb +1 -0
- data/app/views/thredded/kaminari/_first_page.html.erb +11 -0
- data/app/views/thredded/kaminari/_gap.html.erb +8 -0
- data/app/views/thredded/kaminari/_last_page.html.erb +11 -0
- data/app/views/thredded/kaminari/_next_page.html.erb +11 -0
- data/app/views/thredded/kaminari/_page.html.erb +12 -0
- data/app/views/thredded/kaminari/_paginator.html.erb +23 -0
- data/app/views/thredded/kaminari/_prev_page.html.erb +11 -0
- data/app/views/thredded/messageboards/_messageboard.html.erb +15 -0
- data/app/views/thredded/messageboards/index.html.erb +20 -0
- data/app/views/thredded/messageboards/new.html.erb +18 -0
- data/app/views/thredded/post_mailer/at_notification.html.erb +14 -0
- data/app/views/thredded/post_mailer/at_notification.text.erb +10 -0
- data/app/views/thredded/posts/_content_field.html.erb +4 -0
- data/app/views/thredded/posts/_form.html.erb +1 -0
- data/app/views/thredded/posts/_post.html.erb +1 -0
- data/app/views/thredded/posts/_user.html.erb +3 -0
- data/app/views/thredded/posts/edit.html.erb +13 -0
- data/app/views/thredded/posts_common/_form.html.erb +10 -0
- data/app/views/thredded/posts_common/_post.html.erb +20 -0
- data/app/views/thredded/preferences/_form.html.erb +28 -0
- data/app/views/thredded/preferences/_header.html.erb +1 -0
- data/app/views/thredded/preferences/edit.html.erb +12 -0
- data/app/views/thredded/private_post_mailer/at_notification.html.erb +11 -0
- data/app/views/thredded/private_posts/_form.html.erb +1 -0
- data/app/views/thredded/private_posts/_private_post.html.erb +1 -0
- data/app/views/thredded/private_topic_mailer/message_notification.html.erb +17 -0
- data/app/views/thredded/private_topic_mailer/message_notification.text.erb +13 -0
- data/app/views/thredded/private_topics/_breadcrumbs.html.erb +4 -0
- data/app/views/thredded/private_topics/_form.html.erb +24 -0
- data/app/views/thredded/private_topics/_no_private_topics.html.erb +6 -0
- data/app/views/thredded/private_topics/_private_topic.html.erb +22 -0
- data/app/views/thredded/private_topics/index.html.erb +23 -0
- data/app/views/thredded/private_topics/new.html.erb +13 -0
- data/app/views/thredded/private_topics/show.html.erb +14 -0
- data/app/views/thredded/search/_form.html.erb +7 -0
- data/app/views/thredded/shared/_currently_online.html.erb +16 -0
- data/app/views/thredded/shared/_flash_messages.html.erb +7 -0
- data/app/views/thredded/shared/_header.html.erb +4 -0
- data/app/views/thredded/shared/_messageboard_topics_breadcrumbs.html.erb +6 -0
- data/app/views/thredded/shared/_notification_preferences.html.erb +7 -0
- data/app/views/thredded/shared/_page.html.erb +6 -0
- data/app/views/thredded/shared/_time_ago.html.erb +7 -0
- data/app/views/thredded/shared/_top_nav.html.erb +36 -0
- data/app/views/thredded/shared/_topic_nav.html.erb +22 -0
- data/app/views/thredded/theme_previews/_section_title.html.erb +3 -0
- data/app/views/thredded/theme_previews/show.html.erb +108 -0
- data/app/views/thredded/topics/_form.html.erb +23 -0
- data/app/views/thredded/topics/_recent_topics_by_user.html.erb +8 -0
- data/app/views/thredded/topics/_topic.html.erb +29 -0
- data/app/views/thredded/topics/_topic_form_admin_options.html.erb +12 -0
- data/app/views/thredded/topics/by_category.html.erb +56 -0
- data/app/views/thredded/topics/edit.html.erb +38 -0
- data/app/views/thredded/topics/index.html.erb +18 -0
- data/app/views/thredded/topics/menu/_new_topic.html.erb +3 -0
- data/app/views/thredded/topics/new.html.erb +11 -0
- data/app/views/thredded/topics/search.html.erb +9 -0
- data/app/views/thredded/topics/show.html.erb +31 -0
- data/app/views/thredded/topics_common/_header.html.erb +6 -0
- data/app/views/thredded/users/_link.html.erb +9 -0
- data/config/routes.rb +28 -0
- data/db/migrate/20160329231848_create_thredded.rb +173 -0
- data/lib/generators/thredded/install/USAGE +13 -0
- data/lib/generators/thredded/install/install_generator.rb +23 -0
- data/lib/generators/thredded/install/templates/initializer.rb +51 -0
- data/lib/html/pipeline/at_mention_filter.rb +20 -0
- data/lib/html/pipeline/bbcode_filter.rb +19 -0
- data/lib/tasks/thredded_tasks.rake +18 -0
- data/lib/thredded/at_users.rb +19 -0
- data/lib/thredded/engine.rb +35 -0
- data/lib/thredded/errors.rb +67 -0
- data/lib/thredded/main_app_route_delegator.rb +24 -0
- data/lib/thredded/messageboard_user_permissions.rb +22 -0
- data/lib/thredded/post_sql_builder.rb +12 -0
- data/lib/thredded/post_user_permissions.rb +32 -0
- data/lib/thredded/private_topic_user_permissions.rb +26 -0
- data/lib/thredded/search_parser.rb +45 -0
- data/lib/thredded/search_sql_builder.rb +21 -0
- data/lib/thredded/seed_database.rb +76 -0
- data/lib/thredded/table_sql_builder.rb +41 -0
- data/lib/thredded/topic_sql_builder.rb +11 -0
- data/lib/thredded/topic_user_permissions.rb +32 -0
- data/lib/thredded/version.rb +3 -0
- data/lib/thredded.rb +85 -0
- data/thredded.gemspec +68 -0
- metadata +248 -122
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<% content_for :thredded_page_title, 'Topics' %>
|
|
2
|
+
<% content_for :thredded_page_id, 'thredded--topics-index' %>
|
|
3
|
+
<% content_for :thredded_breadcrumbs, render('thredded/shared/messageboard_topics_breadcrumbs') %>
|
|
4
|
+
|
|
5
|
+
<%= thredded_page do %>
|
|
6
|
+
<%= content_tag :section, class: 'thredded--main-section thredded--topics' do %>
|
|
7
|
+
<%= render 'thredded/topics/form',
|
|
8
|
+
messageboard: messageboard,
|
|
9
|
+
topic: @new_topic,
|
|
10
|
+
css_class: 'thredded--is-compact',
|
|
11
|
+
placeholder: 'Start a New Topic' if @new_topic %>
|
|
12
|
+
<%= render @decorated_topics %>
|
|
13
|
+
<% end %>
|
|
14
|
+
|
|
15
|
+
<footer>
|
|
16
|
+
<%= paginate @topics %>
|
|
17
|
+
</footer>
|
|
18
|
+
<% end %>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<% content_for :thredded_page_title, 'Create a New Topic' %>
|
|
2
|
+
<% content_for :thredded_page_id, 'thredded--new-topic' %>
|
|
3
|
+
<% content_for :thredded_breadcrumbs, render('thredded/shared/messageboard_topics_breadcrumbs') %>
|
|
4
|
+
|
|
5
|
+
<%= thredded_page do %>
|
|
6
|
+
<%= render 'thredded/topics/form',
|
|
7
|
+
messageboard: messageboard,
|
|
8
|
+
topic: @new_topic,
|
|
9
|
+
css_class: 'thredded--is-expanded',
|
|
10
|
+
placeholder: 'Start a New Topic' %>
|
|
11
|
+
<% end %>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<% content_for :thredded_page_title, 'Topics Search Results' %>
|
|
2
|
+
<% content_for :thredded_page_id, 'thredded--topic-search-results' %>
|
|
3
|
+
<% content_for :thredded_breadcrumbs, render('thredded/shared/messageboard_topics_breadcrumbs') %>
|
|
4
|
+
|
|
5
|
+
<%= thredded_page do %>
|
|
6
|
+
<section class="thredded--main-section thredded--topics">
|
|
7
|
+
<%= render @decorated_topics %>
|
|
8
|
+
</section>
|
|
9
|
+
<% end %>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<% content_for :thredded_page_title, topic.title %>
|
|
2
|
+
<% content_for :thredded_page_id, 'thredded--posts' %>
|
|
3
|
+
<% content_for :thredded_breadcrumbs, render('thredded/shared/messageboard_topics_breadcrumbs') %>
|
|
4
|
+
|
|
5
|
+
<%= thredded_page do %>
|
|
6
|
+
<%= content_tag_for :section, topic, class: "thredded--main-section thredded--topic #{user_topic.css_class}" do %>
|
|
7
|
+
<%= render 'thredded/topics_common/header', topic: topic %>
|
|
8
|
+
<%= render @posts %>
|
|
9
|
+
<% if can?(:create, @new_post) %>
|
|
10
|
+
<div class="thredded--post-form--wrapper">
|
|
11
|
+
<h3 class="thredded--post-form--title">Add a post</h3>
|
|
12
|
+
<%= render 'thredded/posts/form',
|
|
13
|
+
topic: topic,
|
|
14
|
+
post: @new_post,
|
|
15
|
+
button_text: 'Submit Reply' %>
|
|
16
|
+
</div>
|
|
17
|
+
<% end %>
|
|
18
|
+
|
|
19
|
+
<% if current_ability.can? :destroy, topic %>
|
|
20
|
+
<div class="thredded--topic-delete--wrapper">
|
|
21
|
+
<%= form_tag messageboard_topic_path(messageboard, topic), method: :delete, class: 'thredded--topic-delete-form' do %>
|
|
22
|
+
<%= submit_tag 'Delete Topic', class: 'thredded--form--submit' %>
|
|
23
|
+
<% end %>
|
|
24
|
+
</div>
|
|
25
|
+
<% end %>
|
|
26
|
+
<% end %>
|
|
27
|
+
|
|
28
|
+
<footer>
|
|
29
|
+
<%= paginate @posts %>
|
|
30
|
+
</footer>
|
|
31
|
+
<% end %>
|
data/config/routes.rb
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
Thredded::Engine.routes.draw do
|
|
2
|
+
constraints(->(req) { req.env['QUERY_STRING'].include? 'q=' }) do
|
|
3
|
+
get '/:messageboard_id(.:format)' => 'topics#search', as: :messageboard_search
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
resource :theme_preview, only: [:show] if %w(development test).include? Rails.env
|
|
7
|
+
|
|
8
|
+
resources :private_topics, path: 'private-topics' do
|
|
9
|
+
resources :private_posts, path: '', except: [:index], controller: 'posts'
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
get '/messageboards/new' => 'messageboards#new', as: :new_messageboard
|
|
13
|
+
get '/:messageboard_id/preferences/edit' => 'preferences#edit'
|
|
14
|
+
get '/:messageboard_id/new(.:format)' => 'topics#new', as: :new_messageboard_topic
|
|
15
|
+
get '/:messageboard_id/:id/edit(.:format)' => 'topics#edit', as: :edit_messageboard_topic
|
|
16
|
+
get '/:messageboard_id/:id/page-:page(.:format)' => 'topics#show', as: :paged_messageboard_topic_posts, constraints: { page: /\d+/ }
|
|
17
|
+
get '/:messageboard_id/category/:category_id' => 'topics#category', as: :messageboard_topics_categories
|
|
18
|
+
|
|
19
|
+
resources :messageboards, only: [:index, :create], path: '' do
|
|
20
|
+
resource :preferences, only: [:edit, :update]
|
|
21
|
+
|
|
22
|
+
resources :topics, path: '' do
|
|
23
|
+
resources :posts, path: '', except: [:index]
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
root to: 'messageboards#index'
|
|
28
|
+
end
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
class CreateThredded < ActiveRecord::Migration
|
|
2
|
+
def change
|
|
3
|
+
|
|
4
|
+
unless table_exists?(:friendly_id_slugs)
|
|
5
|
+
# The user might have installed FriendlyId separately already.
|
|
6
|
+
create_table :friendly_id_slugs do |t|
|
|
7
|
+
t.string :slug, limit: 255, null: false
|
|
8
|
+
t.integer :sluggable_id, null: false
|
|
9
|
+
t.string :sluggable_type, limit: 50
|
|
10
|
+
t.string :scope, limit: 191
|
|
11
|
+
t.datetime :created_at, null: false
|
|
12
|
+
end
|
|
13
|
+
add_index :friendly_id_slugs, [:slug, :sluggable_type, :scope], name: :index_friendly_id_slugs_on_slug_and_sluggable_type_and_scope, unique: true
|
|
14
|
+
add_index :friendly_id_slugs, [:slug, :sluggable_type], name: :index_friendly_id_slugs_on_slug_and_sluggable_type
|
|
15
|
+
add_index :friendly_id_slugs, [:sluggable_id], name: :index_friendly_id_slugs_on_sluggable_id
|
|
16
|
+
add_index :friendly_id_slugs, [:sluggable_type], name: :index_friendly_id_slugs_on_sluggable_type
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
create_table :thredded_categories do |t|
|
|
20
|
+
t.integer :messageboard_id, null: false
|
|
21
|
+
t.string :name, limit: 255, null: false
|
|
22
|
+
t.string :description, limit: 255
|
|
23
|
+
t.timestamps null: false
|
|
24
|
+
t.string :slug, limit: 191, null: false
|
|
25
|
+
end
|
|
26
|
+
add_index :thredded_categories, [:messageboard_id, :slug], name: :index_thredded_categories_on_messageboard_id_and_slug, unique: true
|
|
27
|
+
add_index :thredded_categories, [:messageboard_id], name: :index_thredded_categories_on_messageboard_id
|
|
28
|
+
DbTextSearch::CaseInsensitiveEq.add_index connection, :thredded_categories, :name, name: :thredded_categories_name_ci
|
|
29
|
+
|
|
30
|
+
create_table :thredded_messageboards do |t|
|
|
31
|
+
t.string :name, limit: 255, null: false
|
|
32
|
+
t.string :slug, limit: 191
|
|
33
|
+
t.text :description
|
|
34
|
+
t.integer :topics_count, default: 0
|
|
35
|
+
t.integer :posts_count, default: 0
|
|
36
|
+
t.boolean :closed, default: false, null: false
|
|
37
|
+
t.timestamps null: false
|
|
38
|
+
t.string :filter, limit: 255, default: 'markdown', null: false
|
|
39
|
+
end
|
|
40
|
+
add_index :thredded_messageboards, [:closed], name: :index_thredded_messageboards_on_closed
|
|
41
|
+
add_index :thredded_messageboards, [:slug], name: :index_thredded_messageboards_on_slug
|
|
42
|
+
|
|
43
|
+
create_table :thredded_notification_preferences do |t|
|
|
44
|
+
t.boolean :notify_on_mention, default: true
|
|
45
|
+
t.boolean :notify_on_message, default: true
|
|
46
|
+
t.integer :user_id, null: false
|
|
47
|
+
t.integer :messageboard_id, null: false
|
|
48
|
+
t.timestamps null: false
|
|
49
|
+
end
|
|
50
|
+
add_index :thredded_notification_preferences, [:messageboard_id], name: :index_thredded_notification_preferences_on_messageboard_id
|
|
51
|
+
add_index :thredded_notification_preferences, [:user_id], name: :index_thredded_notification_preferences_on_user_id
|
|
52
|
+
|
|
53
|
+
create_table :thredded_post_notifications do |t|
|
|
54
|
+
t.string :email, limit: 255, null: false
|
|
55
|
+
t.integer :post_id, null: false
|
|
56
|
+
t.timestamps null: false
|
|
57
|
+
t.string :post_type, limit: 255
|
|
58
|
+
end
|
|
59
|
+
add_index :thredded_post_notifications, [:post_id, :post_type], name: :index_thredded_post_notifications_on_post
|
|
60
|
+
|
|
61
|
+
create_table :thredded_posts do |t|
|
|
62
|
+
t.integer :user_id, limit: 4
|
|
63
|
+
t.text :content, limit: 65535
|
|
64
|
+
t.string :ip, limit: 255
|
|
65
|
+
t.string :filter, limit: 255, default: 'markdown'
|
|
66
|
+
t.string :source, limit: 255, default: 'web'
|
|
67
|
+
t.integer :postable_id, limit: 4
|
|
68
|
+
t.integer :messageboard_id, null: false
|
|
69
|
+
t.timestamps null: false
|
|
70
|
+
end
|
|
71
|
+
add_index :thredded_posts, [:messageboard_id], name: :index_thredded_posts_on_messageboard_id
|
|
72
|
+
add_index :thredded_posts, [:postable_id], name: :index_thredded_posts_on_postable_id
|
|
73
|
+
add_index :thredded_posts, [:postable_id], name: :index_thredded_posts_on_postable_id_and_postable_type
|
|
74
|
+
add_index :thredded_posts, [:user_id], name: :index_thredded_posts_on_user_id
|
|
75
|
+
DbTextSearch::FullTextSearch.add_index connection, :thredded_posts, :content, name: :thredded_posts_content_fts
|
|
76
|
+
|
|
77
|
+
create_table :thredded_private_posts do |t|
|
|
78
|
+
t.integer :user_id, limit: 4
|
|
79
|
+
t.text :content, limit: 65535
|
|
80
|
+
t.string :ip, limit: 255
|
|
81
|
+
t.string :filter, limit: 255, default: 'markdown'
|
|
82
|
+
t.integer :postable_id, null: false
|
|
83
|
+
t.timestamps null: false
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
create_table :thredded_private_topics do |t|
|
|
87
|
+
t.integer :user_id, null: false
|
|
88
|
+
t.integer :last_user_id, null: false
|
|
89
|
+
t.string :title, limit: 255, null: false
|
|
90
|
+
t.string :slug, limit: 191, null: false
|
|
91
|
+
t.integer :posts_count, default: 0
|
|
92
|
+
t.string :hash_id, limit: 191, null: false
|
|
93
|
+
t.timestamps null: false
|
|
94
|
+
end
|
|
95
|
+
add_index :thredded_private_topics, [:hash_id], name: :index_thredded_private_topics_on_hash_id
|
|
96
|
+
add_index :thredded_private_topics, [:slug], name: :index_thredded_private_topics_on_slug
|
|
97
|
+
|
|
98
|
+
create_table :thredded_private_users do |t|
|
|
99
|
+
t.integer :private_topic_id, limit: 4
|
|
100
|
+
t.integer :user_id, limit: 4
|
|
101
|
+
t.timestamps null: false
|
|
102
|
+
t.boolean :read, default: false
|
|
103
|
+
end
|
|
104
|
+
add_index :thredded_private_users, [:private_topic_id], name: :index_thredded_private_users_on_private_topic_id
|
|
105
|
+
add_index :thredded_private_users, [:read], name: :index_thredded_private_users_on_read
|
|
106
|
+
add_index :thredded_private_users, [:user_id], name: :index_thredded_private_users_on_user_id
|
|
107
|
+
|
|
108
|
+
create_table :thredded_topic_categories do |t|
|
|
109
|
+
t.integer :topic_id, null: false
|
|
110
|
+
t.integer :category_id, null: false
|
|
111
|
+
end
|
|
112
|
+
add_index :thredded_topic_categories, [:category_id], name: :index_thredded_topic_categories_on_category_id
|
|
113
|
+
add_index :thredded_topic_categories, [:topic_id], name: :index_thredded_topic_categories_on_topic_id
|
|
114
|
+
|
|
115
|
+
create_table :thredded_topics do |t|
|
|
116
|
+
t.integer :user_id, null: false
|
|
117
|
+
t.integer :last_user_id, null: false
|
|
118
|
+
t.string :title, limit: 255, null: false
|
|
119
|
+
t.string :slug, limit: 191, null: false
|
|
120
|
+
t.integer :messageboard_id, null: false
|
|
121
|
+
t.integer :posts_count, default: 0, null: false
|
|
122
|
+
t.boolean :sticky, default: false, null: false
|
|
123
|
+
t.boolean :locked, default: false, null: false
|
|
124
|
+
t.string :hash_id, limit: 191, null: false
|
|
125
|
+
t.string :type, limit: 191
|
|
126
|
+
t.timestamps null: false
|
|
127
|
+
end
|
|
128
|
+
add_index :thredded_topics, [:hash_id], name: :index_thredded_topics_on_hash_id
|
|
129
|
+
add_index :thredded_topics, [:messageboard_id, :slug], name: :index_thredded_topics_on_messageboard_id_and_slug, unique: true
|
|
130
|
+
add_index :thredded_topics, [:messageboard_id], name: :index_thredded_topics_on_messageboard_id
|
|
131
|
+
add_index :thredded_topics, [:user_id], name: :index_thredded_topics_on_user_id
|
|
132
|
+
DbTextSearch::FullTextSearch.add_index connection, :thredded_topics, :title, name: :thredded_topics_title_fts
|
|
133
|
+
|
|
134
|
+
create_table :thredded_user_details do |t|
|
|
135
|
+
t.integer :user_id, null: false
|
|
136
|
+
t.datetime :latest_activity_at
|
|
137
|
+
t.integer :posts_count, default: 0
|
|
138
|
+
t.integer :topics_count, default: 0
|
|
139
|
+
t.timestamps null: false
|
|
140
|
+
t.datetime :last_seen_at
|
|
141
|
+
end
|
|
142
|
+
add_index :thredded_user_details, [:latest_activity_at], name: :index_thredded_user_details_on_latest_activity_at
|
|
143
|
+
add_index :thredded_user_details, [:user_id], name: :index_thredded_user_details_on_user_id
|
|
144
|
+
|
|
145
|
+
create_table :thredded_messageboard_users do |t|
|
|
146
|
+
t.references :thredded_user_detail, foreign_key: true, null: false
|
|
147
|
+
t.references :thredded_messageboard, foreign_key: true, null: false
|
|
148
|
+
t.datetime :last_seen_at, null: false
|
|
149
|
+
end
|
|
150
|
+
add_index :thredded_messageboard_users, [:thredded_messageboard_id, :thredded_user_detail_id],
|
|
151
|
+
name: :index_thredded_messageboard_users_primary
|
|
152
|
+
add_index :thredded_messageboard_users, [:thredded_messageboard_id, :last_seen_at],
|
|
153
|
+
name: :index_thredded_messageboard_users_for_recently_active
|
|
154
|
+
|
|
155
|
+
create_table :thredded_user_preferences do |t|
|
|
156
|
+
t.integer :user_id, null: false
|
|
157
|
+
t.string :time_zone, limit: 191, default: 'Eastern Time (US & Canada)'
|
|
158
|
+
t.timestamps null: false
|
|
159
|
+
end
|
|
160
|
+
add_index :thredded_user_preferences, [:user_id], name: :index_thredded_user_preferences_on_user_id
|
|
161
|
+
|
|
162
|
+
create_table :thredded_user_topic_reads do |t|
|
|
163
|
+
t.integer :user_id, null: false
|
|
164
|
+
t.integer :topic_id, null: false
|
|
165
|
+
t.integer :post_id, null: false
|
|
166
|
+
t.integer :posts_count, default: 0, null: false
|
|
167
|
+
t.integer :page, default: 1, null: false
|
|
168
|
+
t.timestamps null: false
|
|
169
|
+
end
|
|
170
|
+
add_index :thredded_user_topic_reads, [:topic_id], name: :index_thredded_user_topic_reads_on_topic_id
|
|
171
|
+
add_index :thredded_user_topic_reads, [:user_id, :topic_id], name: :index_thredded_user_topic_reads_on_user_id_and_topic_id, unique: true
|
|
172
|
+
end
|
|
173
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Description:
|
|
2
|
+
Create an initializer with description of configuration and defaults
|
|
3
|
+
|
|
4
|
+
Example:
|
|
5
|
+
rails generate thredded:install
|
|
6
|
+
|
|
7
|
+
This will create:
|
|
8
|
+
config/initializers/thredded.rb
|
|
9
|
+
|
|
10
|
+
Options:
|
|
11
|
+
|
|
12
|
+
[--theme] # Will copy thredded layout, views, and assets to the parent app
|
|
13
|
+
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Thredded
|
|
2
|
+
module Generators
|
|
3
|
+
class InstallGenerator < Rails::Generators::Base
|
|
4
|
+
class_option :theme,
|
|
5
|
+
type: :boolean,
|
|
6
|
+
default: false,
|
|
7
|
+
desc: 'Copy all thredded layout, views, and assets to parent application.'
|
|
8
|
+
|
|
9
|
+
def set_source_paths
|
|
10
|
+
@source_paths = [
|
|
11
|
+
File.expand_path('../templates', __FILE__),
|
|
12
|
+
File.expand_path('../../../../..', __FILE__),
|
|
13
|
+
]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def copy_initializer_file
|
|
17
|
+
copy_file \
|
|
18
|
+
'initializer.rb',
|
|
19
|
+
'config/initializers/thredded.rb'
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Thredded configuration
|
|
2
|
+
|
|
3
|
+
# ==> User Configuration
|
|
4
|
+
# The name of the class your app uses for your users.
|
|
5
|
+
# By default the engine will use 'User' but if you have another name
|
|
6
|
+
# for your user class - change it here.
|
|
7
|
+
Thredded.user_class = 'User'
|
|
8
|
+
|
|
9
|
+
# User name column, used in @mention syntax and should be unique.
|
|
10
|
+
# This is the column used to search for users' names if/when someone is @ mentioned.
|
|
11
|
+
Thredded.user_name_column = :name
|
|
12
|
+
|
|
13
|
+
# The path (or URL) you will use to link to your users' profiles.
|
|
14
|
+
# When linking to a user, Thredded will use this lambda to spit out
|
|
15
|
+
# the path or url to your user. This lambda is evaluated in the view context.
|
|
16
|
+
Thredded.user_path = lambda do |user|
|
|
17
|
+
user_path = :"#{Thredded.user_class.name.underscore}_path"
|
|
18
|
+
main_app.respond_to?(user_path) ? main_app.send(user_path, user) : "/users/#{user.to_param}"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# User avatar URL. rb-gravatar gem is used by default:
|
|
22
|
+
Thredded.avatar_url = ->(user) { Gravatar.src(user.email, 128, 'mm') }
|
|
23
|
+
|
|
24
|
+
# ==> Permissions Configuration
|
|
25
|
+
# By default, thredded uses a simple permission model, where all the users can post to all message boards,
|
|
26
|
+
# and admins and moderators are determined by a flag on the users table.
|
|
27
|
+
|
|
28
|
+
# The name of the moderator flag column on the users table.
|
|
29
|
+
Thredded.moderator_column = :admin
|
|
30
|
+
# The name of the admin flag column on the users table.
|
|
31
|
+
Thredded.admin_column = :admin
|
|
32
|
+
|
|
33
|
+
# This model can be customized further by overriding a handful of methods on the User model.
|
|
34
|
+
# For more information, see app/models/thredded/user_extender.rb.
|
|
35
|
+
|
|
36
|
+
# ==> Email Configuration
|
|
37
|
+
# Email "From:" field will use the following
|
|
38
|
+
# Thredded.email_from = 'no-reply@example.com'
|
|
39
|
+
|
|
40
|
+
# Incoming email will be directed to this host
|
|
41
|
+
# Thredded.email_incoming_host = 'example.com'
|
|
42
|
+
|
|
43
|
+
# Emails going out will prefix the "Subject:" with the following string
|
|
44
|
+
# Thredded.email_outgoing_prefix = '[My Forum] '
|
|
45
|
+
|
|
46
|
+
# Reply to field for email notifications
|
|
47
|
+
# Thredded.email_reply_to = -> postable { "#{postable.hash_id}@#{Thredded.email_incoming_host}" }
|
|
48
|
+
|
|
49
|
+
# ==> View Configuration
|
|
50
|
+
# Set the layout for rendering the thredded views.
|
|
51
|
+
Thredded.layout = 'thredded/application'
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
require 'thredded/at_users'
|
|
2
|
+
|
|
3
|
+
module HTML
|
|
4
|
+
class Pipeline
|
|
5
|
+
class AtMentionFilter < Filter
|
|
6
|
+
def initialize(text, context = nil, result = nil)
|
|
7
|
+
super text, context, result
|
|
8
|
+
@text = text.to_s.gsub "\r", ''
|
|
9
|
+
@post = context[:post]
|
|
10
|
+
@view_context = context[:view_context]
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def call
|
|
14
|
+
html = Thredded::AtUsers.render(@text, @post, @view_context)
|
|
15
|
+
html.rstrip!
|
|
16
|
+
html
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
require 'bbcoder'
|
|
2
|
+
|
|
3
|
+
module HTML
|
|
4
|
+
class Pipeline
|
|
5
|
+
class BbcodeFilter < TextFilter
|
|
6
|
+
def initialize(text, context = {}, result = nil)
|
|
7
|
+
super text, context, result
|
|
8
|
+
@context = context
|
|
9
|
+
@text = @text.gsub "\r", ''
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def call
|
|
13
|
+
html = BBCoder.new(@text).to_html.gsub(/\n|\r\n/, '<br />')
|
|
14
|
+
html.rstrip!
|
|
15
|
+
"<p>#{html}</p>"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
namespace :thredded do
|
|
2
|
+
desc 'Destroy messageboard and all related data'
|
|
3
|
+
task :destroy, [:slug] => :environment do |_, args|
|
|
4
|
+
Thredded::MessageboardDestroyer.new(args.slug).run
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
task :nuke, [:slug] => :destroy
|
|
8
|
+
|
|
9
|
+
namespace :install do
|
|
10
|
+
desc 'Copy emoji to the Rails `public/emoji` directory'
|
|
11
|
+
task :emoji do
|
|
12
|
+
require 'emoji'
|
|
13
|
+
|
|
14
|
+
target = "#{Rake.original_dir}/public"
|
|
15
|
+
`mkdir -p '#{target}' && cp -Rp '#{Emoji.images_path}/emoji' '#{target}'`
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Thredded
|
|
2
|
+
class AtUsers
|
|
3
|
+
def self.render(content, post, view_context)
|
|
4
|
+
at_names = AtNotificationExtractor.new(content).run
|
|
5
|
+
|
|
6
|
+
if at_names.any?
|
|
7
|
+
members = post.readers_from_user_names(at_names)
|
|
8
|
+
|
|
9
|
+
members.each do |member|
|
|
10
|
+
member_path = Thredded.user_path(view_context, member)
|
|
11
|
+
content.gsub!(/(@#{member.to_s})\b/i,
|
|
12
|
+
%(<a href="#{ERB::Util.html_escape member_path}">\\1</a>))
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
content
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module Thredded
|
|
2
|
+
class Engine < ::Rails::Engine
|
|
3
|
+
isolate_namespace Thredded
|
|
4
|
+
|
|
5
|
+
config.autoload_paths << File.expand_path('../../../app/decorators', __FILE__)
|
|
6
|
+
config.autoload_paths << File.expand_path('../../../app/forms', __FILE__)
|
|
7
|
+
config.autoload_paths << File.expand_path('../../../app/commands', __FILE__)
|
|
8
|
+
config.autoload_paths << File.expand_path('../../../app/jobs', __FILE__)
|
|
9
|
+
|
|
10
|
+
config.generators do |g|
|
|
11
|
+
g.test_framework :rspec, fixture: true
|
|
12
|
+
g.fixture_replacement :factory_girl, dir: 'spec/factories'
|
|
13
|
+
g.helper false
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
config.to_prepare do
|
|
17
|
+
if Thredded.user_class
|
|
18
|
+
Thredded.user_class.send(:include, Thredded::UserExtender)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
unless Thredded.standalone_layout?
|
|
22
|
+
# Delegate all main_app routes to allow calling them directly.
|
|
23
|
+
::Thredded::ApplicationController.helper ::Thredded::MainAppRouteDelegator
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
initializer 'thredded.setup_assets' do
|
|
28
|
+
Thredded::Engine.config.assets.precompile += %w(
|
|
29
|
+
thredded.js
|
|
30
|
+
thredded.css
|
|
31
|
+
thredded/breadcrumb-chevron.svg
|
|
32
|
+
)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
module Thredded
|
|
2
|
+
class Error < StandardError
|
|
3
|
+
end
|
|
4
|
+
|
|
5
|
+
module Errors
|
|
6
|
+
class DatabaseEmpty < Thredded::Error
|
|
7
|
+
def message
|
|
8
|
+
'Seed the database with "rake thredded:dev:seed".'
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
class UserNotFound < Thredded::Error
|
|
13
|
+
def message
|
|
14
|
+
'This user could not be found. Is their name misspelled?'
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class PrivateTopicNotFound < Thredded::Error
|
|
19
|
+
def message
|
|
20
|
+
'This private topic does not exist.'
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
class TopicNotFound < Thredded::Error
|
|
25
|
+
def message
|
|
26
|
+
'This topic does not exist.'
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
class MessageboardNotFound < Thredded::Error
|
|
31
|
+
def message
|
|
32
|
+
'This messageboard does not exist.'
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
class MessageboardReadDenied < Thredded::Error
|
|
37
|
+
def message
|
|
38
|
+
'You are not authorized access to this messageboard.'
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
class MessageboardCreateDenied < Thredded::Error
|
|
43
|
+
def message
|
|
44
|
+
'You are not authorized to create a new messageboard.'
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
class TopicCreateDenied < Thredded::Error
|
|
49
|
+
def message
|
|
50
|
+
'You are not authorized to post in this messageboard.'
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
class PrivateTopicCreateDenied < TopicCreateDenied
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
class EmptySearchResults < Thredded::Error
|
|
58
|
+
def initialize(query)
|
|
59
|
+
@query = query
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def message
|
|
63
|
+
"There are no results for your search - '#{@query}'"
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module Thredded
|
|
2
|
+
# If thredded is rendered within an application layout, this module allows
|
|
3
|
+
# referring to the routes in the layout directly, without having to use `main_app.`.
|
|
4
|
+
module MainAppRouteDelegator
|
|
5
|
+
# delegate url helpers to main_app
|
|
6
|
+
def method_missing(method, *args, &block)
|
|
7
|
+
if main_app_route_method?(method)
|
|
8
|
+
main_app.send(method, *args)
|
|
9
|
+
else
|
|
10
|
+
super
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def respond_to?(method, *args)
|
|
15
|
+
super || main_app_route_method?(method)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def main_app_route_method?(method)
|
|
21
|
+
method.to_s =~ /_(?:path|url)$/ && main_app.respond_to?(method)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module Thredded
|
|
2
|
+
class MessageboardUserPermissions
|
|
3
|
+
attr_reader :messageboard, :user
|
|
4
|
+
|
|
5
|
+
def initialize(messageboard, user)
|
|
6
|
+
@messageboard = messageboard
|
|
7
|
+
@user = user
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def readable?
|
|
11
|
+
user.thredded_can_read_messageboards.include?(messageboard)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def postable?
|
|
15
|
+
user.thredded_can_write_messageboards.include?(messageboard)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def moderatable?
|
|
19
|
+
user.thredded_can_moderate_messageboards.include?(messageboard)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
module Thredded
|
|
2
|
+
class PostSqlBuilder < TableSqlBuilder
|
|
3
|
+
def apply_filters
|
|
4
|
+
@scope = @scope.joins(:topic_categories).where(category_id: categories) if categories.present?
|
|
5
|
+
return unless users.present? || text.present?
|
|
6
|
+
posts_scope = Thredded::Post
|
|
7
|
+
posts_scope = posts_scope.where(user_id: users) if users.present?
|
|
8
|
+
posts_scope = DbTextSearch::FullTextSearch.new(posts_scope, :content).find(text) if text.present?
|
|
9
|
+
@scope = @scope.joins(:posts).merge(posts_scope)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module Thredded
|
|
2
|
+
class PostUserPermissions
|
|
3
|
+
def initialize(post, user, _user_details)
|
|
4
|
+
@post = post
|
|
5
|
+
@topic = post.postable
|
|
6
|
+
@messageboard_user_permission = MessageboardUserPermissions.new(post.messageboard, user)
|
|
7
|
+
@user = user
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def editable?
|
|
11
|
+
created_post? || @messageboard_user_permission.moderatable?
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def manageable?
|
|
15
|
+
created_post? || @messageboard_user_permission.moderatable?
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def creatable?
|
|
19
|
+
thread_is_not_locked? && @messageboard_user_permission.postable?
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def created_post?
|
|
25
|
+
@user.id == @post.user_id
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def thread_is_not_locked?
|
|
29
|
+
@topic.private? || !@topic.locked?
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|