rtcl 1.0.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/MIT-LICENSE +20 -0
- data/README.md +28 -0
- data/Rakefile +14 -0
- data/app/assets/config/rtcl_manifest.js +3 -0
- data/app/assets/images/user-sample.jpg +0 -0
- data/app/assets/javascripts/rtcl/application.js +174 -0
- data/app/assets/javascripts/rtcl/name-that-color.js +1 -0
- data/app/assets/stylesheets/rtcl/application.css +7 -0
- data/app/assets/stylesheets/rtcl/highlight-android.css +1 -0
- data/app/assets/stylesheets/rtcl/styles.css +2578 -0
- data/app/assets/stylesheets/rtcl/trix.css +556 -0
- data/app/controllers/rtcl/application_controller.rb +18 -0
- data/app/controllers/rtcl/articles/comments_controller.rb +11 -0
- data/app/controllers/rtcl/articles_controller.rb +65 -0
- data/app/controllers/rtcl/comments_controller.rb +27 -0
- data/app/helpers/rtcl/application_helper.rb +4 -0
- data/app/jobs/rtcl/application_job.rb +4 -0
- data/app/mailers/rtcl/application_mailer.rb +6 -0
- data/app/models/ability.rb +20 -0
- data/app/models/concerns/rtcl/blogger.rb +9 -0
- data/app/models/rtcl/application_record.rb +5 -0
- data/app/models/rtcl/article.rb +53 -0
- data/app/views/layouts/rtcl/application.html.erb +19 -0
- data/app/views/rtcl/articles/_article.html.erb +33 -0
- data/app/views/rtcl/articles/_article.json.jbuilder +5 -0
- data/app/views/rtcl/articles/_form.html.erb +69 -0
- data/app/views/rtcl/articles/edit.html.erb +17 -0
- data/app/views/rtcl/articles/index.html.erb +32 -0
- data/app/views/rtcl/articles/new.html.erb +16 -0
- data/app/views/rtcl/articles/show.html.erb +64 -0
- data/app/views/rtcl/comments/_comment.html.erb +47 -0
- data/app/views/rtcl/comments/_comments.html.erb +18 -0
- data/app/views/rtcl/comments/_form.html.erb +11 -0
- data/app/views/rtcl/comments/new.html.erb +7 -0
- data/app/views/rtcl/comments/show.html.erb +1 -0
- data/config/routes.rb +11 -0
- data/db/migrate/20220208044728_create_articles.rb +16 -0
- data/lib/generators/rtcl/install/install_generator.rb +28 -0
- data/lib/generators/rtcl/templates/active_model_serializers.rb +1 -0
- data/lib/generators/rtcl/templates/rtcl.rb +10 -0
- data/lib/rtcl/configuration.rb +7 -0
- data/lib/rtcl/engine.rb +33 -0
- data/lib/rtcl/railtie.rb +4 -0
- data/lib/rtcl/version.rb +3 -0
- data/lib/rtcl.rb +30 -0
- data/lib/tasks/rtcl_tasks.rake +4 -0
- metadata +252 -0
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Ability
|
4
|
+
include CanCan::Ability
|
5
|
+
|
6
|
+
def initialize(user)
|
7
|
+
can :read, User
|
8
|
+
can :read, Rtcl::Article, is_private: false
|
9
|
+
|
10
|
+
return unless user.present?
|
11
|
+
|
12
|
+
can :manage, Rtcl::Article, author: user
|
13
|
+
can :manage, Polivalente::Comment, user: user
|
14
|
+
can :manage, User, id: user.id
|
15
|
+
|
16
|
+
return unless user.is_admin?
|
17
|
+
|
18
|
+
can :create, Rtcl::Article
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Rtcl
|
2
|
+
class Article < ApplicationRecord
|
3
|
+
include Polivalente::Archivable
|
4
|
+
include Polivalente::Commentable
|
5
|
+
include Polivalente::ContentHashable
|
6
|
+
include Polivalente::Reactable
|
7
|
+
include Polivalente::Sortable
|
8
|
+
include Polivalente::Taggable
|
9
|
+
include Polivalente::Trashable
|
10
|
+
include Polivalente::Visibility
|
11
|
+
|
12
|
+
has_rich_text :content
|
13
|
+
|
14
|
+
belongs_to :author, class_name: "User"
|
15
|
+
alias :user :author
|
16
|
+
|
17
|
+
enum language: Hash[LanguageList::COMMON_LANGUAGES.map { |l| [l.name, l.iso_639_1] }]
|
18
|
+
|
19
|
+
scope :published, -> { where(status: :published) }
|
20
|
+
scope :with_author, -> { includes(:author) }
|
21
|
+
|
22
|
+
validates_presence_of :content
|
23
|
+
validates_presence_of :language
|
24
|
+
|
25
|
+
validates :title, length: { minimum: 3, maximum: 50}
|
26
|
+
validates :language, inclusion: { in: languages.keys }
|
27
|
+
|
28
|
+
def summary
|
29
|
+
"#{truncate(content)}" if excerpt.nil?
|
30
|
+
excerpt
|
31
|
+
end
|
32
|
+
|
33
|
+
def byline
|
34
|
+
"by #{author.name}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def time_since_created
|
38
|
+
ActionController::Base.helpers.distance_of_time_in_words(Time.now, created_at)
|
39
|
+
end
|
40
|
+
|
41
|
+
def language_code
|
42
|
+
LanguageList::LanguageInfo.find(language).iso_639_1.inspect.gsub(/\"/i, '') unless self.new_record?
|
43
|
+
end
|
44
|
+
|
45
|
+
def colors
|
46
|
+
tag_list.split(',').each do |tag|
|
47
|
+
return tag if Rtcl.config.supported_tags.include? tag.strip
|
48
|
+
end
|
49
|
+
|
50
|
+
"base"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html data-controller="theme" data-theme-target="theme">
|
3
|
+
<head>
|
4
|
+
<title><%= @page_title.nil? ? "" : "#{@page_title} | " %><%= Rails.application.class %></title>
|
5
|
+
<%= csrf_meta_tags %>
|
6
|
+
<%= csp_meta_tag %>
|
7
|
+
|
8
|
+
<%= stylesheet_link_tag "application", media: "all", "data-turbolinks-track": "reload" %>
|
9
|
+
<%= javascript_include_tag "rtcl/application", media: "all", "data-turbolinks-track": "reload" %>
|
10
|
+
</head>
|
11
|
+
<body>
|
12
|
+
|
13
|
+
<%= render 'polivalente/shared/offline_indicator' %>
|
14
|
+
<%= render 'polivalente/shared/notices' %>
|
15
|
+
|
16
|
+
<%= yield %>
|
17
|
+
|
18
|
+
</body>
|
19
|
+
</html>
|
@@ -0,0 +1,33 @@
|
|
1
|
+
<%= turbo_frame_tag(dom_id(article), class: "relative colored-article p-5 rounded-lg shadow-md #{article.colors}") do %>
|
2
|
+
<div class="flex justify-between pb-3">
|
3
|
+
<p class="uppercase text-sm"><%= article.time_since_created %></p>
|
4
|
+
<div class="flex items-center space-x-1.5">
|
5
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" class="bi bi-collection-play" viewBox="0 0 16 16">
|
6
|
+
<path d="M2 3a.5.5 0 0 0 .5.5h11a.5.5 0 0 0 0-1h-11A.5.5 0 0 0 2 3zm2-2a.5.5 0 0 0 .5.5h7a.5.5 0 0 0 0-1h-7A.5.5 0 0 0 4 1zm2.765 5.576A.5.5 0 0 0 6 7v5a.5.5 0 0 0 .765.424l4-2.5a.5.5 0 0 0 0-.848l-4-2.5z"/>
|
7
|
+
<path d="M1.5 14.5A1.5 1.5 0 0 1 0 13V6a1.5 1.5 0 0 1 1.5-1.5h13A1.5 1.5 0 0 1 16 6v7a1.5 1.5 0 0 1-1.5 1.5h-13zm13-1a.5.5 0 0 0 .5-.5V6a.5.5 0 0 0-.5-.5h-13A.5.5 0 0 0 1 6v7a.5.5 0 0 0 .5.5h13z"/>
|
8
|
+
</svg>
|
9
|
+
<%= link_to polivalente.user_path(article.author), class: "secondary rounded-sm px-1 text-sm font-semibold" do %>
|
10
|
+
<%= article.author.name %>
|
11
|
+
<% end %>
|
12
|
+
</div>
|
13
|
+
</div>
|
14
|
+
|
15
|
+
<%= link_to article, class: "mt-2 block", data: "#{article.title}" do %>
|
16
|
+
<h3 class="pb-4 font-bold font-serif text-2xl md:truncate capitalize">
|
17
|
+
<%= article.title %>
|
18
|
+
</h3>
|
19
|
+
<% end %>
|
20
|
+
|
21
|
+
<p class="pb-6"><%= article.excerpt %></p>
|
22
|
+
|
23
|
+
<% if article.is_private %>
|
24
|
+
<div class="flex flex-row space-x-0.5 items-center absolute bottom-2">
|
25
|
+
|
26
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
|
27
|
+
<path fill-rule="evenodd" d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z" clip-rule="evenodd" />
|
28
|
+
</svg>
|
29
|
+
|
30
|
+
<span class="text-sm font-semibold"><%= t("views.visibility.private") %></span>
|
31
|
+
</div>
|
32
|
+
<% end %>
|
33
|
+
<% end %>
|
@@ -0,0 +1,69 @@
|
|
1
|
+
<%= form_with(model: article, :html => {class: 'form flex flex-col text-primary'}) do |form| %>
|
2
|
+
<% if article.errors.any? %>
|
3
|
+
<div id="error_explanation" class="text-primary">
|
4
|
+
<h2><%= pluralize(article.errors.count, "error") %> prohibited this article from being saved:</h2>
|
5
|
+
|
6
|
+
<ul>
|
7
|
+
<% article.errors.each do |error| %>
|
8
|
+
<li><%= error.full_message %></li>
|
9
|
+
<% end %>
|
10
|
+
</ul>
|
11
|
+
</div>
|
12
|
+
<% end %>
|
13
|
+
|
14
|
+
<div class="my-2">
|
15
|
+
<%= form.text_field :title, class: "input-base bg-primary text-secondary-x border-tertiary text-2xl leading-8 font-extrabold tracking-tight sm:text-4xl" %>
|
16
|
+
</div>
|
17
|
+
|
18
|
+
<div class="my-2">
|
19
|
+
<%= form.label :tag_list, "Tags (separated by commas)", class: "text-sm" %>
|
20
|
+
<%= form.text_field :tag_list, class: "input-base bg-primary border-tertiary leading-8 tracking-tight", data: {controller: "trix-autocomplete", trix_autocomplete_url_value: polivalente.autocomplete_tags_path, trix_autocomplete_auto_value: "true", trix_autocomplete_trigger_value: "#"} %>
|
21
|
+
|
22
|
+
<div class="text-xs lowercase pt-1.5">
|
23
|
+
<%= render "polivalente/tags/list", taggable: article %>
|
24
|
+
</div>
|
25
|
+
</div>
|
26
|
+
|
27
|
+
<div class="my-2 relative" data-controller="text-counter" data-text-counter-limit-value="144" data-text-counter-error-class="text-red-500 border-red-400 font-bold">
|
28
|
+
<%= form.label :excerpt, class: "text-sm" %>
|
29
|
+
<%= form.text_area :excerpt, class: "input-base bg-primary border-tertiary max-h-24", data: {text_counter_target: "source"}, rows: "3", min: "3", max: "144" %>
|
30
|
+
<span class="absolute text-sm border bg-secondary border-tertiary rounded px-2 right-2 bottom-5" data-text-counter-target="output"></span>
|
31
|
+
</div>
|
32
|
+
|
33
|
+
<div class="my-8 relative" data-controller="text-counter plyr" data-text-counter-word-mode-value="true" >
|
34
|
+
<%= form.rich_text_area :content, class: "input-base bg-primary border-tertiary", data: {controller: "trix-autocomplete", trix_autocomplete_url_value: polivalente.autocomplete_users_path, trix_autocomplete_trigger_value: "@", text_counter_target: "source", plyr_target: 'editor' } %>
|
35
|
+
<span class="absolute text-sm border border-tertiary bg-primary rounded px-2 right-2 bottom-2" data-text-counter-target="output"></span>
|
36
|
+
</div>
|
37
|
+
|
38
|
+
<div class="-mx-3 my-2">
|
39
|
+
<div class="md:w-full px-3">
|
40
|
+
<%= form.label :language, class: "block uppercase tracking-wide text-xs font-bold mb-2" %>
|
41
|
+
<div class="relative">
|
42
|
+
<%= form.select :language, options_for_select(Rtcl::Article.languages, selected: article.language_code), { :class => "block appearance-none w-full bg-secondary border border-tertiary py-3 px-4 pr-8 rounded" } %>
|
43
|
+
</div>
|
44
|
+
</div>
|
45
|
+
</div>
|
46
|
+
|
47
|
+
<div class="-mx-3 my-2">
|
48
|
+
<div class="md:w-full px-3">
|
49
|
+
<div class="flex items-center">
|
50
|
+
<%= form.check_box :is_private, class: "appearance-none bg-secondary border border-tertiary rounded focus:ring-indigo-500 h-4 w-4 mb-3 p-2" %>
|
51
|
+
<%= form.label :is_private, class: "block uppercase tracking-wide text-xs font-bold mb-3 ml-3" %>
|
52
|
+
</div>
|
53
|
+
</div>
|
54
|
+
</div>
|
55
|
+
|
56
|
+
<div class="actions">
|
57
|
+
<div class="flex justify-between">
|
58
|
+
<%= form.submit nil, class: "py-2 px-4 mb-6 bg-green-600 hover:bg-green-800 text-white text-sm font-bold rounded transition duration-200" %>
|
59
|
+
|
60
|
+
<div class="flex space-x-3">
|
61
|
+
<%= link_to "Delete", article_url, method: :delete, class: "py-2 px-4 mb-6 bg-red-600 hover:bg-red-800 text-white text-sm font-bold rounded transition duration-200" unless article.new_record? %>
|
62
|
+
|
63
|
+
<%= link_to 'Cancel', article.new_record? ? articles_url : article_url, class: "py-2 px-4 mb-6 bg-secondary border-2 hover:bg-tertiary text-primary text-sm font-bold rounded transition duration-200" %>
|
64
|
+
</div>
|
65
|
+
|
66
|
+
</div>
|
67
|
+
</div>
|
68
|
+
<% end %>
|
69
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
<section class="relative py-8 overflow-hidden">
|
2
|
+
<div class="relative px-4 sm:px-6 lg:px-8">
|
3
|
+
<div class="text-lg max-w-prose mx-auto">
|
4
|
+
<h1>
|
5
|
+
<span class="block text-center text-xs text-blue-600 font-semibold tracking-wide uppercase">
|
6
|
+
<%= link_to t("views.articles"), articles_path, class: "text-indigo-600 underline" %><span> - </span>
|
7
|
+
<%= @article.created_at.to_formatted_s(:long) %> <span> - </span>
|
8
|
+
<%= link_to @article.author.name, @article.author, class: "text-indigo-600 underline" %>
|
9
|
+
(<%= @article.time_since_created %>)
|
10
|
+
</span>
|
11
|
+
</h1>
|
12
|
+
</div>
|
13
|
+
<div class="mt-6 prose prose-indigo prose-xl text-primary mx-auto">
|
14
|
+
<%= render "rtcl/articles/form", article: @article %>
|
15
|
+
</div>
|
16
|
+
</div>
|
17
|
+
</section>
|
@@ -0,0 +1,32 @@
|
|
1
|
+
<%= turbo_stream_from "articles" %>
|
2
|
+
|
3
|
+
<main class="mx-auto px-5 md:px-10 lg:px-20">
|
4
|
+
<div class="pt-10 pb-12 px-4 sm:px-6 lg:pt-24 lg:pb-10 lg:px-8">
|
5
|
+
<div class="relative max-w-7xl mx-auto lg:max-w-7xl">
|
6
|
+
<div id="article-list-header">
|
7
|
+
<h2 class="text-4xl tracking-tight font-extrabold text-primary sm:text-6xl">
|
8
|
+
Blog
|
9
|
+
</h2>
|
10
|
+
<div class="mt-3 sm:mt-4 lg:grid lg:grid-cols-2 lg:gap-5 lg:items-center">
|
11
|
+
<p class="text-xl text-secondary">
|
12
|
+
Ideas worth sharing.
|
13
|
+
</p>
|
14
|
+
</div>
|
15
|
+
</div>
|
16
|
+
|
17
|
+
<div class="py-4 flex justify-between">
|
18
|
+
<%= render 'polivalente/tags/cloud', collection: Rtcl::Article.kept.accessible_by(current_ability) %>
|
19
|
+
|
20
|
+
<% if can? :create, Rtcl::Article %>
|
21
|
+
<%= link_to "New Article", new_article_path, class: "py-0.5 px-2 h-8 text-primary border-b hover:border-b-4 border-primary text-sm font-bold transition duration-200" %>
|
22
|
+
<% end %>
|
23
|
+
</div>
|
24
|
+
|
25
|
+
<%= turbo_frame_tag("articles", class: "py-4 grid gap-4 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3") do %>
|
26
|
+
<%= render @articles %>
|
27
|
+
<% end %>
|
28
|
+
|
29
|
+
<%== pagy_nav(@paginator) if @paginator.pages > 1 %>
|
30
|
+
</div>
|
31
|
+
</div>
|
32
|
+
</main>
|
@@ -0,0 +1,16 @@
|
|
1
|
+
<section class="relative py-8 overflow-hidden">
|
2
|
+
<div class="relative px-4 sm:px-6 lg:px-8">
|
3
|
+
<div class="text-lg max-w-prose mx-auto">
|
4
|
+
<h1>
|
5
|
+
<span class="block text-center text-xs text-blue-600 font-semibold tracking-wide uppercase">
|
6
|
+
<%= link_to t("views.articles"), articles_path, class: "text-indigo-600 underline" %><span> - </span>
|
7
|
+
<%= Time.now.to_formatted_s(:long) %> <span> - </span>
|
8
|
+
<%= link_to current_user.name, current_user, class: "text-indigo-600 underline" %>
|
9
|
+
</span>
|
10
|
+
</h1>
|
11
|
+
</div>
|
12
|
+
<div class="mt-6 prose prose-indigo prose-xl text-primary mx-auto">
|
13
|
+
<%= render "rtcl/articles/form", article: @article %>
|
14
|
+
</div>
|
15
|
+
</div>
|
16
|
+
</section>
|
@@ -0,0 +1,64 @@
|
|
1
|
+
<section class="relative py-8 overflow-hidden">
|
2
|
+
<div class="relative px-4 sm:px-6 lg:px-8 text-primary">
|
3
|
+
<div class="text-lg max-w-prose mx-auto">
|
4
|
+
<h1>
|
5
|
+
<div class="mt-8">
|
6
|
+
<div class="md:flex md:items-center md:justify-center">
|
7
|
+
<div class="md:flex-shrink-0">
|
8
|
+
<%= image_tag main_app.url_for(user_avatar(@article.author)), class: "mx-auto h-10 w-10 rounded-full" %>
|
9
|
+
</div>
|
10
|
+
<div class="mt-3 text-center md:mt-0 md:ml-4 md:flex md:items-center">
|
11
|
+
<%= link_to @article.author.name, polivalente.user_path(@article.author), class: "text-base font-medium text-indigo-600" %>
|
12
|
+
|
13
|
+
<svg class="hidden md:block mx-1 h-5 w-5 text-indigo-600" fill="currentColor" viewBox="0 0 20 20">
|
14
|
+
<path d="M11 0h3L9 20H6l5-20z" />
|
15
|
+
</svg>
|
16
|
+
|
17
|
+
<div class="text-base font-medium text-secondary"><%= @article.created_at.to_formatted_s(:short) %></div>
|
18
|
+
|
19
|
+
<svg class="hidden md:block mx-1 h-5 w-5 text-indigo-600" fill="currentColor" viewBox="0 0 20 20">
|
20
|
+
<path d="M11 0h3L9 20H6l5-20z" />
|
21
|
+
</svg>
|
22
|
+
|
23
|
+
<%= link_to "All articles", articles_path, class: "text-base text-indigo-600 font-medium" %>
|
24
|
+
|
25
|
+
<% if can? :manage, @article %>
|
26
|
+
<svg class="hidden md:block mx-1 h-5 w-5 text-indigo-600" fill="currentColor" viewBox="0 0 20 20">
|
27
|
+
<path d="M11 0h3L9 20H6l5-20z" />
|
28
|
+
</svg>
|
29
|
+
|
30
|
+
<%= link_to "Edit", edit_article_path, class: "text-base text-indigo-600" %>
|
31
|
+
<% end %>
|
32
|
+
</div>
|
33
|
+
</div>
|
34
|
+
</div>
|
35
|
+
|
36
|
+
<p class="my-4 block text-3xl font-serif text-center leading-8 font-extrabold tracking-tight sm:text-5xl capitalize">
|
37
|
+
<%= @article.title %>
|
38
|
+
</p>
|
39
|
+
|
40
|
+
<div class="text-center">
|
41
|
+
<%= render "polivalente/tags/list", taggable: @article %>
|
42
|
+
</div>
|
43
|
+
</h1>
|
44
|
+
</div>
|
45
|
+
|
46
|
+
<div class="text-secondary text-center text-sm pt-2">
|
47
|
+
<% if @article.is_private %>
|
48
|
+
<p>This is a private article</p>
|
49
|
+
<% end %>
|
50
|
+
</div>
|
51
|
+
|
52
|
+
<article class="mt-6 text-primary prose prose-indigo prose-xl mx-auto" data-controller="trix-clipboard trix-highlight trix-color trix-plyr">
|
53
|
+
<%= @article.content %>
|
54
|
+
</article>
|
55
|
+
|
56
|
+
<div class="pt-16">
|
57
|
+
<div class="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
58
|
+
<div class="relative py-16 px-8 bg-secondary rounded-xl overflow-hidden lg:px-16 border-tertiary">
|
59
|
+
<%= render 'rtcl/comments/comments' , commentable: @article %>
|
60
|
+
</div>
|
61
|
+
</div>
|
62
|
+
</div>
|
63
|
+
</div>
|
64
|
+
</section>
|
@@ -0,0 +1,47 @@
|
|
1
|
+
<div class="py-2 mb-2 flex flex-col space-y-4 rounded">
|
2
|
+
<div class="flex flex-row items-center space-x-2 justify-between">
|
3
|
+
<p class="text-xs text-secondary">
|
4
|
+
<% if defined? user %>
|
5
|
+
<%= link_to user do %>
|
6
|
+
<span class="font-semibold text-blue-600"><%= user.name %></span>
|
7
|
+
<% end %>
|
8
|
+
<% else %>
|
9
|
+
<% if can? :read, comment.commentable %>
|
10
|
+
<%= link_to comment.commentable do %>
|
11
|
+
<span class="font-semibold text-blue-600 capitalize"><%= comment.commentable.title %></span>
|
12
|
+
<% end %>
|
13
|
+
<% else %>
|
14
|
+
<span class="font-semibold"><%= comment.commentable.title %></span>
|
15
|
+
<% end %>
|
16
|
+
<% end %>
|
17
|
+
|
18
|
+
<span class="italic">
|
19
|
+
on <%= comment.created_at.to_formatted_s(:long) %>
|
20
|
+
</span>
|
21
|
+
</p>
|
22
|
+
|
23
|
+
<div class="flex flex-row items-center space-x-2 text-sm">
|
24
|
+
<% if can? :manage, comment %>
|
25
|
+
<%= button_to url_for([comment.commentable, comment]), method: :delete, class: "button-red" do %>
|
26
|
+
<svg class="w-4 h-4 text-red-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
27
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
28
|
+
</svg>
|
29
|
+
<% end %>
|
30
|
+
<% end %>
|
31
|
+
|
32
|
+
<%#= link_to 'Reply', '', class: "text-blue-600 font-semibold" %>
|
33
|
+
</div>
|
34
|
+
</div>
|
35
|
+
|
36
|
+
<div class="flex flex-row align-center space-x-1.5">
|
37
|
+
<% if defined? user %>
|
38
|
+
<%= link_to user_url(user), class: "rounded-full flex flex-shrink-0 p-0" do %>
|
39
|
+
<%= image_tag main_app.url_for(user_avatar(user)), class: "w-8 h-8 rounded-full object-fit", alt: user.name %>
|
40
|
+
<% end %>
|
41
|
+
<% end %>
|
42
|
+
|
43
|
+
<article class="text-primary">
|
44
|
+
<%= comment.content %>
|
45
|
+
</article>
|
46
|
+
</div>
|
47
|
+
</div>
|
@@ -0,0 +1,18 @@
|
|
1
|
+
<div class="mx-auto comments--section py-2">
|
2
|
+
<p class="text-2xl font-semibold py-2">Comments</p>
|
3
|
+
|
4
|
+
<% if commentable.comments.latest.each do |comment| %>
|
5
|
+
<%= render "rtcl/comments/comment", comment: comment, commentable: commentable, user: comment.user %>
|
6
|
+
<% end.empty? %>
|
7
|
+
<p class="text-secondary">No comments yet.</p>
|
8
|
+
<% end %>
|
9
|
+
|
10
|
+
<% if current_user %>
|
11
|
+
<%= render 'rtcl/comments/form', commentable: commentable %>
|
12
|
+
<% else %>
|
13
|
+
<p class="text-secondary pt-12">
|
14
|
+
Join the conversation by
|
15
|
+
<%= link_to 'signing in.', main_app.new_user_session_path, class: "text-blue-600" %>
|
16
|
+
</p>
|
17
|
+
<% end %>
|
18
|
+
</div>
|
@@ -0,0 +1,11 @@
|
|
1
|
+
<div class="new--comment-form py-4">
|
2
|
+
<%= form_for [commentable, Polivalente::Comment.new] do |form| %>
|
3
|
+
<div class="mb-2">
|
4
|
+
<%= form.rich_text_area :content, data: { controller: "autocomplete trix-attachment-blocker", autocomplete_url_value: polivalente.autocomplete_users_path } %>
|
5
|
+
</div>
|
6
|
+
|
7
|
+
<div class="mb-2">
|
8
|
+
<%= form.submit nil, class: "mb-6 px-2 py-2 bg-indigo-600 hover:bg-indigo-700 text-white font-bold rounded shadow-lg hover:shadow-xl transition duration-200" %>
|
9
|
+
</div>
|
10
|
+
<% end %>
|
11
|
+
</div>
|
@@ -0,0 +1 @@
|
|
1
|
+
<h1>Comments#Show</h1>
|
data/config/routes.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
Rtcl::Engine.routes.draw do
|
2
|
+
root to: "articles#index"
|
3
|
+
|
4
|
+
resources :articles do
|
5
|
+
resources :comments, only: [:create, :show, :destroy], module: :articles
|
6
|
+
end
|
7
|
+
|
8
|
+
get 'articles/tags/:tag', to: 'articles#index', as: :tag
|
9
|
+
|
10
|
+
mount Polivalente::Engine => ""
|
11
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class CreateArticles < ActiveRecord::Migration[7.0]
|
2
|
+
def change
|
3
|
+
create_table :articles do |t|
|
4
|
+
t.references :author, null: false, index: true, foreign_key: { to_table: :users, on_delete: :cascade }
|
5
|
+
t.string :title, null: false
|
6
|
+
t.string :language, null: true
|
7
|
+
t.string :excerpt, null: true, limit: 144
|
8
|
+
t.string :content_hash, null: false, index: true
|
9
|
+
t.json :metadata, null: false, default: "{}"
|
10
|
+
t.boolean :is_private, null: false, index: true, default: false
|
11
|
+
t.datetime :discarded_at, null: true, index: true
|
12
|
+
|
13
|
+
t.timestamps
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Rtcl
|
2
|
+
module Generators
|
3
|
+
class InstallGenerator < ::Rails::Generators::Base
|
4
|
+
source_root File.expand_path("../../templates", __FILE__)
|
5
|
+
|
6
|
+
def copy_migrations
|
7
|
+
# Copy all migrations including ActionMailbox, ActionText, and ActiveStorage
|
8
|
+
rails_command "railties:install:migrations FROM=rtcl", inline: true
|
9
|
+
end
|
10
|
+
|
11
|
+
def copy_initializer
|
12
|
+
template "rtcl.rb", "config/initializers/rtcl.rb"
|
13
|
+
end
|
14
|
+
|
15
|
+
def copy_active_model_serializers_initializer
|
16
|
+
template "active_model_serializers.rb", "config/initializers/active_model_serializers.rb"
|
17
|
+
end
|
18
|
+
|
19
|
+
def add_route
|
20
|
+
route "mount Rtcl::Engine => '/blog'"
|
21
|
+
end
|
22
|
+
|
23
|
+
def show_readme
|
24
|
+
readme "README" if behavior == :invoke
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
ActiveModelSerializers.config.adapter = :json_api
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Rtcl.configure do |config|
|
4
|
+
# ==> Controller configuration
|
5
|
+
config.base_controller = "::ApplicationController"
|
6
|
+
config.base_api_controller = "::ApplicationController"
|
7
|
+
|
8
|
+
# ==> Tag configuration
|
9
|
+
config.supported_tags = []
|
10
|
+
end
|
data/lib/rtcl/engine.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
module Rtcl
|
2
|
+
class Engine < ::Rails::Engine
|
3
|
+
isolate_namespace Rtcl
|
4
|
+
|
5
|
+
config.generators do |g|
|
6
|
+
g.scaffold_stylesheet false
|
7
|
+
g.assets false
|
8
|
+
g.test_framework :rspec
|
9
|
+
g.fixture_replacement :factory_bot
|
10
|
+
g.factory_bot dir: "spec/factories"
|
11
|
+
end
|
12
|
+
|
13
|
+
# Loads engine helpers in main application
|
14
|
+
initializer "local_helper.action_controller" do
|
15
|
+
ActiveSupport.on_load :action_controller do
|
16
|
+
helper Devise::Engine.helpers
|
17
|
+
helper Polivalente::Engine.helpers
|
18
|
+
helper Rtcl::Engine.helpers
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
initializer "rtcl.assets" do |app|
|
23
|
+
if Rails.application.config.respond_to?(:assets)
|
24
|
+
app.config.assets.precompile << "rtcl/application.js"
|
25
|
+
app.config.assets.precompile << "rtcl/application.css"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Do not prefix table names with `rtcl_`
|
30
|
+
def self.table_name_prefix
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/rtcl/railtie.rb
ADDED
data/lib/rtcl/version.rb
ADDED
data/lib/rtcl.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require "rtcl/version"
|
2
|
+
require "rtcl/railtie"
|
3
|
+
require "rtcl/engine"
|
4
|
+
require "rtcl/configuration"
|
5
|
+
|
6
|
+
# Third-party
|
7
|
+
|
8
|
+
require "active_model_serializers"
|
9
|
+
require "cancancan"
|
10
|
+
require "hotwire-rails"
|
11
|
+
require "image_processing"
|
12
|
+
require "jbuilder"
|
13
|
+
require "language_list"
|
14
|
+
require "pagy"
|
15
|
+
require "polivalente"
|
16
|
+
|
17
|
+
module Rtcl
|
18
|
+
class << self
|
19
|
+
attr_reader :config
|
20
|
+
|
21
|
+
def configure
|
22
|
+
@config = Configuration.new
|
23
|
+
yield config
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Do not prefix table names with `rtcl_`
|
28
|
+
def self.table_name_prefix
|
29
|
+
end
|
30
|
+
end
|