gaco_cms 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +48 -0
  3. data/Rakefile +10 -0
  4. data/app/assets/builds/gaco_cms.css +18257 -0
  5. data/app/assets/builds/gaco_cms.js +43235 -0
  6. data/app/assets/builds/gaco_cms.js.map +7 -0
  7. data/app/assets/builds/gaco_cms_front.css +18237 -0
  8. data/app/assets/builds/gaco_cms_front.js +10722 -0
  9. data/app/assets/builds/gaco_cms_front.js.map +7 -0
  10. data/app/assets/javascripts/gaco_cms/base.js +24 -0
  11. data/app/assets/javascripts/gaco_cms/controllers/confirm_controller.ts +20 -0
  12. data/app/assets/javascripts/gaco_cms/controllers/deletable_row_controller.ts +46 -0
  13. data/app/assets/javascripts/gaco_cms/controllers/editor_controller.ts +58 -0
  14. data/app/assets/javascripts/gaco_cms/controllers/file_input_controller.ts +82 -0
  15. data/app/assets/javascripts/gaco_cms/controllers/modal_controller.ts +73 -0
  16. data/app/assets/javascripts/gaco_cms/controllers/remote_content_controller.ts +27 -0
  17. data/app/assets/javascripts/gaco_cms/controllers/repeatable_field_controller.ts +26 -0
  18. data/app/assets/javascripts/gaco_cms/controllers/sortable_controller.ts +27 -0
  19. data/app/assets/javascripts/gaco_cms/controllers/toggle_field_controller.ts +37 -0
  20. data/app/assets/javascripts/gaco_cms/controllers/translatable_controller.ts +86 -0
  21. data/app/assets/javascripts/gaco_cms/lib/progress_bar.ts +7 -0
  22. data/app/assets/javascripts/gaco_cms/lib/request.ts +21 -0
  23. data/app/assets/javascripts/gaco_cms/lib/turbo_request.ts +61 -0
  24. data/app/assets/javascripts/gaco_cms/stimulus.ts +9 -0
  25. data/app/assets/javascripts/gaco_cms.js +7 -0
  26. data/app/assets/javascripts/gaco_cms_front.js +3 -0
  27. data/app/assets/stylesheets/gaco_cms/bootstrap_custom.scss +15 -0
  28. data/app/assets/stylesheets/gaco_cms/shared.scss +22 -0
  29. data/app/assets/stylesheets/gaco_cms.css.scss +8 -0
  30. data/app/assets/stylesheets/gaco_cms_front.css.scss +6 -0
  31. data/app/controllers/concerns/gaco_cms/turbo_concern.rb +52 -0
  32. data/app/controllers/gaco_cms/admin/base_controller.rb +20 -0
  33. data/app/controllers/gaco_cms/admin/field_groups_controller.rb +87 -0
  34. data/app/controllers/gaco_cms/admin/field_groups_manager_controller.rb +66 -0
  35. data/app/controllers/gaco_cms/admin/field_groups_renderer_controller.rb +33 -0
  36. data/app/controllers/gaco_cms/admin/fields_controller.rb +20 -0
  37. data/app/controllers/gaco_cms/admin/page_types_controller.rb +56 -0
  38. data/app/controllers/gaco_cms/admin/pages_controller.rb +70 -0
  39. data/app/controllers/gaco_cms/admin/themes_controller.rb +55 -0
  40. data/app/controllers/gaco_cms/admin_controller.rb +47 -0
  41. data/app/controllers/gaco_cms/front_controller.rb +39 -0
  42. data/app/helpers/gaco_cms/application_helper.rb +26 -0
  43. data/app/jobs/gaco_cms/application_job.rb +6 -0
  44. data/app/mailers/gaco_cms/application_mailer.rb +8 -0
  45. data/app/models/concerns/gaco_cms/fields_assignable.rb +40 -0
  46. data/app/models/concerns/gaco_cms/union_scope.rb +14 -0
  47. data/app/models/gaco_cms/application_record.rb +25 -0
  48. data/app/models/gaco_cms/draft_record.rb +16 -0
  49. data/app/models/gaco_cms/field.rb +78 -0
  50. data/app/models/gaco_cms/field_group.rb +55 -0
  51. data/app/models/gaco_cms/field_value.rb +77 -0
  52. data/app/models/gaco_cms/media_file.rb +22 -0
  53. data/app/models/gaco_cms/page.rb +57 -0
  54. data/app/models/gaco_cms/page_type.rb +38 -0
  55. data/app/models/gaco_cms/theme.rb +28 -0
  56. data/app/services/gaco_cms/application_service.rb +33 -0
  57. data/app/services/gaco_cms/shortcode_parser.rb +217 -0
  58. data/app/services/gaco_cms/theme_content_generator.rb +55 -0
  59. data/app/views/gaco_cms/admin/base/index.html.haml +4 -0
  60. data/app/views/gaco_cms/admin/field_groups_manager/_field.html.haml +53 -0
  61. data/app/views/gaco_cms/admin/field_groups_manager/_group.html.haml +54 -0
  62. data/app/views/gaco_cms/admin/field_groups_manager/index.html.haml +14 -0
  63. data/app/views/gaco_cms/admin/field_groups_renderer/_field.html.haml +31 -0
  64. data/app/views/gaco_cms/admin/field_groups_renderer/_fields_frame.html.haml +16 -0
  65. data/app/views/gaco_cms/admin/field_groups_renderer/_group.html.haml +24 -0
  66. data/app/views/gaco_cms/admin/field_groups_renderer/default_value/_page.html.haml +3 -0
  67. data/app/views/gaco_cms/admin/field_groups_renderer/fields/_editor.html.haml +3 -0
  68. data/app/views/gaco_cms/admin/field_groups_renderer/fields/_file.html.haml +4 -0
  69. data/app/views/gaco_cms/admin/field_groups_renderer/fields/_page.html.haml +3 -0
  70. data/app/views/gaco_cms/admin/field_groups_renderer/fields/_text_area.html.haml +3 -0
  71. data/app/views/gaco_cms/admin/field_groups_renderer/fields/_text_field.html.haml +3 -0
  72. data/app/views/gaco_cms/admin/field_groups_renderer/index.html.haml +20 -0
  73. data/app/views/gaco_cms/admin/page_types/form.html.haml +27 -0
  74. data/app/views/gaco_cms/admin/page_types/index.html.haml +27 -0
  75. data/app/views/gaco_cms/admin/pages/edit.html.haml +57 -0
  76. data/app/views/gaco_cms/admin/pages/index.html.haml +23 -0
  77. data/app/views/gaco_cms/admin/pages/new.html.haml +16 -0
  78. data/app/views/gaco_cms/admin/themes/edit.html.haml +36 -0
  79. data/app/views/gaco_cms/admin/themes/index.html.haml +24 -0
  80. data/app/views/gaco_cms/admin/themes/new.html.haml +21 -0
  81. data/app/views/layouts/gaco_cms/_breadcrumb.html.haml +7 -0
  82. data/app/views/layouts/gaco_cms/_flash_messages.html.haml +13 -0
  83. data/app/views/layouts/gaco_cms/admin.haml +55 -0
  84. data/config/routes.rb +26 -0
  85. data/db/migrate/20230202133738_create_cms_pages.rb +16 -0
  86. data/db/migrate/20230202134840_create_cms_field_groups.rb +17 -0
  87. data/db/migrate/20230202135314_create_cms_fields.rb +21 -0
  88. data/db/migrate/20230202135739_create_cms_field_values.rb +16 -0
  89. data/db/migrate/20230202181744_create_cms_media_files.rb +10 -0
  90. data/db/migrate/20230206231547_create_cms_page_types.rb +23 -0
  91. data/db/migrate/20230209090008_create_cms_themes.rb +19 -0
  92. data/lib/gaco_cms/config.rb +19 -0
  93. data/lib/gaco_cms/engine.rb +31 -0
  94. data/lib/gaco_cms/version.rb +5 -0
  95. data/lib/gaco_cms.rb +18 -0
  96. data/lib/generators/gaco_cms/install_generator.rb +16 -0
  97. data/lib/generators/gaco_cms/install_template/gaco_cms.rb +11 -0
  98. data/lib/generators/gaco_cms/install_template/themes/default/index.html.haml +2 -0
  99. data/lib/generators/gaco_cms/install_template/themes/default/layouts/application.html.haml +34 -0
  100. data/lib/generators/gaco_cms/install_template/themes/default/page.html.haml +2 -0
  101. data/lib/tasks/auto_annotate_models.rake +59 -0
  102. data/lib/tasks/gaco_cms_tasks.rake +5 -0
  103. 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('&lt;', '<').gsub('&gt;', '>')
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