gaco_cms 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/README.md +48 -0
- data/Rakefile +10 -0
- data/app/assets/builds/gaco_cms.css +18257 -0
- data/app/assets/builds/gaco_cms.js +43235 -0
- data/app/assets/builds/gaco_cms.js.map +7 -0
- data/app/assets/builds/gaco_cms_front.css +18237 -0
- data/app/assets/builds/gaco_cms_front.js +10722 -0
- data/app/assets/builds/gaco_cms_front.js.map +7 -0
- data/app/assets/javascripts/gaco_cms/base.js +24 -0
- data/app/assets/javascripts/gaco_cms/controllers/confirm_controller.ts +20 -0
- data/app/assets/javascripts/gaco_cms/controllers/deletable_row_controller.ts +46 -0
- data/app/assets/javascripts/gaco_cms/controllers/editor_controller.ts +58 -0
- data/app/assets/javascripts/gaco_cms/controllers/file_input_controller.ts +82 -0
- data/app/assets/javascripts/gaco_cms/controllers/modal_controller.ts +73 -0
- data/app/assets/javascripts/gaco_cms/controllers/remote_content_controller.ts +27 -0
- data/app/assets/javascripts/gaco_cms/controllers/repeatable_field_controller.ts +26 -0
- data/app/assets/javascripts/gaco_cms/controllers/sortable_controller.ts +27 -0
- data/app/assets/javascripts/gaco_cms/controllers/toggle_field_controller.ts +37 -0
- data/app/assets/javascripts/gaco_cms/controllers/translatable_controller.ts +86 -0
- data/app/assets/javascripts/gaco_cms/lib/progress_bar.ts +7 -0
- data/app/assets/javascripts/gaco_cms/lib/request.ts +21 -0
- data/app/assets/javascripts/gaco_cms/lib/turbo_request.ts +61 -0
- data/app/assets/javascripts/gaco_cms/stimulus.ts +9 -0
- data/app/assets/javascripts/gaco_cms.js +7 -0
- data/app/assets/javascripts/gaco_cms_front.js +3 -0
- data/app/assets/stylesheets/gaco_cms/bootstrap_custom.scss +15 -0
- data/app/assets/stylesheets/gaco_cms/shared.scss +22 -0
- data/app/assets/stylesheets/gaco_cms.css.scss +8 -0
- data/app/assets/stylesheets/gaco_cms_front.css.scss +6 -0
- data/app/controllers/concerns/gaco_cms/turbo_concern.rb +52 -0
- data/app/controllers/gaco_cms/admin/base_controller.rb +20 -0
- data/app/controllers/gaco_cms/admin/field_groups_controller.rb +87 -0
- data/app/controllers/gaco_cms/admin/field_groups_manager_controller.rb +66 -0
- data/app/controllers/gaco_cms/admin/field_groups_renderer_controller.rb +33 -0
- data/app/controllers/gaco_cms/admin/fields_controller.rb +20 -0
- data/app/controllers/gaco_cms/admin/page_types_controller.rb +56 -0
- data/app/controllers/gaco_cms/admin/pages_controller.rb +70 -0
- data/app/controllers/gaco_cms/admin/themes_controller.rb +55 -0
- data/app/controllers/gaco_cms/admin_controller.rb +47 -0
- data/app/controllers/gaco_cms/front_controller.rb +39 -0
- data/app/helpers/gaco_cms/application_helper.rb +26 -0
- data/app/jobs/gaco_cms/application_job.rb +6 -0
- data/app/mailers/gaco_cms/application_mailer.rb +8 -0
- data/app/models/concerns/gaco_cms/fields_assignable.rb +40 -0
- data/app/models/concerns/gaco_cms/union_scope.rb +14 -0
- data/app/models/gaco_cms/application_record.rb +25 -0
- data/app/models/gaco_cms/draft_record.rb +16 -0
- data/app/models/gaco_cms/field.rb +78 -0
- data/app/models/gaco_cms/field_group.rb +55 -0
- data/app/models/gaco_cms/field_value.rb +77 -0
- data/app/models/gaco_cms/media_file.rb +22 -0
- data/app/models/gaco_cms/page.rb +57 -0
- data/app/models/gaco_cms/page_type.rb +38 -0
- data/app/models/gaco_cms/theme.rb +28 -0
- data/app/services/gaco_cms/application_service.rb +33 -0
- data/app/services/gaco_cms/shortcode_parser.rb +217 -0
- data/app/services/gaco_cms/theme_content_generator.rb +55 -0
- data/app/views/gaco_cms/admin/base/index.html.haml +4 -0
- data/app/views/gaco_cms/admin/field_groups_manager/_field.html.haml +53 -0
- data/app/views/gaco_cms/admin/field_groups_manager/_group.html.haml +54 -0
- data/app/views/gaco_cms/admin/field_groups_manager/index.html.haml +14 -0
- data/app/views/gaco_cms/admin/field_groups_renderer/_field.html.haml +31 -0
- data/app/views/gaco_cms/admin/field_groups_renderer/_fields_frame.html.haml +16 -0
- data/app/views/gaco_cms/admin/field_groups_renderer/_group.html.haml +24 -0
- data/app/views/gaco_cms/admin/field_groups_renderer/default_value/_page.html.haml +3 -0
- data/app/views/gaco_cms/admin/field_groups_renderer/fields/_editor.html.haml +3 -0
- data/app/views/gaco_cms/admin/field_groups_renderer/fields/_file.html.haml +4 -0
- data/app/views/gaco_cms/admin/field_groups_renderer/fields/_page.html.haml +3 -0
- data/app/views/gaco_cms/admin/field_groups_renderer/fields/_text_area.html.haml +3 -0
- data/app/views/gaco_cms/admin/field_groups_renderer/fields/_text_field.html.haml +3 -0
- data/app/views/gaco_cms/admin/field_groups_renderer/index.html.haml +20 -0
- data/app/views/gaco_cms/admin/page_types/form.html.haml +27 -0
- data/app/views/gaco_cms/admin/page_types/index.html.haml +27 -0
- data/app/views/gaco_cms/admin/pages/edit.html.haml +57 -0
- data/app/views/gaco_cms/admin/pages/index.html.haml +23 -0
- data/app/views/gaco_cms/admin/pages/new.html.haml +16 -0
- data/app/views/gaco_cms/admin/themes/edit.html.haml +36 -0
- data/app/views/gaco_cms/admin/themes/index.html.haml +24 -0
- data/app/views/gaco_cms/admin/themes/new.html.haml +21 -0
- data/app/views/layouts/gaco_cms/_breadcrumb.html.haml +7 -0
- data/app/views/layouts/gaco_cms/_flash_messages.html.haml +13 -0
- data/app/views/layouts/gaco_cms/admin.haml +55 -0
- data/config/routes.rb +26 -0
- data/db/migrate/20230202133738_create_cms_pages.rb +16 -0
- data/db/migrate/20230202134840_create_cms_field_groups.rb +17 -0
- data/db/migrate/20230202135314_create_cms_fields.rb +21 -0
- data/db/migrate/20230202135739_create_cms_field_values.rb +16 -0
- data/db/migrate/20230202181744_create_cms_media_files.rb +10 -0
- data/db/migrate/20230206231547_create_cms_page_types.rb +23 -0
- data/db/migrate/20230209090008_create_cms_themes.rb +19 -0
- data/lib/gaco_cms/config.rb +19 -0
- data/lib/gaco_cms/engine.rb +31 -0
- data/lib/gaco_cms/version.rb +5 -0
- data/lib/gaco_cms.rb +18 -0
- data/lib/generators/gaco_cms/install_generator.rb +16 -0
- data/lib/generators/gaco_cms/install_template/gaco_cms.rb +11 -0
- data/lib/generators/gaco_cms/install_template/themes/default/index.html.haml +2 -0
- data/lib/generators/gaco_cms/install_template/themes/default/layouts/application.html.haml +34 -0
- data/lib/generators/gaco_cms/install_template/themes/default/page.html.haml +2 -0
- data/lib/tasks/auto_annotate_models.rake +59 -0
- data/lib/tasks/gaco_cms_tasks.rake +5 -0
- metadata +174 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GacoCms
|
|
4
|
+
class ApplicationRecord < ActiveRecord::Base
|
|
5
|
+
self.abstract_class = true
|
|
6
|
+
|
|
7
|
+
def cache_key_locale(*keys)
|
|
8
|
+
"#{cache_key_with_version}/#{I18n.locale}/#{keys.join('-')}"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def activestorage_url(file)
|
|
12
|
+
return '' unless file&.blob
|
|
13
|
+
return file.url.split('?').first if file.service.name == :amazon
|
|
14
|
+
|
|
15
|
+
Rails.application.routes.url_helpers.rails_blob_path(file, only_path: true)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class << self
|
|
19
|
+
alias attr_label human_attribute_name
|
|
20
|
+
def human_name(args = {})
|
|
21
|
+
model_name.human(args)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# == Schema Information
|
|
4
|
+
#
|
|
5
|
+
# Table name: simple_cms_draft_records
|
|
6
|
+
#
|
|
7
|
+
# id :integer not null, primary key
|
|
8
|
+
# record_type :string
|
|
9
|
+
# created_at :datetime not null
|
|
10
|
+
# updated_at :datetime not null
|
|
11
|
+
#
|
|
12
|
+
module GacoCms
|
|
13
|
+
class DraftRecord < ApplicationRecord
|
|
14
|
+
include GacoCms::FieldsAssignable
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# == Schema Information
|
|
4
|
+
#
|
|
5
|
+
# Table name: simple_cms_fields
|
|
6
|
+
#
|
|
7
|
+
# id :integer not null, primary key
|
|
8
|
+
# data :text
|
|
9
|
+
# def_value :text
|
|
10
|
+
# description :text
|
|
11
|
+
# key :string
|
|
12
|
+
# kind :string default("text_field")
|
|
13
|
+
# position :integer default(0)
|
|
14
|
+
# repeat :boolean default(FALSE)
|
|
15
|
+
# required :boolean default(FALSE)
|
|
16
|
+
# title :text
|
|
17
|
+
# translatable :boolean default(FALSE)
|
|
18
|
+
# created_at :datetime not null
|
|
19
|
+
# updated_at :datetime not null
|
|
20
|
+
# field_group_id :integer not null
|
|
21
|
+
#
|
|
22
|
+
# Indexes
|
|
23
|
+
#
|
|
24
|
+
# index_simple_cms_fields_on_field_group_id (field_group_id)
|
|
25
|
+
#
|
|
26
|
+
# Foreign Keys
|
|
27
|
+
#
|
|
28
|
+
# field_group_id (field_group_id => simple_cms_field_groups.id)
|
|
29
|
+
#
|
|
30
|
+
|
|
31
|
+
module GacoCms
|
|
32
|
+
class Field < ApplicationRecord
|
|
33
|
+
include BuddyTranslatable
|
|
34
|
+
translatable :title, :description, :def_value
|
|
35
|
+
|
|
36
|
+
EXTRA_KINDS = GacoCms::Config.extra_fields
|
|
37
|
+
KINDS = %i[text_field text_area editor file page].concat(EXTRA_KINDS.keys)
|
|
38
|
+
enum kind: KINDS.map { |k| [k, k.to_s] }.to_h
|
|
39
|
+
|
|
40
|
+
belongs_to :field_group, optional: false
|
|
41
|
+
has_many :field_values, dependent: :destroy, inverse_of: :field
|
|
42
|
+
after_update_commit :update_values_key, if: :saved_change_to_key?
|
|
43
|
+
|
|
44
|
+
validates :key, uniqueness: { scope: :field_group_id }
|
|
45
|
+
scope :ordered, -> { order(position: :asc) }
|
|
46
|
+
|
|
47
|
+
def self.dropdown_data
|
|
48
|
+
KINDS.map do |key|
|
|
49
|
+
title = key.to_s.titleize
|
|
50
|
+
title = ApplicationHelper.translated_value_for(EXTRA_KINDS[key][:label]) if EXTRA_KINDS[key]
|
|
51
|
+
[title, key]
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def default_value_tpl
|
|
56
|
+
return "/gaco_cms/admin/field_groups_renderer/default_value/#{kind}" if kind == 'page'
|
|
57
|
+
|
|
58
|
+
EXTRA_KINDS.dig(kind&.to_sym, :default_value_tpl)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def allow_translation?
|
|
62
|
+
return false if %w[page].include?(kind)
|
|
63
|
+
|
|
64
|
+
res = EXTRA_KINDS.dig(kind&.to_sym, :translatable)
|
|
65
|
+
res.nil? ? true : res
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def tpl
|
|
69
|
+
EXTRA_KINDS.dig(kind&.to_sym, :tpl) || "/gaco_cms/admin/field_groups_renderer/fields/#{kind}"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
def update_values_key
|
|
75
|
+
field_values.each { |v| v.update!(field_key: key) }
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# == Schema Information
|
|
4
|
+
#
|
|
5
|
+
# Table name: simple_cms_field_groups
|
|
6
|
+
#
|
|
7
|
+
# id :integer not null, primary key
|
|
8
|
+
# description :text
|
|
9
|
+
# key :string
|
|
10
|
+
# position :integer default(0)
|
|
11
|
+
# record_type :string
|
|
12
|
+
# repeat :boolean default(FALSE)
|
|
13
|
+
# title :text
|
|
14
|
+
# created_at :datetime not null
|
|
15
|
+
# updated_at :datetime not null
|
|
16
|
+
# record_id :integer
|
|
17
|
+
# template :text default('')
|
|
18
|
+
#
|
|
19
|
+
# Indexes
|
|
20
|
+
#
|
|
21
|
+
# index_simple_cms_field_groups_on_record (record_type,record_id)
|
|
22
|
+
#
|
|
23
|
+
|
|
24
|
+
module GacoCms
|
|
25
|
+
class FieldGroup < ApplicationRecord
|
|
26
|
+
include UnionScope
|
|
27
|
+
include BuddyTranslatable
|
|
28
|
+
translatable :title, :description
|
|
29
|
+
|
|
30
|
+
belongs_to :record, polymorphic: true
|
|
31
|
+
has_many :fields, -> { ordered }, dependent: :destroy
|
|
32
|
+
|
|
33
|
+
scope :ordered, -> { order(position: :asc) }
|
|
34
|
+
accepts_nested_attributes_for :fields, allow_destroy: true
|
|
35
|
+
|
|
36
|
+
def available_records
|
|
37
|
+
map_data = ->(item) { [item.title, "#{item.class.name}/#{item.id}"] }
|
|
38
|
+
{
|
|
39
|
+
Page.human_name(count: 2) => GacoCms::Page.title_ordered.select(:title, :id).map(&map_data),
|
|
40
|
+
'All Pages Under' => PageType.title_ordered.select(:title, :id).map(&map_data),
|
|
41
|
+
Theme.human_name(count: 2) => GacoCms::Theme.ordered.map(&map_data)
|
|
42
|
+
}
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def selected_record
|
|
46
|
+
"#{record_type}/#{record_id}"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def record_label
|
|
50
|
+
return "All pages under \"#{record.title}\"" if record_type.include?('PageType')
|
|
51
|
+
|
|
52
|
+
"#{record.class.human_name} => \"#{record.title}\""
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# == Schema Information
|
|
4
|
+
#
|
|
5
|
+
# Table name: simple_cms_field_values
|
|
6
|
+
#
|
|
7
|
+
# id :integer not null, primary key
|
|
8
|
+
# field_key :string
|
|
9
|
+
# group_no :integer default(0)
|
|
10
|
+
# position :integer default(0)
|
|
11
|
+
# record_type :string
|
|
12
|
+
# value :text
|
|
13
|
+
# created_at :datetime not null
|
|
14
|
+
# updated_at :datetime not null
|
|
15
|
+
# field_id :integer not null
|
|
16
|
+
# record_id :integer
|
|
17
|
+
#
|
|
18
|
+
# Indexes
|
|
19
|
+
#
|
|
20
|
+
# index_simple_cms_field_values_on_field_id (field_id)
|
|
21
|
+
# index_simple_cms_field_values_on_record (record_type,record_id)
|
|
22
|
+
#
|
|
23
|
+
# Foreign Keys
|
|
24
|
+
#
|
|
25
|
+
# field_id (field_id => simple_cms_fields.id)
|
|
26
|
+
#
|
|
27
|
+
|
|
28
|
+
module GacoCms
|
|
29
|
+
class FieldValue < ApplicationRecord
|
|
30
|
+
include BuddyTranslatable
|
|
31
|
+
translatable :value
|
|
32
|
+
|
|
33
|
+
belongs_to :record, polymorphic: true, touch: true
|
|
34
|
+
belongs_to :field, optional: false
|
|
35
|
+
before_validation :retrieve_field_key, unless: :field_key
|
|
36
|
+
validates :field_key, presence: true
|
|
37
|
+
validates :record, presence: true
|
|
38
|
+
|
|
39
|
+
scope :for_group, ->(group) { joins(:field).merge(GacoCms::Field.where(field_group_id: group.id)) }
|
|
40
|
+
scope :ordered, -> { order(position: :asc) }
|
|
41
|
+
delegate :translatable, :repeat, :required, :kind, :def_value, :def_value_data, to: :field
|
|
42
|
+
|
|
43
|
+
def self.group_nos_for(group)
|
|
44
|
+
for_group(group).pluck(:group_no).uniq.sort
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def self.all_or_new_for(field, group_no)
|
|
48
|
+
items = where(field: field).where(group_no: group_no)
|
|
49
|
+
items.any? ? items.ordered : [items.new]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def self.grouped
|
|
53
|
+
groups = reorder(group_no: :asc).pluck('distinct(group_no)')
|
|
54
|
+
groups.map do |g_no|
|
|
55
|
+
where(group_no: g_no).ordered
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def value_for_input
|
|
60
|
+
@value_for_input ||= begin
|
|
61
|
+
return new_record? ? def_value : value unless translatable
|
|
62
|
+
|
|
63
|
+
new_record? ? def_value_data.to_json : value_data.to_json
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def the_value
|
|
68
|
+
ShortcodeParser.call(value, record)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
|
|
73
|
+
def retrieve_field_key
|
|
74
|
+
self.field_key = field.key
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# == Schema Information
|
|
4
|
+
#
|
|
5
|
+
# Table name: simple_cms_media_files
|
|
6
|
+
#
|
|
7
|
+
# id :integer not null, primary key
|
|
8
|
+
# name :string
|
|
9
|
+
# created_at :datetime not null
|
|
10
|
+
# updated_at :datetime not null
|
|
11
|
+
#
|
|
12
|
+
|
|
13
|
+
module GacoCms
|
|
14
|
+
class MediaFile < ApplicationRecord
|
|
15
|
+
has_one_attached :file # TODO: if image, then create a thumb version 100x100
|
|
16
|
+
validates :file, presence: true
|
|
17
|
+
|
|
18
|
+
def url
|
|
19
|
+
activestorage_url(file)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# == Schema Information
|
|
4
|
+
#
|
|
5
|
+
# Table name: simple_cms_pages
|
|
6
|
+
#
|
|
7
|
+
# id :integer not null, primary key
|
|
8
|
+
# content :text
|
|
9
|
+
# key :text
|
|
10
|
+
# photo_url :text
|
|
11
|
+
# summary :text
|
|
12
|
+
# template :text
|
|
13
|
+
# title :text
|
|
14
|
+
# created_at :datetime not null
|
|
15
|
+
# updated_at :datetime not null
|
|
16
|
+
# page_type_id :integer not null
|
|
17
|
+
#
|
|
18
|
+
# Indexes
|
|
19
|
+
#
|
|
20
|
+
# index_simple_cms_pages_on_page_type_id (page_type_id)
|
|
21
|
+
#
|
|
22
|
+
# Foreign Keys
|
|
23
|
+
#
|
|
24
|
+
# page_type_id (page_type_id => simple_cms_page_types.id)
|
|
25
|
+
#
|
|
26
|
+
|
|
27
|
+
require 'byebug'
|
|
28
|
+
module GacoCms
|
|
29
|
+
class Page < ApplicationRecord
|
|
30
|
+
include BuddyTranslatable
|
|
31
|
+
include GacoCms::FieldsAssignable
|
|
32
|
+
translatable :key, :title, :summary, :content
|
|
33
|
+
|
|
34
|
+
belongs_to :page_type
|
|
35
|
+
scope :title_ordered, -> { order(title: :asc) }
|
|
36
|
+
|
|
37
|
+
def self.by_key(key)
|
|
38
|
+
where_key_with(key).take
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# TODO: deprecated
|
|
42
|
+
def all_field_groups
|
|
43
|
+
FieldGroup.union_scope(field_groups, page_type.field_groups)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def the_content(cache: true)
|
|
47
|
+
callback = proc do
|
|
48
|
+
tpl = template.presence || page_type.template.to_s
|
|
49
|
+
tpl = "[page_content]#{tpl}" unless tpl.include?('[page_content]')
|
|
50
|
+
ShortcodeParser.call(tpl, self).to_s
|
|
51
|
+
end
|
|
52
|
+
return callback.call unless cache
|
|
53
|
+
|
|
54
|
+
Rails.cache.fetch(cache_key_locale(:the_content), expires_at: Time.current.end_of_day, &callback)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# == Schema Information
|
|
4
|
+
#
|
|
5
|
+
# Table name: simple_cms_page_types
|
|
6
|
+
#
|
|
7
|
+
# id :integer not null, primary key
|
|
8
|
+
# key :string
|
|
9
|
+
# template :text default("")
|
|
10
|
+
# title :text
|
|
11
|
+
# created_at :datetime not null
|
|
12
|
+
# updated_at :datetime not null
|
|
13
|
+
#
|
|
14
|
+
# Indexes
|
|
15
|
+
#
|
|
16
|
+
# index_simple_cms_page_types_on_key (key)
|
|
17
|
+
#
|
|
18
|
+
|
|
19
|
+
module GacoCms
|
|
20
|
+
class PageType < ApplicationRecord
|
|
21
|
+
include BuddyTranslatable
|
|
22
|
+
translatable :title
|
|
23
|
+
|
|
24
|
+
has_many :pages, dependent: :destroy
|
|
25
|
+
has_many :field_groups, -> { ordered }, as: :record, inverse_of: :record, dependent: :destroy
|
|
26
|
+
after_update_commit :touch_pages
|
|
27
|
+
accepts_nested_attributes_for :field_groups, allow_destroy: true
|
|
28
|
+
|
|
29
|
+
scope :title_ordered, -> { order(title: :asc) }
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
# reset pages cached values
|
|
34
|
+
def touch_pages
|
|
35
|
+
pages.find_each(&:touch)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# == Schema Information
|
|
4
|
+
#
|
|
5
|
+
# Table name: simple_cms_themes
|
|
6
|
+
#
|
|
7
|
+
# id :integer not null, primary key
|
|
8
|
+
# active :boolean default(FALSE)
|
|
9
|
+
# key :string not null
|
|
10
|
+
# title :string not null
|
|
11
|
+
# created_at :datetime not null
|
|
12
|
+
# updated_at :datetime not null
|
|
13
|
+
#
|
|
14
|
+
|
|
15
|
+
module GacoCms
|
|
16
|
+
class Theme < ApplicationRecord
|
|
17
|
+
include GacoCms::FieldsAssignable
|
|
18
|
+
|
|
19
|
+
validates :key, presence: true
|
|
20
|
+
validates :title, presence: true, uniqueness: true
|
|
21
|
+
|
|
22
|
+
scope :ordered, -> { order(title: :asc) }
|
|
23
|
+
|
|
24
|
+
def self.current
|
|
25
|
+
find_by(active: true)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GacoCms
|
|
4
|
+
class ApplicationService
|
|
5
|
+
def initialize(*args); end
|
|
6
|
+
|
|
7
|
+
class << self
|
|
8
|
+
# @see #initialize
|
|
9
|
+
def call(*args, **kwargs)
|
|
10
|
+
new(*args, **kwargs).call
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Code style definition for Sidekiq delay method
|
|
14
|
+
# @!method delay(settings = {})
|
|
15
|
+
# @param [Hash] settings
|
|
16
|
+
# @return [self] returns itself
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
#
|
|
22
|
+
# @param [String] message
|
|
23
|
+
# @param [:info|:warn|:error] mode
|
|
24
|
+
#
|
|
25
|
+
def log(message, mode: :info)
|
|
26
|
+
Rails.logger.send(mode, "#{self.class.name} => #{message}")
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def print_error(error)
|
|
30
|
+
log("Failed with #{error.message}. #{error.backtrace[0..10]}")
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GacoCms
|
|
4
|
+
class ShortcodeParser < ApplicationService # rubocop:disable Metrics/ClassLength
|
|
5
|
+
attr_reader :content, :record, :ignored_shortcodes
|
|
6
|
+
|
|
7
|
+
def initialize(content, record, ignore: [])
|
|
8
|
+
@content = content
|
|
9
|
+
@record = record
|
|
10
|
+
@ignored_shortcodes = Array(ignore)
|
|
11
|
+
super
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def call
|
|
15
|
+
counter = 0
|
|
16
|
+
loop do
|
|
17
|
+
detected = @content.scan(/#{shortcodes_regex}/).to_a
|
|
18
|
+
break if detected.empty?
|
|
19
|
+
break log("Too many levels detected, skipping after #{counter} levels", mode: :error) if counter > 5
|
|
20
|
+
|
|
21
|
+
detected.each { |item| @content = replace_shortcode(item) }
|
|
22
|
+
counter += 1
|
|
23
|
+
end
|
|
24
|
+
@content
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def shortcodes # rubocop:disable Metrics/MethodLength
|
|
30
|
+
pp_sample = '<img src="[page_field key=\'photo\']"/> Or [page_field key=\'photo\' page_id=10 group_no=1]'
|
|
31
|
+
pif_sample = '[page_img_field key="photo"] Or [page_img_field key="photo" page_id=10 style="width: 100%"]'
|
|
32
|
+
pmf_sample = '[page_field_multiple key="thumbnail" content="<li><img src=\'{field_yield}\' /></li>" '\
|
|
33
|
+
'wrapper="<ul>{yield}</ul>"] Or [page_field_multiple key="thumbnail" page_id=10 content=...'
|
|
34
|
+
pgf_sample = '[page_grouped_fields keys="name,photo" wrapper="<ul>{yield}</ul>" content="<li>{name_yield}: '\
|
|
35
|
+
'{photo_yield}</li>"] Or [page_grouped_fields keys="name,photo" page_id=10 content=...'
|
|
36
|
+
tf_sample = '<img src="[theme_field key=\'photo\'] or [theme_field key=\'photo\' group_no=1]" />'
|
|
37
|
+
pf_sample = '[page_photo] or [page_photo page_id=10 style=".."]'
|
|
38
|
+
pg_sample = '[page_group key="my_key"] or [page_group key="my_key" page_id=10 wrapper="<div class=\'row\'>{yield}</div>"]'
|
|
39
|
+
{
|
|
40
|
+
page_content: { render: method(:page_content_parser), sample: '[page_content] or [page_content page_id=10]' },
|
|
41
|
+
page_title: { render: method(:page_title_parser), sample: '[page_title] or [page_title page_id=10]' },
|
|
42
|
+
page_photo: { render: method(:page_photo_parser), sample: pp_sample },
|
|
43
|
+
page_field: { render: method(:page_field_parser), sample: pf_sample },
|
|
44
|
+
page_img_field: { render: method(:page_img_field_parser), sample: pif_sample },
|
|
45
|
+
page_field_multiple: { render: method(:page_field_multiple_parser), sample: pmf_sample },
|
|
46
|
+
# page_fields: { render: method(:page_fields_parser), sample: },
|
|
47
|
+
page_grouped_fields: { render: method(:page_grouped_fields_parser), sample: pgf_sample },
|
|
48
|
+
page_url: { render: method(:page_url_parser), sample: '[page_url] or [page_url page_id=10]' },
|
|
49
|
+
page_group: { render: method(:page_group_parser), sample: pg_sample },
|
|
50
|
+
theme_field: { render: method(:theme_field_parser), sample: tf_sample },
|
|
51
|
+
theme_img_field: { render: method(:theme_img_field_parser), sample: '[theme_img_field key="my_img"]' }
|
|
52
|
+
}.except(*ignored_shortcodes)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def shortcodes_regex
|
|
56
|
+
"(\\[(#{shortcodes.keys.join('|')})((\s)((?!\\]).)*|)\\])"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# @param item [Array[shortcode, code, attrs]]
|
|
60
|
+
def replace_shortcode(item)
|
|
61
|
+
shortcode, code, attrs = item
|
|
62
|
+
attrs = parse_shortcode_attrs(attrs)
|
|
63
|
+
shortcode, shortcode_content = shortcode_content_for(shortcode, code)
|
|
64
|
+
args = { shortcode: shortcode, code: code, shortcode_content: shortcode_content }
|
|
65
|
+
content.sub(shortcode, parse_shortcode(code, attrs, args).to_s)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def shortcode_content_for(shortcode, code)
|
|
69
|
+
close_code = "[/#{code}]"
|
|
70
|
+
return [shortcode, nil] unless content.include?(close_code)
|
|
71
|
+
|
|
72
|
+
shortcode_bk = shortcode
|
|
73
|
+
tmp_content = content[content.index(shortcode)..]
|
|
74
|
+
shortcode = tmp_content[0..(tmp_content.index(close_code) + close_code.size - 1)]
|
|
75
|
+
[shortcode, shortcode.sub(shortcode_bk, '').sub(close_code, '')]
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# determine the content to replace instead the shortcode
|
|
79
|
+
def parse_shortcode(code, attrs, args = {})
|
|
80
|
+
renderer = shortcodes[code.to_sym][:render]
|
|
81
|
+
if renderer.is_a?(String)
|
|
82
|
+
params = { attributes: attrs, args: args }
|
|
83
|
+
ActionController::Base.new.render_to_string template: renderer, locals: params, formats: [:html]
|
|
84
|
+
else
|
|
85
|
+
shortcodes[code.to_sym][:render].call(attrs, args)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# parse the attributes of a shortcode
|
|
90
|
+
def parse_shortcode_attrs(text)
|
|
91
|
+
return {} if text.blank?
|
|
92
|
+
|
|
93
|
+
res = {}
|
|
94
|
+
regex = /(\w+)\s*=\s*"([^"]*)"(?:\s|$)|(\w+)\s*=\s*\'([^\']*)\'(?:\s|$)|(\w+)\s*=\s*([^\s\'"]+)(?:\s|$)|"([^"]*)"(?:\s|$)|(\S+)(?:\s|$)/ # rubocop:disable Style/RedundantRegexpEscape, Layout/LineLength
|
|
95
|
+
text.scan(regex).each do |item|
|
|
96
|
+
item.each_with_index do |c, index|
|
|
97
|
+
break res[c] = item[index + 1] if c.present?
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
res
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def img_tag_for(url, attrs)
|
|
104
|
+
attrs = attrs.slice('style', 'alt')
|
|
105
|
+
props = attrs.map { |k, v| "#{k}=\"#{v}\"" }.join(' ')
|
|
106
|
+
"<img src='#{url}' #{props} />"
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def replace_field_wrapper(attrs, content)
|
|
110
|
+
return content unless attrs.key?('wrapper')
|
|
111
|
+
|
|
112
|
+
attrs['wrapper'].to_s.sub('{yield}', content).gsub('<', '<').gsub('>', '>')
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
############################
|
|
116
|
+
def calc_context(context, attrs, klass: Page)
|
|
117
|
+
return context if context
|
|
118
|
+
return Page.where_key_with(attrs['page_key']).take if attrs['page_key'].present?
|
|
119
|
+
|
|
120
|
+
attrs['page_id'].present? ? klass.find_by(id: attrs['page_id']) : record
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def page_field_parser(attrs, _args, context = nil)
|
|
124
|
+
context = calc_context(context, attrs)
|
|
125
|
+
return '--PageNotFound--' unless context
|
|
126
|
+
|
|
127
|
+
context.the_value(attrs['key'])
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def page_field_multiple_parser(attrs, _args, context = nil)
|
|
131
|
+
context = calc_context(context, attrs)
|
|
132
|
+
return '--PageNotFound--' unless context
|
|
133
|
+
|
|
134
|
+
res = context.the_values(attrs['key']).map do |val|
|
|
135
|
+
attrs['content'].to_s.gsub('{field_yield}', val)
|
|
136
|
+
end.join
|
|
137
|
+
replace_field_wrapper(attrs, res)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def page_img_field_parser(attrs, _args, context = nil)
|
|
141
|
+
context = calc_context(context, attrs)
|
|
142
|
+
return '--PageNotFound--' unless context
|
|
143
|
+
|
|
144
|
+
src = context.the_value(attrs['key'])
|
|
145
|
+
img_tag_for(src, attrs)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def page_grouped_fields_parser(attrs, _args, context = nil)
|
|
149
|
+
context = calc_context(context, attrs)
|
|
150
|
+
return '--PageNotFound--' unless context
|
|
151
|
+
|
|
152
|
+
keys = attrs['keys'].to_s.split(',')
|
|
153
|
+
context.the_grouped_values(*keys).map do |fields|
|
|
154
|
+
content = attrs['content'].to_s
|
|
155
|
+
keys.each do |key|
|
|
156
|
+
content = content.gsub("{#{key}_yield}", fields[key])
|
|
157
|
+
end
|
|
158
|
+
replace_field_wrapper(attrs, content)
|
|
159
|
+
end.join
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def page_content_parser(attrs, _args)
|
|
163
|
+
context = calc_context(nil, attrs)
|
|
164
|
+
return '--PageNotFound--' unless context
|
|
165
|
+
|
|
166
|
+
context.content
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def page_photo_parser(attrs, _args)
|
|
170
|
+
context = calc_context(nil, attrs)
|
|
171
|
+
return '--PageNotFound--' unless context
|
|
172
|
+
|
|
173
|
+
img_tag_for(context.photo_url, attrs)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def page_title_parser(attrs, _args)
|
|
177
|
+
context = calc_context(nil, attrs)
|
|
178
|
+
return '--PageNotFound--' unless context
|
|
179
|
+
|
|
180
|
+
context.title
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def page_url_parser(attrs, _args)
|
|
184
|
+
context = calc_context(nil, attrs)
|
|
185
|
+
return '--PageNotFound--' unless context
|
|
186
|
+
|
|
187
|
+
ApplicationHelper.page_url_for(context.id)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# TODO: add missing test cases
|
|
191
|
+
def page_group_parser(attrs, _args)
|
|
192
|
+
context = calc_context(nil, attrs)
|
|
193
|
+
return '--PageNotFound--' unless context
|
|
194
|
+
|
|
195
|
+
group = context.field_groups.find_by(key: attrs['key'])
|
|
196
|
+
return '--GroupNotFound--' unless group
|
|
197
|
+
|
|
198
|
+
keys = group.fields.pluck(:key)
|
|
199
|
+
result = context.the_grouped_values(*keys).map do |fields|
|
|
200
|
+
content = group.template.to_s
|
|
201
|
+
keys.each do |key|
|
|
202
|
+
content = content.gsub("{#{key}_yield}", fields[key])
|
|
203
|
+
end
|
|
204
|
+
content
|
|
205
|
+
end.join
|
|
206
|
+
replace_field_wrapper(attrs, result)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def theme_field_parser(attrs, args)
|
|
210
|
+
page_field_parser(attrs, args, Theme.current)
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def theme_img_field_parser(attrs, args)
|
|
214
|
+
page_img_field_parser(attrs, args, Theme.current)
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
end
|