rocket_cms 0.1.13

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.
Files changed (99) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.rspec +1 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +2 -0
  6. data/.travis.yml +19 -0
  7. data/Gemfile +4 -0
  8. data/LICENSE.txt +22 -0
  9. data/README.md +36 -0
  10. data/Rakefile +1 -0
  11. data/app/assets/javascripts/head.load.js +707 -0
  12. data/app/assets/javascripts/jquery.placeholder.js +157 -0
  13. data/app/assets/javascripts/rocket_cms.js.coffee +10 -0
  14. data/app/assets/javascripts/rocket_cms/flash.js.coffee +3 -0
  15. data/app/assets/javascripts/rocket_cms/map.js.coffee +22 -0
  16. data/app/assets/stylesheets/rocket_cms.css.sass +2 -0
  17. data/app/assets/stylesheets/rocket_cms/flash.css.sass +66 -0
  18. data/app/assets/stylesheets/rocket_cms/normalize.css.scss +406 -0
  19. data/app/controllers/concerns/no_cache.rb +12 -0
  20. data/app/controllers/concerns/rs_errors.rb +58 -0
  21. data/app/controllers/concerns/rs_menu.rb +45 -0
  22. data/app/controllers/concerns/rs_pages.rb +41 -0
  23. data/app/controllers/contacts_controller.rb +29 -0
  24. data/app/controllers/news_controller.rb +22 -0
  25. data/app/controllers/pages_controller.rb +12 -0
  26. data/app/controllers/search_controller.rb +25 -0
  27. data/app/mailers/contact_mailer.rb +15 -0
  28. data/app/models/ckeditor/asset.rb +5 -0
  29. data/app/models/ckeditor/attachment_file.rb +15 -0
  30. data/app/models/ckeditor/picture.rb +16 -0
  31. data/app/models/concerns/boolean_field.rb +9 -0
  32. data/app/models/concerns/enableable.rb +8 -0
  33. data/app/models/concerns/geocodeable.rb +4 -0
  34. data/app/models/concerns/manual_slug.rb +38 -0
  35. data/app/models/concerns/mappable.rb +77 -0
  36. data/app/models/concerns/seoable.rb +35 -0
  37. data/app/models/concerns/sort_field.rb +12 -0
  38. data/app/models/concerns/sortable.rb +8 -0
  39. data/app/models/concerns/trackable.rb +8 -0
  40. data/app/models/contact_message.rb +6 -0
  41. data/app/models/menu.rb +6 -0
  42. data/app/models/news.rb +5 -0
  43. data/app/models/page.rb +6 -0
  44. data/app/views/contact_mailer/new_message_email.html.haml +15 -0
  45. data/app/views/contacts/new.html.haml +10 -0
  46. data/app/views/contacts/sent.html.haml +4 -0
  47. data/app/views/errors/_base.html.haml +3 -0
  48. data/app/views/errors/error_403.html.haml +1 -0
  49. data/app/views/errors/error_404.html.haml +1 -0
  50. data/app/views/errors/error_500.html.haml +1 -0
  51. data/app/views/news/index.html.haml +8 -0
  52. data/app/views/news/show.html.haml +8 -0
  53. data/app/views/pages/show.html.haml +1 -0
  54. data/app/views/rails_admin/main/_check_boxes.html.haml +27 -0
  55. data/app/views/rails_admin/main/_form_raw.html.haml +1 -0
  56. data/app/views/search/index.html.haml +19 -0
  57. data/app/views/shared/_admin_link.html.haml +3 -0
  58. data/app/views/shared/_messages.html.haml +7 -0
  59. data/app/views/shared/_meta.html.haml +6 -0
  60. data/app/views/shared/_obj.html.haml +14 -0
  61. data/app/views/shared/_og.html.haml +4 -0
  62. data/config/locales/en.rocket_admin.yml +6 -0
  63. data/config/locales/en.rs.yml +17 -0
  64. data/config/locales/ru.cancan.yml +4 -0
  65. data/config/locales/ru.devise.yml +65 -0
  66. data/config/locales/ru.kaminari.yml +17 -0
  67. data/config/locales/ru.models.yml +78 -0
  68. data/config/locales/ru.mongoid.yml +450 -0
  69. data/config/locales/ru.rails_admin.yml +147 -0
  70. data/config/locales/ru.rocket_admin.yml +6 -0
  71. data/config/locales/ru.rs.yml +17 -0
  72. data/config/locales/ru.simple_captcha.yml +3 -0
  73. data/config/locales/ru.simple_form.yml +9 -0
  74. data/lib/filename_to_slug.rb +32 -0
  75. data/lib/generators/rocket_cms/admin_generator.rb +20 -0
  76. data/lib/generators/rocket_cms/templates/ability.erb +17 -0
  77. data/lib/generators/rocket_cms/templates/admin.erb +71 -0
  78. data/lib/generators/rocket_cms/utils.rb +22 -0
  79. data/lib/history_tracker.rb +4 -0
  80. data/lib/rails_admin/custom_show_in_app.rb +39 -0
  81. data/lib/rocket_cms.rb +57 -0
  82. data/lib/rocket_cms/admin.rb +128 -0
  83. data/lib/rocket_cms/configuration.rb +54 -0
  84. data/lib/rocket_cms/controller.rb +24 -0
  85. data/lib/rocket_cms/elastic_search.rb +32 -0
  86. data/lib/rocket_cms/engine.rb +4 -0
  87. data/lib/rocket_cms/model.rb +16 -0
  88. data/lib/rocket_cms/models/contact_message.rb +37 -0
  89. data/lib/rocket_cms/models/menu.rb +16 -0
  90. data/lib/rocket_cms/models/news.rb +61 -0
  91. data/lib/rocket_cms/models/page.rb +86 -0
  92. data/lib/rocket_cms/patch.rb +58 -0
  93. data/lib/rocket_cms/rails_admin_menu.rb +137 -0
  94. data/lib/rocket_cms/railtie.rb +39 -0
  95. data/lib/rocket_cms/tasks.rb +14 -0
  96. data/lib/rocket_cms/version.rb +3 -0
  97. data/lib/smart_excerpt.rb +98 -0
  98. data/rocket_cms.gemspec +50 -0
  99. metadata +533 -0
@@ -0,0 +1,54 @@
1
+ module RocketCMS
2
+ def self.configuration
3
+ @configuration ||= Configuration.new
4
+ end
5
+
6
+ def self.configure
7
+ yield configuration
8
+ end
9
+
10
+ class Configuration
11
+ attr_accessor :news_image_styles
12
+ attr_accessor :news_per_page
13
+ attr_accessor :news_excerpt
14
+
15
+ attr_accessor :error_layout
16
+ attr_accessor :menu_max_depth
17
+
18
+ attr_accessor :search_enabled
19
+ attr_accessor :search_per_page
20
+ attr_accessor :search_models
21
+
22
+ attr_accessor :contacts_captcha
23
+ attr_accessor :contacts_fields
24
+ attr_accessor :contacts_message_required
25
+
26
+ def initialize
27
+ @news_image_styles = {
28
+ main: '400x200>',
29
+ thumb: '200x100>',
30
+ }
31
+ @news_per_page = 10
32
+ @news_excerpt = 12
33
+
34
+ @error_layout = 'application'
35
+ @menu_max_depth = 2
36
+
37
+ @search_enabled = false
38
+ @search_per_page = 10
39
+ @search_models = []
40
+
41
+ @contacts_captcha = false
42
+ @contacts_fields = {}
43
+ @contacts_message_required = true
44
+ end
45
+
46
+ def search_enabled=(val)
47
+ @search_enabled = val
48
+ if @search_enabled
49
+ @search_models << 'Page'
50
+ @search_models << 'News'
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,24 @@
1
+ module RocketCMS::Controller
2
+ extend ActiveSupport::Concern
3
+ included do
4
+ include RsErrors
5
+ include RsPages
6
+ include RsMenu
7
+ protect_from_forgery with: :exception
8
+ helper_method :page_title
9
+ end
10
+
11
+ protected
12
+
13
+ def page_title
14
+ if @seo_page.nil?
15
+ Settings.default_title
16
+ else
17
+ @seo_page.page_title
18
+ end
19
+ end
20
+
21
+ def ckeditor_authenticate
22
+ redirect_to '/' unless user_signed_in? && current_user.has_role?('admin')
23
+ end
24
+ end
@@ -0,0 +1,32 @@
1
+ module RocketCMS::ElasticSearch
2
+ extend ActiveSupport::Concern
3
+ included do
4
+ include Mongoid::Elasticsearch
5
+ elasticsearch!({
6
+ index_options: {
7
+ settings: {
8
+ index: {
9
+ analysis: {
10
+ analyzer: {
11
+ my_analyzer: {
12
+ type: "snowball",
13
+ language: "Russian"
14
+ }
15
+ }
16
+ }
17
+ }
18
+ }
19
+ },
20
+ index_mapings: {
21
+ name: {type: 'string', boost: 10, analyzer: 'my_analyzer'},
22
+ content: {type: 'string', boost: 1, analyzer: 'my_analyzer'},
23
+ }
24
+ })
25
+ def es_index?
26
+ enabled
27
+ end
28
+ def as_indexed_json
29
+ {name: name, content: SmartExcerpt.strip_tags(content)}
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,4 @@
1
+ module RocketCMS
2
+ class Engine < ::Rails::Engine
3
+ end
4
+ end
@@ -0,0 +1,16 @@
1
+ module RocketCMS
2
+ module Model
3
+ extend ActiveSupport::Concern
4
+ included do
5
+ include Mongoid::Document
6
+ include Mongoid::Timestamps::Short
7
+ include ActiveModel::ForbiddenAttributesProtection
8
+ include Trackable
9
+ include BooleanField
10
+ include SortField
11
+ include Mongoid::Paperclip
12
+ include SmartExcerpt
13
+ include SimpleCaptcha::ModelHelpers
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,37 @@
1
+ module RocketCMS
2
+ module Models
3
+ module ContactMessage
4
+ extend ActiveSupport::Concern
5
+ include RocketCMS::Model
6
+
7
+ included do
8
+ apply_simple_captcha
9
+
10
+ field :name, type: String
11
+ field :email, type: String
12
+ field :phone, type: String
13
+ field :content, type: String
14
+
15
+ validates_email_format_of :email, unless: 'email.blank?'
16
+
17
+ if RocketCMS.configuration.contacts_message_required
18
+ validates_presence_of :content
19
+ end
20
+
21
+ validate do
22
+ if email.blank? && phone.blank?
23
+ errors.add(:email, I18n.t('rs.no_contact_info'))
24
+ end
25
+ end
26
+
27
+ RocketCMS.configuration.contacts_fields.each_pair do |fn, ft|
28
+ field fn, type: ft
29
+ end
30
+
31
+ after_create do
32
+ ContactMailer.new_message_email(self).deliver
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,16 @@
1
+ module RocketCMS
2
+ module Models
3
+ module Menu
4
+ extend ActiveSupport::Concern
5
+ include RocketCMS::Model
6
+ include Enableable
7
+ include ManualSlug
8
+
9
+ included do
10
+ field :name, type: String
11
+ manual_slug :name
12
+ has_and_belongs_to_many :pages, inverse_of: :menus
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,61 @@
1
+ module RocketCMS
2
+ module Models
3
+ module News
4
+ extend ActiveSupport::Concern
5
+ include RocketCMS::Model
6
+ include Seoable
7
+ include Enableable
8
+ if RocketCMS.configuration.search_enabled
9
+ include RocketCMS::ElasticSearch
10
+ end
11
+ unless RocketCMS.configuration.news_image_styles.nil?
12
+ include Mongoid::Paperclip
13
+ end
14
+ include ManualSlug
15
+
16
+ included do
17
+ field :time, type: Time
18
+ index({enabled: 1, time: 1})
19
+ scope :after_now, -> { where(:time.lt => Time.now) }
20
+
21
+ field :excerpt, type: String
22
+ field :content, type: String
23
+
24
+ unless RocketCMS.configuration.news_image_styles.nil?
25
+ has_mongoid_attached_file :image, styles: RocketCMS.configuration.news_image_styles
26
+ validates_attachment_content_type :image, :content_type => ['image/gif', 'image/jpeg', 'image/jpg', 'image/png'], if: :image?
27
+ end
28
+
29
+ validates_presence_of :name, :content
30
+
31
+ before_validation do
32
+ self.time = Time.now if self.time.blank?
33
+ end
34
+
35
+ manual_slug :report_slug
36
+
37
+ scope :by_date, -> { desc(:time) }
38
+ scope :recent, ->(count = 5) { enabled.after_now.by_date.limit(count) }
39
+ unless RocketCMS.configuration.news_per_page.nil?
40
+ paginates_per RocketCMS.configuration.news_per_page
41
+ end
42
+
43
+ smart_excerpt :excerpt, :content, RocketCMS.configuration.news_excerpt
44
+
45
+ RocketCMS.apply_patches self
46
+ end
47
+
48
+ def report_slug
49
+ time.strftime('%Y-%m-%d') + '-' + name[0..20]
50
+ end
51
+
52
+ def html5_date
53
+ time.strftime('%Y-%m-%d')
54
+ end
55
+
56
+ def format_date
57
+ time.strftime('%d.%m.%Y')
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,86 @@
1
+ module RocketCMS
2
+ module Models
3
+ module Page
4
+ extend ActiveSupport::Concern
5
+
6
+ include RocketCMS::Model
7
+ include Enableable
8
+ include Seoable
9
+ include ManualSlug
10
+ if RocketCMS.configuration.search_enabled
11
+ include RocketCMS::ElasticSearch
12
+ end
13
+
14
+ included do
15
+ field :regexp, type: String
16
+ field :redirect, type: String
17
+ field :content, type: String
18
+ field :fullpath, type: String
19
+
20
+ scope :sorted, -> { order_by([:lft, :asc]) }
21
+ scope :menu, ->(menu_id) { enabled.sorted.where(menu_ids: menu_id) }
22
+
23
+ has_and_belongs_to_many :menus, inverse_of: :pages
24
+
25
+ acts_as_nested_set
26
+
27
+ manual_slug :name
28
+
29
+ before_validation do
30
+ self.fullpath = "/pages/#{slug}" if self.fullpath.blank?
31
+ end
32
+ validates_uniqueness_of :fullpath
33
+
34
+ validates_presence_of :name
35
+ end
36
+
37
+ def get_fullpath
38
+ redirect.blank? ? fullpath : redirect
39
+ end
40
+
41
+ def has_content?
42
+ @content_used.nil? && !content.blank?
43
+ end
44
+
45
+ def page_content
46
+ if @content_used.nil?
47
+ @content_used = true
48
+ if content.nil?
49
+ ''
50
+ else
51
+ content.gsub(/\{\{(.*?)\}\}/) do
52
+ Settings.get($1).val
53
+ end
54
+ end
55
+ else
56
+ ''
57
+ end
58
+ end
59
+
60
+ def is_current?(url)
61
+ if fullpath == '/'
62
+ url == '/'
63
+ else
64
+ url.match(clean_regexp)
65
+ end
66
+ end
67
+
68
+ def clean_regexp
69
+ if regexp.blank?
70
+ /^#{Regexp.escape(fullpath)}$/
71
+ else
72
+ begin
73
+ /#{regexp}/
74
+ rescue
75
+ # not a valid regexp - treat as literal search string
76
+ /#{Regexp.escape(regexp)}/
77
+ end
78
+ end
79
+ end
80
+
81
+ def nav_options
82
+ {highlights_on: clean_regexp}
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,58 @@
1
+ module RocketCMS
2
+ class PatchDSL
3
+ def self.call(&blk)
4
+ instance = new
5
+ instance.instance_eval(&blk)
6
+ instance.instance_variables.inject({}) { |result_hash, instance_variable|
7
+ result_hash[instance_variable[1..-1].to_sym] = instance.instance_variable_get(instance_variable)
8
+ result_hash
9
+ }
10
+ end
11
+
12
+ def initialize
13
+ @model = Proc.new {}
14
+ @admin = Proc.new {}
15
+ @list = Proc.new {}
16
+ @show = Proc.new {}
17
+ @edit = Proc.new {}
18
+ @export = Proc.new {}
19
+ end
20
+
21
+ def model(&blk); @model = blk; end
22
+ def admin(&blk); @admin = blk; end
23
+ def list(&blk); @list = blk; end
24
+ def show(&blk); @show = blk; end
25
+ def edit(&blk); @edit = blk; end
26
+ def export(&blk); @export = blk; end
27
+ end
28
+
29
+ @@patches = {}
30
+
31
+ def self.only_patches(s, sections)
32
+ s.instance_eval do
33
+ sections.each do |section|
34
+ send section do
35
+ RocketCMS.apply_patches self
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ def self.apply_patches(s)
42
+ if s.class.name.index('RailsAdmin::Config::Sections::').nil?
43
+ if s.class.name == 'RailsAdmin::Config::Model'
44
+ s.instance_eval(&@@patches[s.abstract_model.model_name][:admin]) unless @@patches[s.abstract_model.model_name].nil?
45
+ else
46
+ s.instance_eval(&@@patches[s.name][:model]) unless @@patches[s.name].nil?
47
+ end
48
+ else
49
+ model = s.abstract_model.model_name
50
+ action = s.class.name.split('::')[-1].downcase.to_sym
51
+ s.instance_eval(&@@patches[model][action]) unless @@patches[model].nil?
52
+ end
53
+ end
54
+
55
+ def self.patch(model, &blk)
56
+ @@patches[model] = PatchDSL.call(&blk)
57
+ end
58
+ end
@@ -0,0 +1,137 @@
1
+ module RailsAdmin
2
+ module Config
3
+ module Fields
4
+ module Types
5
+ class Menu < RailsAdmin::Config::Fields::Base
6
+ # Register field type for the type loader
7
+ RailsAdmin::Config::Fields::Types::register(self)
8
+ include RailsAdmin::Engine.routes.url_helpers
9
+
10
+ register_instance_option :pretty_value do
11
+ obj = bindings[:object]
12
+ ret = []
13
+ menus = Rails.cache.fetch 'menus', expires_in: 2.minutes do
14
+ ::Menu.all.map { |m| {id: m.id.to_s, name: m.name } }
15
+ end
16
+ menus.each do |m|
17
+ on = obj.menu_ids.include?(BSON::ObjectId.from_string(m[:id]))
18
+ ret << bindings[:view].link_to(
19
+ m[:name],
20
+ bindings[:view].toggle_menu_path(model_name: @abstract_model, id: obj.id, menu: m[:id], on: !on),
21
+ #method: :post,
22
+ title: m[:name],
23
+ class: "btn btn-mini #{on ? "btn-success" : "btn-danger"}",
24
+ style: 'margin-bottom: 5px;',
25
+ onclick: 'var $t = $(this); $.ajax({type: "POST", url: $t.attr("href"), data: {ajax:true}, success: function(r) { $t.attr("href", r.href); $t.attr("class", r.class); }, error: function(e) { alert(e.responseText); }}); return false;'
26
+ )
27
+ end
28
+ ('<div style="white-space: normal;">' + ret.join(' ') + '</div>').html_safe
29
+ end
30
+
31
+ register_instance_option :formatted_value do
32
+ pretty_value
33
+ end
34
+
35
+ register_instance_option :export_value do
36
+ nil
37
+ end
38
+
39
+ register_instance_option :partial do
40
+ :form_raw
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ module RailsAdmin
49
+ module Config
50
+ module Actions
51
+ class ToggleMenu < Base
52
+ RailsAdmin::Config::Actions.register(self)
53
+
54
+ # Is the action acting on the root level (Example: /admin/contact)
55
+ register_instance_option :root? do
56
+ false
57
+ end
58
+
59
+ register_instance_option :collection? do
60
+ false
61
+ end
62
+
63
+ # Is the action on an object scope (Example: /admin/team/1/edit)
64
+ register_instance_option :member? do
65
+ true
66
+ end
67
+
68
+ register_instance_option :controller do
69
+ proc do
70
+ ajax_link = Proc.new do |am, obj, menu, on|
71
+ render json: {
72
+ href: toggle_menu_path(model_name: am, id: obj.id, menu: menu.id, on: !on),
73
+ class: "btn btn-mini #{on ? "btn-success" : "btn-danger"}",
74
+ }
75
+ end
76
+ if params['id'].present?
77
+ begin
78
+ @object = @abstract_model.model.find(params['id'])
79
+ @menu = ::Menu.find(params[:menu])
80
+ if params[:on] == 'true'
81
+ @object.menus << @menu
82
+ else
83
+ @object.menus.delete(@menu)
84
+ end
85
+
86
+ if @object.save
87
+ if params['ajax'].present?
88
+ if params[:on] == 'true'
89
+ ajax_link.call(@abstract_model, @object, @menu, true)
90
+ else
91
+ ajax_link.call(@abstract_model, @object, @menu, false)
92
+ end
93
+ else
94
+ if params[:on] == 'true'
95
+ flash[:success] = I18n.t('rs.m.enabled', menu: @menu.name)
96
+ else
97
+ flash[:success] = I18n.t('rs.m.disabled', menu: @menu.name)
98
+ end
99
+ end
100
+ else
101
+ if params['ajax'].present?
102
+ render text: @object.errors.full_messages.join(', '), layout: false, status: 422
103
+ else
104
+ flash[:error] = @object.errors.full_messages.join(', ')
105
+ end
106
+ end
107
+ rescue Exception => e
108
+ if params['ajax'].present?
109
+ render text: I18n.t('rs.m.error', err: e.to_s), status: 422
110
+ else
111
+ flash[:error] = I18n.t('rs.m.error', err: e.to_s)
112
+ end
113
+ end
114
+ else
115
+ if params['ajax'].present?
116
+ render text: I18n.t('rs.m.no_id'), status: 422
117
+ else
118
+ flash[:error] = I18n.t('rs.m.no_id')
119
+ end
120
+
121
+ end
122
+
123
+ redirect_to :back unless params['ajax'].present?
124
+ end
125
+ end
126
+
127
+ register_instance_option :link_icon do
128
+ 'icon-move'
129
+ end
130
+
131
+ register_instance_option :http_methods do
132
+ [:post]
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end