katalyst-content 0.1.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/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
|