gaco_cms 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/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
|