effective_regions 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +580 -0
  4. data/Rakefile +23 -0
  5. data/app/assets/images/effective/templates/image_and_title.png +0 -0
  6. data/app/assets/javascripts/effective/snippets/current_user_info.js.coffee +24 -0
  7. data/app/controllers/effective/regions_controller.rb +154 -0
  8. data/app/helpers/effective_regions_helper.rb +108 -0
  9. data/app/models/concerns/acts_as_regionable.rb +34 -0
  10. data/app/models/effective/access_denied.rb +17 -0
  11. data/app/models/effective/region.rb +44 -0
  12. data/app/models/effective/snippets/current_user_info.rb +12 -0
  13. data/app/models/effective/snippets/snippet.rb +69 -0
  14. data/app/models/effective/templates/image_and_title.rb +13 -0
  15. data/app/models/effective/templates/template.rb +26 -0
  16. data/app/views/effective/snippets/_current_user_info.html.haml +10 -0
  17. data/app/views/effective/templates/_image_and_title.html.haml +5 -0
  18. data/config/routes.rb +19 -0
  19. data/db/migrate/01_create_effective_regions.rb.erb +23 -0
  20. data/lib/effective_regions.rb +57 -0
  21. data/lib/effective_regions/engine.rb +27 -0
  22. data/lib/effective_regions/version.rb +3 -0
  23. data/lib/generators/effective_regions/install_generator.rb +32 -0
  24. data/lib/generators/templates/README +1 -0
  25. data/lib/generators/templates/effective_regions.rb +71 -0
  26. data/lib/tasks/effective_regions_tasks.rake +4 -0
  27. data/spec/dummy/README.rdoc +261 -0
  28. data/spec/dummy/Rakefile +7 -0
  29. data/spec/dummy/app/assets/javascripts/application.js +15 -0
  30. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  31. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  32. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  33. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  34. data/spec/dummy/config.ru +4 -0
  35. data/spec/dummy/config/application.rb +59 -0
  36. data/spec/dummy/config/boot.rb +10 -0
  37. data/spec/dummy/config/database.yml +25 -0
  38. data/spec/dummy/config/environment.rb +5 -0
  39. data/spec/dummy/config/environments/development.rb +37 -0
  40. data/spec/dummy/config/environments/production.rb +67 -0
  41. data/spec/dummy/config/environments/test.rb +37 -0
  42. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  43. data/spec/dummy/config/initializers/inflections.rb +15 -0
  44. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  45. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  46. data/spec/dummy/config/initializers/session_store.rb +8 -0
  47. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  48. data/spec/dummy/config/locales/en.yml +5 -0
  49. data/spec/dummy/config/routes.rb +58 -0
  50. data/spec/dummy/db/development.sqlite3 +0 -0
  51. data/spec/dummy/db/schema.rb +16 -0
  52. data/spec/dummy/db/test.sqlite3 +0 -0
  53. data/spec/dummy/log/development.log +20 -0
  54. data/spec/dummy/log/test.log +2 -0
  55. data/spec/dummy/public/404.html +26 -0
  56. data/spec/dummy/public/422.html +26 -0
  57. data/spec/dummy/public/500.html +25 -0
  58. data/spec/dummy/public/favicon.ico +0 -0
  59. data/spec/dummy/script/rails +6 -0
  60. data/spec/effective_regions_spec.rb +7 -0
  61. data/spec/spec_helper.rb +34 -0
  62. data/spec/support/factories.rb +1 -0
  63. metadata +199 -0
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+
8
+ # Our tasks
9
+ load 'lib/tasks/effective_regions_tasks.rake'
10
+
11
+ # Testing tasks
12
+ APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
13
+ load 'rails/tasks/engine.rake'
14
+
15
+ Bundler::GemHelper.install_tasks
16
+
17
+ require 'rspec/core'
18
+ require 'rspec/core/rake_task'
19
+
20
+ desc "Run all specs in spec directory (excluding plugin specs)"
21
+ RSpec::Core::RakeTask.new(:spec => 'app:db:test:prepare')
22
+
23
+ task :default => :spec
@@ -0,0 +1,24 @@
1
+ CKEDITOR.dialog.add 'current_user_info', (editor) ->
2
+ title: 'Current User info',
3
+ minWidth: 200,
4
+ minHeight: 100,
5
+ contents: [
6
+ {
7
+ id: 'current_user_info',
8
+ elements: [
9
+ {
10
+ id: 'method',
11
+ type: 'select',
12
+ label: 'Current User Info',
13
+ items: [
14
+ ['E-mail', 'email'],
15
+ ['Full Name', 'full_name'],
16
+ ['First Name', 'first_name'],
17
+ ['Last Name', 'last_name']
18
+ ],
19
+ setup: (widget) -> this.setValue(widget.data.method),
20
+ commit: (widget) -> widget.setData('method', this.getValue())
21
+ }
22
+ ]
23
+ }
24
+ ]
@@ -0,0 +1,154 @@
1
+ module Effective
2
+ class RegionsController < ApplicationController
3
+ respond_to :html, :json
4
+ layout false
5
+
6
+ before_filter :authenticate_user! if defined?(Devise)
7
+ skip_log_page_views :quiet => true, :only => [:snippet, :snippets, :templates] if defined?(EffectiveLogging)
8
+
9
+ skip_before_filter :verify_authenticity_token, :only => [:update]
10
+
11
+ def edit
12
+ EffectiveRegions.authorized?(self, :edit, Effective::Region.new())
13
+
14
+ cookies['effective_regions_editting'] = {:value => request.referrer, :path => '/'}
15
+
16
+ # TODO: turn this into a cookie or something better.
17
+ redirect_to request.url.gsub('/edit', '') + '?edit=true'
18
+ end
19
+
20
+ def update
21
+ javascript_should_refresh_page = ''
22
+
23
+ Effective::Region.transaction do
24
+ (request.fullpath.slice!(0..4) rescue nil) if request.fullpath.to_s.starts_with?('/edit') # This is so the before_save_method can reference the real current page
25
+
26
+ region_params.each do |key, vals| # article_section_2_title => {:content => '<p></p>'}
27
+ to_save = nil # Which object, the regionable, or the region (if global) to save
28
+
29
+ regionable, title = find_regionable(key)
30
+
31
+ if regionable
32
+ EffectiveRegions.authorized?(self, :update, regionable) # can I update the regionable object?
33
+
34
+ region = regionable.regions.find { |region| region.title == title }
35
+ region ||= regionable.regions.build(:title => title)
36
+
37
+ to_save = regionable
38
+ else
39
+ region = Effective::Region.global.where(:title => title).first_or_initialize
40
+ EffectiveRegions.authorized?(self, :update, region) # can I update the global region?
41
+
42
+ to_save = region
43
+ end
44
+
45
+ region.content = cleanup(vals[:content])
46
+
47
+ region.snippets = HashWithIndifferentAccess.new()
48
+ (vals[:snippets] || []).each { |snippet, vals| region.snippets[snippet] = vals }
49
+
50
+ # Last chance for a developer to make some changes here
51
+ if (run_before_save_method(region, regionable) rescue nil) == :refresh
52
+ javascript_should_refresh_page = 'refresh'
53
+ end
54
+
55
+ to_save.save!
56
+ end
57
+
58
+ render :text => javascript_should_refresh_page, :status => 200
59
+ return
60
+ end
61
+
62
+ render :text => 'error', :status => :unprocessable_entity
63
+ end
64
+
65
+ def snippets
66
+ EffectiveRegions.authorized?(self, :edit, Effective::Region.new())
67
+
68
+ retval = {}
69
+ EffectiveRegions.snippets.each do |snippet|
70
+ retval[snippet.class_name] = {
71
+ :dialog_url => snippet.snippet_dialog_url,
72
+ :label => snippet.snippet_label,
73
+ :description => snippet.snippet_description,
74
+ :inline => snippet.snippet_inline,
75
+ :editables => snippet.snippet_editables,
76
+ :tag => snippet.snippet_tag.to_s
77
+ #:template => ActionView::Base.new(ActionController::Base.view_paths, {}, ActionController::Base.new).render(:partial => snippet.to_partial_path, :object => snippet, :locals => {:snippet => snippet})
78
+ }
79
+ end
80
+
81
+ render :json => retval
82
+ end
83
+
84
+ def snippet # This is a GET. CKEDITOR passes us data, we need to render the non-editable content
85
+ klass = "Effective::Snippets::#{region_params[:name].try(:classify)}".safe_constantize
86
+
87
+ if klass.present?
88
+ @snippet = klass.new(region_params[:data])
89
+ render :partial => @snippet.to_partial_path, :object => @snippet, :locals => {:snippet => @snippet, :snippet_preview => true}
90
+ else
91
+ render :text => "Missing class Effective::Snippets::#{region_params[:name].try(:classify)}"
92
+ end
93
+ end
94
+
95
+ def templates
96
+ EffectiveRegions.authorized?(self, :edit, Effective::Region.new())
97
+
98
+ retval = EffectiveRegions.templates.map do |template|
99
+ {
100
+ :title => template.title,
101
+ :description => template.description,
102
+ :image => template.image || "#{template.class_name}.png",
103
+ :html => render_to_string(:partial => template.to_partial_path, :object => template, :locals => {:template => template})
104
+ }
105
+ end
106
+
107
+ render :json => retval
108
+ end
109
+
110
+ protected
111
+
112
+ def find_regionable(key)
113
+ regionable = nil
114
+ title = nil
115
+
116
+ if(class_name, id, title = key.scan(/(\w+)_(\d+)_(\w+)/).flatten).present?
117
+ regionable = (class_name.classify.safe_constantize).find(id) rescue nil
118
+ end
119
+
120
+ return regionable, (title || key)
121
+ end
122
+
123
+ def cleanup(str)
124
+ (str || '').tap do |str|
125
+ str.chomp!('<p>&nbsp;</p>') # Remove any trailing empty <p>'s
126
+ str.gsub!("\n", '')
127
+ str.strip!
128
+ end
129
+ end
130
+
131
+ def region_params
132
+ begin
133
+ params.require(:effective_regions).permit!
134
+ rescue => e
135
+ params[:effective_regions]
136
+ end
137
+ end
138
+
139
+ private
140
+
141
+ def run_before_save_method(region, regionable)
142
+ return nil if region == nil
143
+
144
+ if EffectiveRegions.before_save_method.respond_to?(:call)
145
+ view_context.instance_exec(region, (regionable || :global), &EffectiveRegions.before_save_method)
146
+ elsif EffectiveRegions.before_save_method.kind_of?(Symbol)
147
+ self.instance_exec(self, region, (regionable || :global), &EffectiveRegions.before_save_method)
148
+ end
149
+
150
+ end
151
+
152
+ end
153
+ end
154
+
@@ -0,0 +1,108 @@
1
+ module EffectiveRegionsHelper
2
+ def effective_region(*args)
3
+ options = args.extract_options!
4
+ block_given? ? ckeditor_region(args, options) { yield } : ckeditor_region(args, options)
5
+ end
6
+
7
+ def simple_effective_region(*args)
8
+ (options = args.extract_options!).merge!(:type => :simple)
9
+ block_given? ? ckeditor_region(args, options) { yield } : ckeditor_region(args, options)
10
+ end
11
+
12
+ def snippet_effective_region(*args)
13
+ (options = args.extract_options!).merge!(:type => :snippets)
14
+ block_given? ? ckeditor_region(args, options) { yield } : ckeditor_region(args, options)
15
+ end
16
+
17
+ def wrapped_snippet_effective_region(*args)
18
+ (options = args.extract_options!).merge!(:type => :wrapped_snippets)
19
+ block_given? ? ckeditor_region(args, options) { yield } : ckeditor_region(args, options)
20
+ end
21
+
22
+ # Loads the Ckeditor Javascript & Stylesheets only when in edit mode
23
+ def effective_regions_include_tags
24
+ if effectively_editting?
25
+ javascript_include_tag('effective_ckeditor') + stylesheet_link_tag('effective_ckeditor')
26
+ end
27
+ end
28
+
29
+ def effectively_editting?
30
+ @effectively_editting ||= request.fullpath.include?('?edit=true')
31
+ end
32
+
33
+ private
34
+
35
+ def ckeditor_region(args, options = {}, &block)
36
+ obj = args.first
37
+ title = args.last.to_s.parameterize
38
+ editable_tag = options.delete(:editable_tag) || :div
39
+
40
+ # Set up the editable div options we need to send to ckeditor
41
+ if effectively_editting?
42
+ opts = {
43
+ :contenteditable => true,
44
+ 'data-effective-ckeditor' => (options.delete(:type) || :full).to_s,
45
+ 'data-allowed-snippets' => [options.delete(:snippets)].flatten.compact.to_json,
46
+ :style => ['-webkit-user-modify: read-write;', options.delete(:style), ('display: inline;' if options.delete(:inline))].compact.join(' '),
47
+ :class => ['effective-region', options.delete(:class)].compact.join(' ')
48
+ }.merge(options)
49
+ end
50
+
51
+ if obj.kind_of?(ActiveRecord::Base)
52
+ raise StandardError.new('Object passed to effective_region helper must declare act_as_regionable') unless obj.respond_to?(:acts_as_regionable)
53
+
54
+ region = obj.regions.find { |region| region.title == title }
55
+
56
+ if effectively_editting?
57
+ can_edit = (EffectiveRegions.authorized?(controller, :update, obj) rescue false)
58
+ opts[:id] = [model_name_from_record_or_class(obj).param_key(), obj.id, title].join('_')
59
+ end
60
+ else # This is a global region
61
+ regions = (@effective_regions_global ||= Effective::Region.global.to_a)
62
+ region = regions.find { |region| region.title == title } || Effective::Region.new(:title => title)
63
+
64
+ if effectively_editting?
65
+ can_edit = (EffectiveRegions.authorized?(controller, :update, region) rescue false)
66
+ opts[:id] = title.to_s.parameterize
67
+ end
68
+ end
69
+
70
+ if effectively_editting? && can_edit # If we need the editable div
71
+ content_tag(editable_tag, opts) do
72
+ region.try(:content).present? ? render_region(region, true) : ((capture(&block).strip.html_safe) if block_given?)
73
+ end
74
+ else
75
+ region.try(:content).present? ? render_region(region, false) : ((capture(&block).strip.html_safe) if block_given?)
76
+ end
77
+ end
78
+
79
+ def render_region(region, can_edit = true)
80
+ return '' unless region
81
+
82
+ region.content.tap do |html|
83
+ html.scan(/\[(snippet_\d+)\]/).flatten.uniq.each do |id| # find snippet_1 and replace with snippet content
84
+ snippet = region.snippet_objects.find { |snippet| snippet.id == id }
85
+ html.gsub!("[#{id}]", render_snippet(snippet, can_edit)) if snippet
86
+ end
87
+ end.html_safe
88
+ end
89
+
90
+ def render_snippet(snippet, can_edit = true)
91
+ return '' unless snippet
92
+
93
+ if Rails.env.production?
94
+ content = render(:partial => snippet.to_partial_path, :object => snippet, :locals => {:snippet => snippet}) rescue ''
95
+ else
96
+ content = render(:partial => snippet.to_partial_path, :object => snippet, :locals => {:snippet => snippet})
97
+ end
98
+
99
+ if effectively_editting? && can_edit
100
+ content_tag(snippet.snippet_tag, content, :data => {'effective-snippet' => snippet.class_name, 'snippet-data' => snippet.data().to_json})
101
+ else
102
+ content
103
+ end.html_safe
104
+ end
105
+
106
+
107
+
108
+ end
@@ -0,0 +1,34 @@
1
+ module ActsAsRegionable
2
+ extend ActiveSupport::Concern
3
+
4
+ module ActiveRecord
5
+ def acts_as_regionable(*options)
6
+ @acts_as_regionable_opts = options || []
7
+ include ::ActsAsRegionable
8
+ end
9
+ end
10
+
11
+ included do
12
+ has_many :regions, :as => :regionable, :class_name => 'Effective::Region', :dependent => :delete_all, :autosave => true
13
+ end
14
+
15
+ module ClassMethods
16
+ end
17
+
18
+ def acts_as_regionable
19
+ true
20
+ end
21
+
22
+ def snippet_objects(klass = nil)
23
+ objs = regions.map { |region| region.snippet_objects }.flatten
24
+
25
+ if klass
26
+ objs = objs.select { |obj| obj.class == klass }
27
+ else
28
+ objs
29
+ end
30
+
31
+ end
32
+
33
+ end
34
+
@@ -0,0 +1,17 @@
1
+ unless defined?(Effective::AccessDenied)
2
+ module Effective
3
+ class AccessDenied < StandardError
4
+ attr_reader :action, :subject
5
+
6
+ def initialize(message = nil, action = nil, subject = nil)
7
+ @message = message
8
+ @action = action
9
+ @subject = subject
10
+ end
11
+
12
+ def to_s
13
+ @message || I18n.t(:'unauthorized.default', :default => 'Access Denied')
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,44 @@
1
+ module Effective
2
+ class Region < ActiveRecord::Base
3
+ self.table_name = EffectiveRegions.regions_table_name.to_s
4
+
5
+ belongs_to :regionable, :polymorphic => true
6
+
7
+ structure do
8
+ title :string, :validates => [:presence]
9
+ content :text
10
+ snippets :text
11
+
12
+ timestamps
13
+ end
14
+
15
+ serialize :snippets, HashWithIndifferentAccess
16
+
17
+ scope :global, -> { where("#{EffectiveRegions.regions_table_name}.regionable_type IS NULL").where("#{EffectiveRegions.regions_table_name}.regionable_id IS NULL") }
18
+
19
+ def snippets
20
+ self[:snippets] || HashWithIndifferentAccess.new()
21
+ end
22
+
23
+ # Hash of the Snippets objectified
24
+ #
25
+ # Returns a Hash of {'snippet_1' => CurrentUserInfo.new(snippets[:key]['options'])}
26
+ def snippet_objects
27
+ @snippet_objects ||= snippets.map do |key, snippet| # Key here is 'snippet_1'
28
+ if snippet['class_name']
29
+ klass = "Effective::Snippets::#{snippet['class_name'].classify}".safe_constantize
30
+ klass.new(snippet.merge!(:region => self, :id => key)) if klass
31
+ end
32
+ end.compact
33
+ end
34
+
35
+ def global?
36
+ regionable_id == nil && regionable_type == nil
37
+ end
38
+
39
+ end
40
+ end
41
+
42
+
43
+
44
+
@@ -0,0 +1,12 @@
1
+ module Effective
2
+ module Snippets
3
+ class CurrentUserInfo < Snippet
4
+ attribute :method, String
5
+
6
+ def snippet_tag
7
+ :span
8
+ end
9
+
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,69 @@
1
+ require 'virtus'
2
+
3
+ module Effective
4
+ module Snippets
5
+ class Snippet
6
+ include Virtus.model
7
+
8
+ attribute :id, String # This will be snippet_12345
9
+ attribute :region, Effective::Region # The region Object
10
+
11
+ # SO I have to add some restrictions on how snippets are built:
12
+
13
+ # Each Snippet has to be a block (or inline) element with nested children.
14
+ # It has to start with a root object
15
+
16
+ # That root object has to do {snippet_data(snippet)}
17
+
18
+
19
+ def initialize(atts = {})
20
+ (atts || {}).each { |k, v| self.send("#{k}=", v) if respond_to?("#{k}=") }
21
+ end
22
+
23
+ def id
24
+ super.presence || "snippet_#{object_id}"
25
+ end
26
+
27
+ def data
28
+ self.attributes.reject { |k, v| [:region, :id].include?(k) }
29
+ end
30
+
31
+ def to_partial_path
32
+ "effective/snippets/#{class_name}"
33
+ end
34
+
35
+ def class_name
36
+ @class_name ||= self.class.name.demodulize.underscore.to_sym
37
+ end
38
+
39
+ ### The following methods are used for the CKEditor widget creation.
40
+ def snippet_label
41
+ class_name.to_s.humanize
42
+ end
43
+
44
+ def snippet_description
45
+ "Insert #{snippet_label}"
46
+ end
47
+
48
+ def snippet_dialog_url
49
+ "/assets/effective/snippets/#{class_name}.js"
50
+ end
51
+
52
+ # This is the tag that the ckeditor snippet will be created as
53
+ # It supports divs and spans, but that's it
54
+ # No ULs, or LIs
55
+ def snippet_tag
56
+ :div
57
+ end
58
+
59
+ def snippet_inline
60
+ [:span].include?(snippet_tag)
61
+ end
62
+
63
+ def snippet_editables
64
+ false
65
+ end
66
+
67
+ end
68
+ end
69
+ end