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