thredded-workgroup 0.2.0

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 (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