thredded-workgroup 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.rubocop +1 -0
- data/.rubocop.yml +65 -0
- data/.travis.yml +45 -0
- data/CHANGELOG.md +9 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +6 -0
- data/Guardfile +77 -0
- data/LICENSE.txt +21 -0
- data/README.md +125 -0
- data/RELEASE_CHECKLIST.md +19 -0
- data/Rakefile +161 -0
- data/app/assets/images/thredded/workgroup/.keep +0 -0
- data/app/assets/javascripts/thredded-workgroup.js +13 -0
- data/app/assets/javascripts/thredded/workgroup/follow.js +36 -0
- data/app/assets/javascripts/thredded/workgroup/topics.js +18 -0
- data/app/assets/stylesheets/thredded-workgroup.scss +3 -0
- data/app/assets/stylesheets/thredded/workgroup/_navs.scss +35 -0
- data/app/assets/stylesheets/thredded/workgroup/_topics.scss +52 -0
- data/app/controllers/thredded/posts_controller.rb +50 -0
- data/app/controllers/thredded/workgroup/application_controller.rb +7 -0
- data/app/controllers/thredded/workgroup/navs_controller.rb +41 -0
- data/app/helpers/thredded/application_helper.rb +9 -0
- data/app/helpers/thredded/workgroup/application_helper.rb +7 -0
- data/app/view_models/thredded/topic_view.rb +15 -0
- data/app/view_models/thredded/topics_page_view.rb +15 -0
- data/app/views/layouts/thredded/workgroup/application.html.erb +14 -0
- data/app/views/thredded/posts_common/form/_after_content.html.erb +1 -0
- data/app/views/thredded/shared/_header.html.erb +4 -0
- data/app/views/thredded/topics/_topic.html.erb +51 -0
- data/app/views/thredded/topics/_topics_with_last_post.html.erb +14 -0
- data/app/views/thredded/topics/topic/_body.html.erb +27 -0
- data/app/views/thredded/workgroup/navs/_personal_nav.html.erb +23 -0
- data/app/views/thredded/workgroup/navs/all_topics.html.erb +9 -0
- data/app/views/thredded/workgroup/navs/awaiting.html.erb +9 -0
- data/app/views/thredded/workgroup/navs/following.html.erb +9 -0
- data/app/views/thredded/workgroup/navs/unread.html.erb +9 -0
- data/bin/console +15 -0
- data/bin/dummy-rails +4 -0
- data/bin/rails.rb +14 -0
- data/bin/rspec +17 -0
- data/bin/setup +8 -0
- data/bin/update_from_thredded +40 -0
- data/config/locales/en.yml +20 -0
- data/config/routes.rb +11 -0
- data/lib/thredded/workgroup.rb +19 -0
- data/lib/thredded/workgroup/engine.rb +26 -0
- data/lib/thredded/workgroup/route_delegator.rb +25 -0
- data/lib/thredded/workgroup/thredded_route_delegator.rb +19 -0
- data/lib/thredded/workgroup/version.rb +6 -0
- data/script/create-db-users +42 -0
- data/script/dummy-console +3 -0
- data/shared.gemfile +27 -0
- data/thredded-workgroup.gemspec +65 -0
- 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,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,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
|