rtcl 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|