rtcl 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +28 -0
  4. data/Rakefile +14 -0
  5. data/app/assets/config/rtcl_manifest.js +3 -0
  6. data/app/assets/images/user-sample.jpg +0 -0
  7. data/app/assets/javascripts/rtcl/application.js +174 -0
  8. data/app/assets/javascripts/rtcl/name-that-color.js +1 -0
  9. data/app/assets/stylesheets/rtcl/application.css +7 -0
  10. data/app/assets/stylesheets/rtcl/highlight-android.css +1 -0
  11. data/app/assets/stylesheets/rtcl/styles.css +2578 -0
  12. data/app/assets/stylesheets/rtcl/trix.css +556 -0
  13. data/app/controllers/rtcl/application_controller.rb +18 -0
  14. data/app/controllers/rtcl/articles/comments_controller.rb +11 -0
  15. data/app/controllers/rtcl/articles_controller.rb +65 -0
  16. data/app/controllers/rtcl/comments_controller.rb +27 -0
  17. data/app/helpers/rtcl/application_helper.rb +4 -0
  18. data/app/jobs/rtcl/application_job.rb +4 -0
  19. data/app/mailers/rtcl/application_mailer.rb +6 -0
  20. data/app/models/ability.rb +20 -0
  21. data/app/models/concerns/rtcl/blogger.rb +9 -0
  22. data/app/models/rtcl/application_record.rb +5 -0
  23. data/app/models/rtcl/article.rb +53 -0
  24. data/app/views/layouts/rtcl/application.html.erb +19 -0
  25. data/app/views/rtcl/articles/_article.html.erb +33 -0
  26. data/app/views/rtcl/articles/_article.json.jbuilder +5 -0
  27. data/app/views/rtcl/articles/_form.html.erb +69 -0
  28. data/app/views/rtcl/articles/edit.html.erb +17 -0
  29. data/app/views/rtcl/articles/index.html.erb +32 -0
  30. data/app/views/rtcl/articles/new.html.erb +16 -0
  31. data/app/views/rtcl/articles/show.html.erb +64 -0
  32. data/app/views/rtcl/comments/_comment.html.erb +47 -0
  33. data/app/views/rtcl/comments/_comments.html.erb +18 -0
  34. data/app/views/rtcl/comments/_form.html.erb +11 -0
  35. data/app/views/rtcl/comments/new.html.erb +7 -0
  36. data/app/views/rtcl/comments/show.html.erb +1 -0
  37. data/config/routes.rb +11 -0
  38. data/db/migrate/20220208044728_create_articles.rb +16 -0
  39. data/lib/generators/rtcl/install/install_generator.rb +28 -0
  40. data/lib/generators/rtcl/templates/active_model_serializers.rb +1 -0
  41. data/lib/generators/rtcl/templates/rtcl.rb +10 -0
  42. data/lib/rtcl/configuration.rb +7 -0
  43. data/lib/rtcl/engine.rb +33 -0
  44. data/lib/rtcl/railtie.rb +4 -0
  45. data/lib/rtcl/version.rb +3 -0
  46. data/lib/rtcl.rb +30 -0
  47. data/lib/tasks/rtcl_tasks.rake +4 -0
  48. 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,9 @@
1
+ module Rtcl
2
+ module Blogger
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ has_many :articles, foreign_key: :author_id, class_name: "Rtcl::Article"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,5 @@
1
+ module Rtcl
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ 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,5 @@
1
+ json.extract! article, :id, :author_id, :title, :created_at, :updated_at
2
+ json.name article.title
3
+ json.sgid article.attachable_sgid
4
+ json.content render(partial: "rtcl/articles/article", locals: { article: article }, formats: [:html])
5
+ json.url article_url(article)
@@ -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,7 @@
1
+ <% if !is_logged_in? %>
2
+ <%= render 'rtcl/comments/form', commentable: commentable %>
3
+ <% else %>
4
+ <p class="text-secondary">
5
+ Join the conversation by <%= link_to 'signing in', sign_in_path, class: "text-blue-600" %>.
6
+ </p>
7
+ <% end %>
@@ -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
@@ -0,0 +1,7 @@
1
+ module Rtcl
2
+ class Configuration
3
+ attr_accessor :base_controller
4
+ attr_accessor :base_api_controller
5
+ attr_accessor :supported_tags
6
+ end
7
+ end
@@ -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
@@ -0,0 +1,4 @@
1
+ module Rtcl
2
+ class Railtie < ::Rails::Railtie
3
+ end
4
+ end
@@ -0,0 +1,3 @@
1
+ module Rtcl
2
+ VERSION = "1.0.0"
3
+ end
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
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :rtcl do
3
+ # # Task goes here
4
+ # end