katalyst-content 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +95 -0
- data/app/assets/config/katalyst-content.js +1 -0
- data/app/assets/javascripts/controllers/content/editor/container_controller.js +113 -0
- data/app/assets/javascripts/controllers/content/editor/item_controller.js +45 -0
- data/app/assets/javascripts/controllers/content/editor/list_controller.js +105 -0
- data/app/assets/javascripts/controllers/content/editor/new_item_controller.js +12 -0
- data/app/assets/javascripts/controllers/content/editor/status_bar_controller.js +22 -0
- data/app/assets/javascripts/utils/content/editor/container.js +52 -0
- data/app/assets/javascripts/utils/content/editor/item.js +245 -0
- data/app/assets/javascripts/utils/content/editor/rules-engine.js +177 -0
- data/app/assets/stylesheets/katalyst/content/_index.scss +31 -0
- data/app/assets/stylesheets/katalyst/content/editor/_icon.scss +17 -0
- data/app/assets/stylesheets/katalyst/content/editor/_index.scss +145 -0
- data/app/assets/stylesheets/katalyst/content/editor/_item-actions.scss +93 -0
- data/app/assets/stylesheets/katalyst/content/editor/_item-rules.scss +19 -0
- data/app/assets/stylesheets/katalyst/content/editor/_new-items.scss +39 -0
- data/app/assets/stylesheets/katalyst/content/editor/_status-bar.scss +87 -0
- data/app/controllers/katalyst/content/application_controller.rb +8 -0
- data/app/controllers/katalyst/content/items_controller.rb +70 -0
- data/app/helpers/katalyst/content/application_helper.rb +8 -0
- data/app/helpers/katalyst/content/editor/base.rb +44 -0
- data/app/helpers/katalyst/content/editor/container.rb +41 -0
- data/app/helpers/katalyst/content/editor/item.rb +67 -0
- data/app/helpers/katalyst/content/editor/list.rb +41 -0
- data/app/helpers/katalyst/content/editor/new_item.rb +53 -0
- data/app/helpers/katalyst/content/editor/status_bar.rb +57 -0
- data/app/helpers/katalyst/content/editor_helper.rb +42 -0
- data/app/models/concerns/katalyst/content/container.rb +100 -0
- data/app/models/concerns/katalyst/content/garbage_collection.rb +31 -0
- data/app/models/concerns/katalyst/content/has_tree.rb +63 -0
- data/app/models/concerns/katalyst/content/version.rb +33 -0
- data/app/models/katalyst/content/content.rb +21 -0
- data/app/models/katalyst/content/item.rb +36 -0
- data/app/models/katalyst/content/node.rb +21 -0
- data/app/models/katalyst/content/types/nodes_type.rb +42 -0
- data/app/views/active_storage/blobs/_blob.html.erb +14 -0
- data/app/views/katalyst/content/contents/_content.html+form.erb +39 -0
- data/app/views/katalyst/content/contents/_content.html.erb +5 -0
- data/app/views/katalyst/content/editor/_item.html.erb +11 -0
- data/app/views/katalyst/content/editor/_list_item.html.erb +14 -0
- data/app/views/katalyst/content/editor/_new_item.html.erb +3 -0
- data/app/views/katalyst/content/editor/_new_items.html.erb +5 -0
- data/app/views/katalyst/content/items/_item.html+form.erb +34 -0
- data/app/views/katalyst/content/items/_item.html.erb +3 -0
- data/app/views/katalyst/content/items/edit.html.erb +4 -0
- data/app/views/katalyst/content/items/new.html.erb +4 -0
- data/app/views/katalyst/content/items/update.turbo_stream.erb +7 -0
- data/app/views/layouts/action_text/contents/_content.html.erb +3 -0
- data/config/importmap.rb +8 -0
- data/config/locales/en.yml +12 -0
- data/config/routes.rb +3 -0
- data/db/migrate/20220913003839_create_katalyst_content_items.rb +17 -0
- data/lib/katalyst/content/config.rb +18 -0
- data/lib/katalyst/content/engine.rb +36 -0
- data/lib/katalyst/content/version.rb +7 -0
- data/lib/katalyst/content.rb +19 -0
- data/lib/tasks/yarn.rake +18 -0
- data/spec/factories/katalyst/content/items.rb +16 -0
- metadata +103 -0
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Katalyst
|
4
|
+
module Content
|
5
|
+
module Container
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do # rubocop:disable Metrics/BlockLength
|
9
|
+
include Katalyst::Content::GarbageCollection
|
10
|
+
|
11
|
+
before_destroy :unset_versions
|
12
|
+
|
13
|
+
belongs_to :draft_version,
|
14
|
+
autosave: true,
|
15
|
+
class_name: "#{name}::Version",
|
16
|
+
inverse_of: :parent,
|
17
|
+
optional: true
|
18
|
+
belongs_to :published_version,
|
19
|
+
class_name: "#{name}::Version",
|
20
|
+
inverse_of: :parent,
|
21
|
+
optional: true
|
22
|
+
|
23
|
+
delegate :nodes, :items, :tree, to: :published_version, prefix: :published, allow_nil: true
|
24
|
+
delegate :nodes, :items, :tree, to: :draft_version, prefix: :draft, allow_nil: true
|
25
|
+
|
26
|
+
has_many :versions,
|
27
|
+
class_name: "#{name}::Version",
|
28
|
+
dependent: :delete_all,
|
29
|
+
foreign_key: :parent_id,
|
30
|
+
inverse_of: :parent,
|
31
|
+
validate: true do
|
32
|
+
def active
|
33
|
+
parent = proxy_association.owner
|
34
|
+
where(id: [parent.published_version_id, parent.draft_version_id].uniq.compact)
|
35
|
+
end
|
36
|
+
|
37
|
+
def inactive
|
38
|
+
parent = proxy_association.owner
|
39
|
+
where.not(id: [parent.published_version_id, parent.draft_version_id].uniq.compact)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
has_many :items,
|
44
|
+
as: :container,
|
45
|
+
autosave: true,
|
46
|
+
class_name: "Katalyst::Content::Item",
|
47
|
+
dependent: :destroy,
|
48
|
+
validate: true
|
49
|
+
end
|
50
|
+
|
51
|
+
# A resource is in draft mode if it has an unpublished draft or it has no published version.
|
52
|
+
# @return the current state of the resource, either `published` or `draft`
|
53
|
+
def state
|
54
|
+
if published_version_id && published_version_id == draft_version_id
|
55
|
+
:published
|
56
|
+
else
|
57
|
+
:draft
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Promotes the draft version to become the published version
|
62
|
+
def publish!
|
63
|
+
update!(published_version: draft_version)
|
64
|
+
self
|
65
|
+
end
|
66
|
+
|
67
|
+
# Reverts the draft version to the current published version
|
68
|
+
def revert!
|
69
|
+
update!(draft_version: published_version)
|
70
|
+
self
|
71
|
+
end
|
72
|
+
|
73
|
+
# Updates the current draft version with new structure. Attributes should be structural information about the
|
74
|
+
# items, e.g. `{index => {id:, depth:}` or `[{id:, depth:}]`.
|
75
|
+
#
|
76
|
+
# This method conforms to the behaviour of `accepts_nested_attributes_for` so that it can be used with rails form
|
77
|
+
# helpers.
|
78
|
+
def items_attributes=(attributes)
|
79
|
+
next_version.nodes = attributes
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def unset_versions
|
85
|
+
update(draft_version: nil, published_version: nil)
|
86
|
+
end
|
87
|
+
|
88
|
+
# Returns an unsaved copy of draft version for accumulating changes.
|
89
|
+
def next_version
|
90
|
+
if draft_version.nil?
|
91
|
+
build_draft_version
|
92
|
+
elsif draft_version.persisted?
|
93
|
+
self.draft_version = draft_version.dup
|
94
|
+
else
|
95
|
+
draft_version
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Katalyst
|
4
|
+
module Content
|
5
|
+
module GarbageCollection
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
after_update :remove_stale_versions
|
10
|
+
end
|
11
|
+
|
12
|
+
def remove_stale_versions
|
13
|
+
transaction do
|
14
|
+
# find all the versions that are not linked to the record
|
15
|
+
orphaned_versions = versions.inactive
|
16
|
+
|
17
|
+
next unless orphaned_versions.any?
|
18
|
+
|
19
|
+
# find items that are not included in active versions
|
20
|
+
orphaned_items = items.pluck(:id) - versions.active.pluck(:nodes).flat_map { |k| k.map(&:id) }.uniq
|
21
|
+
|
22
|
+
# delete orphaned items with a 2 hour grace period to allow for in-progress editing
|
23
|
+
items.where(id: orphaned_items, updated_at: ..2.hours.ago).destroy_all
|
24
|
+
|
25
|
+
# delete orphaned versions without a grace period as they can only be created by updates
|
26
|
+
orphaned_versions.destroy_all
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Katalyst
|
4
|
+
module Content
|
5
|
+
module HasTree
|
6
|
+
# Constructs a tree from an ordered list of items with depth.
|
7
|
+
# * items that have higher depth than their predecessor are nested as `children`.
|
8
|
+
# * items with the same depth become siblings.
|
9
|
+
def tree
|
10
|
+
items.reduce(Builder.new, &:add).tree
|
11
|
+
end
|
12
|
+
|
13
|
+
class Builder
|
14
|
+
attr_reader :current, :depth, :children, :tree
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
@depth = 0
|
18
|
+
@children = @tree = []
|
19
|
+
end
|
20
|
+
|
21
|
+
def add(node)
|
22
|
+
if node.depth == depth
|
23
|
+
node.parent = current
|
24
|
+
children << node
|
25
|
+
self
|
26
|
+
elsif node.depth > depth
|
27
|
+
push(children.last)
|
28
|
+
add(node)
|
29
|
+
else
|
30
|
+
pop
|
31
|
+
add(node)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
attr_writer :current, :depth, :children
|
38
|
+
|
39
|
+
# Add node to the top of builder stacks
|
40
|
+
def push(node)
|
41
|
+
self.depth += 1
|
42
|
+
self.current = node
|
43
|
+
self.children = node.children = []
|
44
|
+
node
|
45
|
+
end
|
46
|
+
|
47
|
+
# Remove current from builder stack
|
48
|
+
def pop
|
49
|
+
previous = current
|
50
|
+
self.depth -= 1
|
51
|
+
if depth.zero?
|
52
|
+
self.current = nil
|
53
|
+
self.children = tree
|
54
|
+
else
|
55
|
+
self.current = previous.parent
|
56
|
+
self.children = current.children
|
57
|
+
end
|
58
|
+
previous
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Katalyst
|
4
|
+
module Content
|
5
|
+
module Version
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do # rubocop:disable Metrics/BlockLength
|
9
|
+
include Katalyst::Content::HasTree
|
10
|
+
|
11
|
+
# rubocop:disable Rails/ReflectionClassName
|
12
|
+
belongs_to :parent, class_name: name.gsub(/::Version$/, ""), inverse_of: :versions
|
13
|
+
# rubocop:enable Rails/ReflectionClassName
|
14
|
+
|
15
|
+
attribute :nodes, Katalyst::Content::Types::NodesType.new, default: -> { [] }
|
16
|
+
end
|
17
|
+
|
18
|
+
def items
|
19
|
+
# support building menus in memory
|
20
|
+
# requires that items are added in order and index and depth are set
|
21
|
+
return parent.items unless parent.persisted?
|
22
|
+
|
23
|
+
items = parent.items.where(id: nodes.map(&:id)).index_by(&:id)
|
24
|
+
nodes.map do |node|
|
25
|
+
item = items[node.id]
|
26
|
+
item.index = node.index
|
27
|
+
item.depth = node.depth
|
28
|
+
item
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Katalyst
|
4
|
+
module Content
|
5
|
+
class Content < Item
|
6
|
+
has_rich_text :content
|
7
|
+
|
8
|
+
validates :content, presence: true
|
9
|
+
|
10
|
+
def initialize_copy(source)
|
11
|
+
super
|
12
|
+
|
13
|
+
self.content = source.content&.body if source.content.is_a?(ActionText::RichText)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.permitted_params
|
17
|
+
super + %i[content]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Katalyst
|
4
|
+
module Content
|
5
|
+
# STI base class for content items
|
6
|
+
class Item < ApplicationRecord
|
7
|
+
belongs_to :container, polymorphic: true
|
8
|
+
|
9
|
+
validates :heading, presence: true
|
10
|
+
validates :background, presence: true, inclusion: { in: Katalyst::Content.config.backgrounds }
|
11
|
+
|
12
|
+
after_initialize :initialize_tree
|
13
|
+
|
14
|
+
attr_accessor :parent, :children, :index, :depth
|
15
|
+
|
16
|
+
def self.permitted_params
|
17
|
+
%i[
|
18
|
+
container_type
|
19
|
+
container_id
|
20
|
+
type
|
21
|
+
heading
|
22
|
+
show_heading
|
23
|
+
background
|
24
|
+
visible
|
25
|
+
]
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def initialize_tree
|
31
|
+
self.parent ||= nil
|
32
|
+
self.children ||= []
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Katalyst
|
4
|
+
module Content
|
5
|
+
# Data class for representing structural information about an item
|
6
|
+
class Node
|
7
|
+
include ActiveModel::Model
|
8
|
+
include ActiveModel::Attributes
|
9
|
+
|
10
|
+
attribute :id, :integer
|
11
|
+
attribute :index, :integer
|
12
|
+
attribute :depth, :integer, default: 0
|
13
|
+
|
14
|
+
attr_accessor :item
|
15
|
+
|
16
|
+
def as_json
|
17
|
+
attributes.slice("id", "depth").as_json
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Katalyst
|
4
|
+
module Content
|
5
|
+
module Types
|
6
|
+
# Data serialization/deserialization for Katalyst::Content structural data
|
7
|
+
class NodesType < ActiveRecord::Type::Json
|
8
|
+
def serialize(value)
|
9
|
+
super(value.as_json)
|
10
|
+
end
|
11
|
+
|
12
|
+
def deserialize(value)
|
13
|
+
case value
|
14
|
+
when nil
|
15
|
+
nil
|
16
|
+
when String
|
17
|
+
deserialize(super)
|
18
|
+
when Hash
|
19
|
+
deserialize_params(value)
|
20
|
+
when Array
|
21
|
+
deserialize_array(value)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
# Deserialize a params-style array, e.g. "0" => { ... }
|
28
|
+
def deserialize_params(value)
|
29
|
+
value.map do |index, attributes|
|
30
|
+
Node.new(index: index, **attributes)
|
31
|
+
end.select(&:id).sort_by(&:index)
|
32
|
+
end
|
33
|
+
|
34
|
+
def deserialize_array(value)
|
35
|
+
value.map.with_index do |attributes, index|
|
36
|
+
Node.new(index: index, **attributes)
|
37
|
+
end.select(&:id).sort_by(&:index)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<figure class="attachment attachment--<%= blob.representable? ? "preview" : "file" %> attachment--<%= blob.filename.extension %>">
|
2
|
+
<% if blob.representable? %>
|
3
|
+
<%= image_tag blob.representation(resize_to_limit: local_assigns[:in_gallery] ? [ 800, 600 ] : [ 1024, 768 ]) %>
|
4
|
+
<% end %>
|
5
|
+
|
6
|
+
<figcaption class="attachment__caption">
|
7
|
+
<% if caption = blob.try(:caption) %>
|
8
|
+
<%= caption %>
|
9
|
+
<% else %>
|
10
|
+
<span class="attachment__name"><%= blob.filename %></span>
|
11
|
+
<span class="attachment__size"><%= number_to_human_size blob.byte_size %></span>
|
12
|
+
<% end %>
|
13
|
+
</figcaption>
|
14
|
+
</figure>
|
@@ -0,0 +1,39 @@
|
|
1
|
+
<%= form_with model: content, scope: :item, url: path do |form| %>
|
2
|
+
<%= form.hidden_field :container_type %>
|
3
|
+
<%= form.hidden_field :container_id %>
|
4
|
+
<%= form.hidden_field :type %>
|
5
|
+
|
6
|
+
<%= tag.div class: "errors" do %>
|
7
|
+
<% content.errors.full_messages.each do |error| %>
|
8
|
+
<li class="error"><%= error %></li>
|
9
|
+
<% end %>
|
10
|
+
<% end if content.errors.any? %>
|
11
|
+
|
12
|
+
<div class="field">
|
13
|
+
<%= form.label :heading %>
|
14
|
+
<%= form.text_field :heading %>
|
15
|
+
</div>
|
16
|
+
|
17
|
+
<div class="field">
|
18
|
+
<%= form.label :show_heading %>
|
19
|
+
<%= form.check_box :show_heading %>
|
20
|
+
</div>
|
21
|
+
|
22
|
+
<div class="field">
|
23
|
+
<%= form.label :background %>
|
24
|
+
<%= form.select :background, Katalyst::Content.config.backgrounds %>
|
25
|
+
</div>
|
26
|
+
|
27
|
+
<div class="field">
|
28
|
+
<%= form.label :visible %>
|
29
|
+
<%= form.check_box :visible %>
|
30
|
+
</div>
|
31
|
+
|
32
|
+
<div class="field">
|
33
|
+
<%= form.label :content %>
|
34
|
+
<%= form.rich_text_area :content %>
|
35
|
+
</div>
|
36
|
+
|
37
|
+
<%= form.submit "Done" %>
|
38
|
+
<%= link_to "Discard", :back %>
|
39
|
+
<% end %>
|
@@ -0,0 +1,11 @@
|
|
1
|
+
<%= content_editor_item item: item do |builder| %>
|
2
|
+
<div class="tree" data-invisible="<%= !item.visible? %>">
|
3
|
+
<%= builder.accordion_actions %>
|
4
|
+
|
5
|
+
<span role="img" value="<%= item.model_name.to_param %>" title="Type"></span>
|
6
|
+
<h4 class="heading" title="<%= item.heading %>"><%= item.heading %></h4>
|
7
|
+
<span role="img" value="invisible" title="Hidden"></span>
|
8
|
+
</div>
|
9
|
+
|
10
|
+
<%= builder.item_actions %>
|
11
|
+
<% end %>
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<li draggable="true"
|
2
|
+
data-content-item
|
3
|
+
data-content-item-id="<%= item.id %>"
|
4
|
+
data-content-index="<%= item.index %>"
|
5
|
+
data-content-depth="<%= item.depth %>"
|
6
|
+
data-deny-de-nest
|
7
|
+
data-deny-nest
|
8
|
+
data-deny-collapse
|
9
|
+
data-deny-expand
|
10
|
+
data-deny-remove
|
11
|
+
data-deny-drag
|
12
|
+
data-deny-edit>
|
13
|
+
<%= yield %>
|
14
|
+
</li>
|
@@ -0,0 +1,34 @@
|
|
1
|
+
<%= form_with model: item, scope: :item, url: path do |form| %>
|
2
|
+
<%= form.hidden_field :container_type %>
|
3
|
+
<%= form.hidden_field :container_id %>
|
4
|
+
<%= form.hidden_field :type %>
|
5
|
+
|
6
|
+
<%= tag.div class: "errors" do %>
|
7
|
+
<% item.errors.full_messages.each do |error| %>
|
8
|
+
<li class="error"><%= error %></li>
|
9
|
+
<% end %>
|
10
|
+
<% end if item.errors.any? %>
|
11
|
+
|
12
|
+
<div class="field">
|
13
|
+
<%= form.label :heading %>
|
14
|
+
<%= form.text_field :heading %>
|
15
|
+
</div>
|
16
|
+
|
17
|
+
<div class="field">
|
18
|
+
<%= form.label :show_heading %>
|
19
|
+
<%= form.check_box :show_heading %>
|
20
|
+
</div>
|
21
|
+
|
22
|
+
<div class="field">
|
23
|
+
<%= form.label :background %>
|
24
|
+
<%= form.select :background, Katalyst::Content.config.backgrounds %>
|
25
|
+
</div>
|
26
|
+
|
27
|
+
<div class="field">
|
28
|
+
<%= form.label :visible %>
|
29
|
+
<%= form.check_box :visible %>
|
30
|
+
</div>
|
31
|
+
|
32
|
+
<%= form.submit "Done" %>
|
33
|
+
<%= link_to "Discard", :back %>
|
34
|
+
<% end %>
|
@@ -0,0 +1,7 @@
|
|
1
|
+
<%= turbo_stream.replace "content--editor--item-frame" do %>
|
2
|
+
<%= render "katalyst/content/editor/new_items", container: item.container %>
|
3
|
+
<% end %>
|
4
|
+
|
5
|
+
<%= turbo_stream.replace dom_id(previous) do %>
|
6
|
+
<%= render "katalyst/content/editor/item", item: item %>
|
7
|
+
<% end %>
|
data/config/importmap.rb
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
pin "trix"
|
4
|
+
pin "@rails/actiontext", to: "actiontext.js"
|
5
|
+
|
6
|
+
pin_all_from Katalyst::Content::Engine.root.join("app/assets/javascripts"),
|
7
|
+
# preload in tests so that we don't start clicking before controllers load
|
8
|
+
preload: Rails.env.test?
|
data/config/routes.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class CreateKatalystContentItems < ActiveRecord::Migration[7.0]
|
4
|
+
def change
|
5
|
+
create_table :katalyst_content_items do |t|
|
6
|
+
t.string :type
|
7
|
+
t.belongs_to :container, polymorphic: true
|
8
|
+
|
9
|
+
t.string :heading, null: false
|
10
|
+
t.boolean :show_heading, null: false, default: true
|
11
|
+
t.string :background, null: false
|
12
|
+
t.boolean :visible, null: false, default: true
|
13
|
+
|
14
|
+
t.timestamps
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/configurable"
|
4
|
+
|
5
|
+
module Katalyst
|
6
|
+
module Content
|
7
|
+
class Config
|
8
|
+
include ActiveSupport::Configurable
|
9
|
+
|
10
|
+
config_accessor(:backgrounds) { %w[light dark] }
|
11
|
+
config_accessor(:items) do
|
12
|
+
%w[
|
13
|
+
Katalyst::Content::Content
|
14
|
+
]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails"
|
4
|
+
|
5
|
+
module Katalyst
|
6
|
+
module Content
|
7
|
+
class Engine < ::Rails::Engine
|
8
|
+
isolate_namespace Katalyst::Content
|
9
|
+
|
10
|
+
initializer "katalyst-content.factories", after: "factory_bot.set_factory_paths" do
|
11
|
+
FactoryBot.definition_file_paths << Engine.root.join("spec/factories") if defined?(FactoryBot)
|
12
|
+
end
|
13
|
+
|
14
|
+
config.generators do |g|
|
15
|
+
g.test_framework :rspec
|
16
|
+
g.fixture_replacement :factory_bot
|
17
|
+
g.factory_bot dir: "spec/factories"
|
18
|
+
end
|
19
|
+
|
20
|
+
initializer "katalyst-content.asset" do
|
21
|
+
config.after_initialize do |app|
|
22
|
+
if app.config.respond_to?(:assets)
|
23
|
+
app.config.assets.precompile += %w(katalyst-content.js)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
initializer "katalyst-content.importmap", before: "importmap" do |app|
|
29
|
+
if app.config.respond_to?(:importmap)
|
30
|
+
app.config.importmap.paths << root.join("config/importmap.rb")
|
31
|
+
app.config.importmap.cache_sweepers << root.join("app/assets/javascripts")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "katalyst/content/config"
|
4
|
+
require "katalyst/content/engine"
|
5
|
+
require "katalyst/content/version"
|
6
|
+
|
7
|
+
module Katalyst
|
8
|
+
module Content
|
9
|
+
extend self
|
10
|
+
|
11
|
+
def config
|
12
|
+
@config ||= Config.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def configure
|
16
|
+
yield config
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/tasks/yarn.rake
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
namespace :yarn do
|
4
|
+
desc "Install npm packages with yarn"
|
5
|
+
task install: :environment do
|
6
|
+
sh "yarn install"
|
7
|
+
end
|
8
|
+
|
9
|
+
desc "Lint JS/SCSS files using yarn (prettier)"
|
10
|
+
task lint: :install do
|
11
|
+
sh "yarn lint"
|
12
|
+
end
|
13
|
+
|
14
|
+
desc "Autoformat JS/SCSS files using yarn (prettier)"
|
15
|
+
task format: :install do
|
16
|
+
sh "yarn format"
|
17
|
+
end
|
18
|
+
end
|