thredded-workgroup 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.rspec +2 -0
  4. data/.rubocop +1 -0
  5. data/.rubocop.yml +65 -0
  6. data/.travis.yml +45 -0
  7. data/CHANGELOG.md +9 -0
  8. data/CODE_OF_CONDUCT.md +49 -0
  9. data/Gemfile +6 -0
  10. data/Guardfile +77 -0
  11. data/LICENSE.txt +21 -0
  12. data/README.md +125 -0
  13. data/RELEASE_CHECKLIST.md +19 -0
  14. data/Rakefile +161 -0
  15. data/app/assets/images/thredded/workgroup/.keep +0 -0
  16. data/app/assets/javascripts/thredded-workgroup.js +13 -0
  17. data/app/assets/javascripts/thredded/workgroup/follow.js +36 -0
  18. data/app/assets/javascripts/thredded/workgroup/topics.js +18 -0
  19. data/app/assets/stylesheets/thredded-workgroup.scss +3 -0
  20. data/app/assets/stylesheets/thredded/workgroup/_navs.scss +35 -0
  21. data/app/assets/stylesheets/thredded/workgroup/_topics.scss +52 -0
  22. data/app/controllers/thredded/posts_controller.rb +50 -0
  23. data/app/controllers/thredded/workgroup/application_controller.rb +7 -0
  24. data/app/controllers/thredded/workgroup/navs_controller.rb +41 -0
  25. data/app/helpers/thredded/application_helper.rb +9 -0
  26. data/app/helpers/thredded/workgroup/application_helper.rb +7 -0
  27. data/app/view_models/thredded/topic_view.rb +15 -0
  28. data/app/view_models/thredded/topics_page_view.rb +15 -0
  29. data/app/views/layouts/thredded/workgroup/application.html.erb +14 -0
  30. data/app/views/thredded/posts_common/form/_after_content.html.erb +1 -0
  31. data/app/views/thredded/shared/_header.html.erb +4 -0
  32. data/app/views/thredded/topics/_topic.html.erb +51 -0
  33. data/app/views/thredded/topics/_topics_with_last_post.html.erb +14 -0
  34. data/app/views/thredded/topics/topic/_body.html.erb +27 -0
  35. data/app/views/thredded/workgroup/navs/_personal_nav.html.erb +23 -0
  36. data/app/views/thredded/workgroup/navs/all_topics.html.erb +9 -0
  37. data/app/views/thredded/workgroup/navs/awaiting.html.erb +9 -0
  38. data/app/views/thredded/workgroup/navs/following.html.erb +9 -0
  39. data/app/views/thredded/workgroup/navs/unread.html.erb +9 -0
  40. data/bin/console +15 -0
  41. data/bin/dummy-rails +4 -0
  42. data/bin/rails.rb +14 -0
  43. data/bin/rspec +17 -0
  44. data/bin/setup +8 -0
  45. data/bin/update_from_thredded +40 -0
  46. data/config/locales/en.yml +20 -0
  47. data/config/routes.rb +11 -0
  48. data/lib/thredded/workgroup.rb +19 -0
  49. data/lib/thredded/workgroup/engine.rb +26 -0
  50. data/lib/thredded/workgroup/route_delegator.rb +25 -0
  51. data/lib/thredded/workgroup/thredded_route_delegator.rb +19 -0
  52. data/lib/thredded/workgroup/version.rb +6 -0
  53. data/script/create-db-users +42 -0
  54. data/script/dummy-console +3 -0
  55. data/shared.gemfile +27 -0
  56. data/thredded-workgroup.gemspec +65 -0
  57. metadata +520 -0
data/Rakefile ADDED
@@ -0,0 +1,161 @@
1
+ # frozen_string_literal: true
2
+ begin
3
+ require "bundler/setup"
4
+ require "bundler/gem_tasks"
5
+ rescue LoadError
6
+ puts "You must `gem install bundler` and `bundle install` to run rake tasks"
7
+ end
8
+
9
+ begin
10
+ require "rdoc/task"
11
+ rescue LoadError
12
+ require "rdoc/rdoc"
13
+ require "rake/rdoctask"
14
+ RDoc::Task = Rake::RDocTask
15
+ end
16
+
17
+ RDoc::Task.new(:rdoc) do |rdoc|
18
+ rdoc.rdoc_dir = "rdoc"
19
+ rdoc.title = "Thredded Workgroup"
20
+ rdoc.options << "--line-numbers"
21
+ rdoc.rdoc_files.include("README.rdoc")
22
+ rdoc.rdoc_files.include("lib/**/*.rb")
23
+ end
24
+
25
+ APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
26
+ load "rails/tasks/engine.rake"
27
+
28
+ # Common methods for the test_all_dbs, test_all_gemfiles, and test_all Rake tasks.
29
+ module TestTasks
30
+ module_function
31
+
32
+ TEST_CMD = "bundle exec rspec"
33
+
34
+ def run_all(envs, cmd = "bundle install --quiet && #{TEST_CMD}", success_message:)
35
+ statuses = envs.map { |env| run(env, cmd) }
36
+ failed = statuses.reject(&:first).map(&:last)
37
+ if failed.empty?
38
+ $stderr.puts success_message
39
+ else
40
+ $stderr.puts "❌ FAILING (#{failed.size}):\n#{failed.map { |env| to_bash_cmd_with_env(cmd, env) } * "\n"}"
41
+ exit 1
42
+ end
43
+ end
44
+
45
+ def run(env, cmd)
46
+ require "pty"
47
+ require "English"
48
+ Bundler.with_clean_env do
49
+ $stderr.puts to_bash_cmd_with_env(cmd, env)
50
+ PTY.spawn(env, cmd) do |r, _w, pid|
51
+ begin
52
+ r.each_line { |l| puts l }
53
+ rescue Errno::EIO
54
+ # Errno:EIO error means that the process has finished giving output.
55
+ next
56
+ ensure
57
+ ::Process.wait pid
58
+ end
59
+ end
60
+ [$CHILD_STATUS && $CHILD_STATUS.exitstatus.zero?, env]
61
+ end
62
+ end
63
+
64
+ def gemfiles
65
+ Dir.glob("./spec/gemfiles/*.gemfile").sort
66
+ end
67
+
68
+ def dbs
69
+ %w(sqlite3 postgresql)
70
+ # %w(sqlite3 mysql2 postgresql)
71
+ end
72
+
73
+ def to_bash_cmd_with_env(cmd, env)
74
+ "(export #{env.map { |k, v| "#{k}=#{v}" }.join(' ')}; #{cmd})"
75
+ end
76
+ end
77
+
78
+ desc "Test all Gemfiles from spec/*.gemfile"
79
+ task :test_all_gemfiles do
80
+ envs = TestTasks.gemfiles.map { |gemfile| { "BUNDLE_GEMFILE" => gemfile } }
81
+ TestTasks.run_all envs, success_message: "✓ Tests pass with all #{envs.size} gemfiles"
82
+ end
83
+
84
+ desc "Test all supported databases"
85
+ task :test_all_dbs do
86
+ envs = TestTasks.dbs.map { |db| { "DB" => db } }
87
+ TestTasks.run_all envs, TestTasks::TEST_CMD, success_message: "✓ Tests pass with all #{envs.size} databases"
88
+ end
89
+
90
+ desc "Test all databases x gemfiles"
91
+ task :test_all do
92
+ dbs = TestTasks.dbs
93
+ gemfiles = TestTasks.gemfiles
94
+ TestTasks.run_all dbs.flat_map { |db| gemfiles.map { |gemfile| { "DB" => db, "BUNDLE_GEMFILE" => gemfile } } },
95
+ success_message: "✓ Tests pass with all #{dbs.size} databases x #{gemfiles.size} gemfiles"
96
+ end
97
+
98
+ Bundler::GemHelper.install_tasks
99
+
100
+ namespace :dev do
101
+ desc "Start development web server"
102
+ task :server do
103
+ require "rails/commands/server"
104
+
105
+ host = "0.0.0.0"
106
+ port = ENV["PORT"] || 9293
107
+ ENV["RACK_ENV"] = ENV["RAILS_ENV"] = "development"
108
+ Dir.chdir "spec/dummy"
109
+
110
+ Rack::Server.start(
111
+ environment: "development",
112
+ Host: host,
113
+ Port: port,
114
+ config: "config.ru"
115
+ )
116
+ end
117
+ end
118
+
119
+ namespace :assets do
120
+ desc "Precompile assets within dummy app"
121
+ task precompile: "app:assets:precompile"
122
+
123
+ desc "Remove old compiled assets from dummy app"
124
+ task clean: "app:assets:clean"
125
+ end
126
+
127
+ if ENV["HEROKU"]
128
+ require "rollbar/rake_tasks"
129
+ namespace :assets do
130
+ task precompile: "app:thredded:install:emoji"
131
+ end
132
+ else
133
+ require "rubocop/rake_task"
134
+ RuboCop::RakeTask.new
135
+
136
+ require "rspec/core/rake_task"
137
+ RSpec::Core::RakeTask.new(:spec)
138
+
139
+ task(:default).clear
140
+ task default: [:spec, :rubocop]
141
+ end
142
+
143
+ namespace :db do
144
+ desc "Wipe out all tables' data"
145
+ task truncate: :environment do
146
+ connection = ActiveRecord::Base.connection
147
+ tables = connection.tables - %w(schema_migrations)
148
+
149
+ tables.each do |table|
150
+ if connection.adapter_name =~ /sqlite/i
151
+ connection.execute("DELETE FROM #{table}")
152
+ connection.execute("DELETE FROM sqlite_sequence where name='#{table}'")
153
+ else
154
+ connection.execute("TRUNCATE #{table} CASCADE")
155
+ end
156
+ end
157
+ end
158
+
159
+ desc "Re-seed database with new data"
160
+ task reseed: [:truncate, :seed]
161
+ end
File without changes
@@ -0,0 +1,13 @@
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 any plugin's vendor/assets/javascripts directory 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
+ // compiled file.
9
+ //
10
+ // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
11
+ // about supported directives.
12
+ //
13
+ //= require_tree ./thredded/workgroup
@@ -0,0 +1,36 @@
1
+ (function($) {
2
+ $(document).ready(function () {
3
+ function updateFollowStatus($svg, $topic, followStatus) {
4
+ var $newSvg = $svg.clone();
5
+ if (followStatus) {
6
+ $topic.removeClass('thredded--topic-notfollowing').addClass('thredded--topic-following')
7
+ $newSvg.find('use').attr('xlink:href', '#thredded-follow-icon');
8
+
9
+ } else {
10
+ $topic.removeClass('thredded--topic-following').addClass('thredded--topic-notfollowing')
11
+ $newSvg.find('use').attr('xlink:href', '#thredded-unfollow-icon');
12
+ }
13
+ $newSvg.click(clickFollowIcon);
14
+ $svg.replaceWith($newSvg);
15
+ $newSvg.show();
16
+ };
17
+ function clickFollowIcon() {
18
+ var $icon = $(this);
19
+ var $topic = $icon.parents('.thredded--topics--topic');
20
+ var following = $topic.hasClass('thredded--topic-following');
21
+ var topicId = $topic.data("topic");
22
+ var messageboardId = $topic.data("messageboard");
23
+ var rootPath = $('#thredded--container').data('thredded-root-url');
24
+
25
+ var path = "" + rootPath + messageboardId + "/" + topicId + "/" + (following ? 'unfollow.json' : 'follow.json');
26
+ $icon.show();
27
+ $.ajax({
28
+ url: path, type: "POST", success: function (data) {
29
+ updateFollowStatus($icon, $topic, data["follow"])
30
+ }
31
+ });
32
+ }
33
+
34
+ $('.thredded--topics--follow-icon').click(clickFollowIcon);
35
+ });
36
+ })(jQuery);
@@ -0,0 +1,18 @@
1
+ (function($){
2
+ $(function () {
3
+ $('.thredded--topics--topic .thredded--topic-title, .thredded--topics--topic .thredded--topic-followers,' +
4
+ '.thredded--topics--topic .thredded--topics--posts-count').hover(
5
+ function(){$(this).closest('.thredded--topics--topic').addClass("thredded--rollover")},
6
+ function(){$(this).closest('.thredded--topics--topic').removeClass("thredded--rollover")}
7
+ ).click(function() {
8
+ var $a = $(this).closest('.thredded--topics--topic').find(".thredded--topic-title a");
9
+ window.location.href = $a.attr('href');
10
+ });
11
+
12
+ $('.thredded--topic-post-and-last-user').hover(
13
+ function(){ $(this).addClass('thredded--hovering')},
14
+ function(){ $(this).removeClass('thredded--hovering')}
15
+ ).click(function(){$(this).toggleClass('thredded--condensed')});
16
+ });
17
+ })(jQuery);
18
+
@@ -0,0 +1,3 @@
1
+ @import 'thredded';
2
+ @import 'thredded/workgroup/navs';
3
+ @import 'thredded/workgroup/topics';
@@ -0,0 +1,35 @@
1
+ ul.thredded--workgroup {
2
+ border-bottom: 1px solid #eee;
3
+ font-size: 0.875rem;
4
+ margin-left: 0;
5
+ margin-bottom: 1rem;
6
+ text-align: left;
7
+ list-style-type: none;
8
+ padding-left: 0;
9
+ li {
10
+ display: inline-block;
11
+ margin-right: 1rem;
12
+ &.active {
13
+ font-weight: bold;
14
+ }
15
+ a {
16
+ display: inline-block;
17
+ color: #a5aab6;
18
+ padding: 0.75rem 0;
19
+ text-decoration: none;
20
+ &:hover {
21
+ text-decoration: underline;
22
+ }
23
+ }
24
+ .expanded {
25
+ @include thredded-media-mobile {
26
+ display: none;
27
+ }
28
+ }
29
+ .condensed {
30
+ @include thredded-media-tablet-and-up {
31
+ display: none;
32
+ }
33
+ }
34
+ }
35
+ }
@@ -0,0 +1,52 @@
1
+ .thredded--topic-source cite a {
2
+ color: #a5aab6;
3
+ text-decoration: none;
4
+ &:hover {
5
+ color: #4a90e2;
6
+
7
+ }
8
+ }
9
+
10
+ cite.thredded--messageboard-name {
11
+ color: #a5aab6;
12
+ font-size: 0.875rem;
13
+ font-style: normal;
14
+ white-space: nowrap;
15
+ }
16
+
17
+ .thredded--topic-post-and-last-user {
18
+ max-width: 35rem;
19
+ margin: auto;
20
+ margin-top: 1rem;
21
+
22
+ &.thredded--condensed {
23
+ max-height: 100px;
24
+ overflow: hidden;
25
+ opacity: 0.8;
26
+ &.thredded--hovering {
27
+ opacity: 1;
28
+ }
29
+ }
30
+ &.thredded--hovering {
31
+ cursor: pointer;
32
+ }
33
+ }
34
+
35
+ .thredded--topics--topic .thredded--topics--follow-icon {
36
+ &:hover {
37
+ cursor: pointer;
38
+ opacity: 0.7;
39
+ color: #4a90e2;
40
+ }
41
+ }
42
+
43
+ .thredded--topics--topic.thredded--rollover{
44
+ cursor: pointer;
45
+
46
+ .thredded--topic-title a {
47
+ color: #4a90e2;
48
+ &:hover {
49
+ cursor: pointer;
50
+ }
51
+ }
52
+ }
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+ require_dependency File.expand_path("../../app/controllers/thredded/posts_controller", Thredded::Engine.called_from)
3
+
4
+ module Thredded
5
+ module PostsControllerWhichRedirects
6
+ def create
7
+ @post_form = Thredded::PostForm.new(
8
+ user: thredded_current_user, topic: parent_topic, post_params: new_post_params
9
+ )
10
+ authorize_creating @post_form.post
11
+
12
+ if @post_form.save # rubocop:disable Style/GuardClause
13
+ # TODO: extract as a hook on thredded#posts_controller `after_create(post)`
14
+ redirect_after_create(@post_form.post)
15
+ else
16
+ return render :new
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def after_mark_as_unread
23
+ if post.private_topic_post?
24
+ redirect_to private_topics_path
25
+ else
26
+ redirect_to thredded_workgroup.unread_nav_path
27
+ end
28
+ end
29
+
30
+ def redirect_after_create(post)
31
+ UserTopicReadState.touch!(thredded_current_user.id, post)
32
+ if params[:post_referer].present?
33
+ redirect_to params[:post_referer], notice: generate_flash_for(post)
34
+ else
35
+ redirect_to unread_nav_path, notice: generate_flash_for(post)
36
+ end
37
+ end
38
+
39
+ def generate_flash_for(post)
40
+ path_to_post = messageboard_topic_path(parent_topic.messageboard, parent_topic, anchor: "post_#{post.id}")
41
+ # rubocop:disable Rails/OutputSafety
42
+ "Successfully replied to #{view_context.link_to(parent_topic.title, path_to_post)}".html_safe
43
+ # rubocop:enable Rails/OutputSafety
44
+ end
45
+ end
46
+
47
+ class PostsController
48
+ prepend ::Thredded::PostsControllerWhichRedirects
49
+ end
50
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+ module Thredded
3
+ module Workgroup
4
+ class ApplicationController < Thredded::ApplicationController
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+ module Thredded
3
+ module Workgroup
4
+ class NavsController < Thredded::Workgroup::ApplicationController
5
+ before_action :thredded_require_login!, except: :root
6
+
7
+ def unread
8
+ @topics = gather_topics(Thredded::Topic.unread_followed_by(thredded_current_user))
9
+ end
10
+
11
+ def following
12
+ @topics = gather_topics(Thredded::Topic.followed_by(thredded_current_user))
13
+ end
14
+
15
+ def all_topics
16
+ @topics = gather_topics(Thredded::Topic.all)
17
+ end
18
+
19
+ def awaiting
20
+ @topics = gather_topics(Thredded::Topic.followed_by(thredded_current_user)
21
+ .where(last_user_id: thredded_current_user.id))
22
+ end
23
+
24
+ protected
25
+
26
+ def gather_topics(scope)
27
+ Thredded::TopicsPageView.new(
28
+ thredded_current_user,
29
+ scope
30
+ .order_recently_posted_first
31
+ .includes(:categories, :last_user, :user, :last_post, :messageboard, :followers)
32
+ .page(current_page)
33
+ )
34
+ end
35
+
36
+ def current_page
37
+ (params[:page] || 1).to_i
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+ require_dependency File.expand_path("../../app/helpers/thredded/application_helper", Thredded::Engine.called_from)
3
+ module Thredded
4
+ module ApplicationHelper
5
+ # include Thredded::Engine.routes.url_helpers
6
+ # include Thredded::Workgroup::Engine.routes.url_helpers
7
+ #
8
+ end
9
+ end