newsletter 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +15 -0
- data/.gitignore +20 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +26 -0
- data/Gemfile.lock +199 -0
- data/Guardfile +24 -0
- data/LICENSE +22 -0
- data/MIT-LICENSE +20 -0
- data/Manifest.txt +220 -0
- data/Procfile +2 -0
- data/README +243 -0
- data/README.rdoc +3 -0
- data/Rakefile +29 -0
- data/app/assets/images/newsletter/.gitkeep +0 -0
- data/app/assets/javascripts/newsletter/application.js +15 -0
- data/app/assets/stylesheets/newsletter/application.css +13 -0
- data/app/controllers/newsletter/application_controller.rb +27 -0
- data/app/controllers/newsletter/areas_controller.rb +63 -0
- data/app/controllers/newsletter/designs_controller.rb +51 -0
- data/app/controllers/newsletter/elements_controller.rb +75 -0
- data/app/controllers/newsletter/fields_controller.rb +49 -0
- data/app/controllers/newsletter/newsletters_controller.rb +104 -0
- data/app/controllers/newsletter/pieces_controller.rb +72 -0
- data/app/helpers/newsletter/application_helper.rb +4 -0
- data/app/helpers/newsletter/areas_helper.rb +4 -0
- data/app/helpers/newsletter/assets_helper.rb +4 -0
- data/app/helpers/newsletter/designs_helper.rb +11 -0
- data/app/helpers/newsletter/elements_helper.rb +4 -0
- data/app/helpers/newsletter/field_values_helper.rb +4 -0
- data/app/helpers/newsletter/fields_helper.rb +4 -0
- data/app/helpers/newsletter/newsletters_helper.rb +43 -0
- data/app/helpers/newsletter/pieces_helper.rb +4 -0
- data/app/models/newsletter/area.rb +50 -0
- data/app/models/newsletter/asset.rb +31 -0
- data/app/models/newsletter/design.rb +160 -0
- data/app/models/newsletter/element.rb +169 -0
- data/app/models/newsletter/field/inline_asset.rb +67 -0
- data/app/models/newsletter/field/text.rb +15 -0
- data/app/models/newsletter/field/text_area.rb +15 -0
- data/app/models/newsletter/field.rb +115 -0
- data/app/models/newsletter/field_value.rb +20 -0
- data/app/models/newsletter/newsletter.rb +125 -0
- data/app/models/newsletter/piece.rb +96 -0
- data/app/models/newsletter.rb +11 -0
- data/app/uploaders/newsletter/asset_uploader.rb +51 -0
- data/app/views/layouts/newsletter/application.html.erb +135 -0
- data/app/views/newsletter/areas/_area.html.erb +49 -0
- data/app/views/newsletter/designs/_area_fields.html.erb +4 -0
- data/app/views/newsletter/designs/_form.html.erb +41 -0
- data/app/views/newsletter/designs/_newsletter_area.html.erb +17 -0
- data/app/views/newsletter/designs/edit.html.erb +14 -0
- data/app/views/newsletter/designs/index.html.erb +31 -0
- data/app/views/newsletter/designs/new.html.erb +9 -0
- data/app/views/newsletter/designs/show.html.erb +22 -0
- data/app/views/newsletter/elements/_field_fields.html.erb +27 -0
- data/app/views/newsletter/elements/_form.html.erb +51 -0
- data/app/views/newsletter/elements/_newsletter_field.html.erb +37 -0
- data/app/views/newsletter/elements/edit.html.erb +12 -0
- data/app/views/newsletter/elements/index.html.erb +32 -0
- data/app/views/newsletter/elements/new.html.erb +9 -0
- data/app/views/newsletter/elements/show.html.erb +22 -0
- data/app/views/newsletter/fields/_form.html.erb +15 -0
- data/app/views/newsletter/fields/_inline_asset.html.erb +36 -0
- data/app/views/newsletter/fields/_text.html.erb +4 -0
- data/app/views/newsletter/fields/_text_area.html.erb +7 -0
- data/app/views/newsletter/newsletters/_form.html.erb +21 -0
- data/app/views/newsletter/newsletters/_head.html.erb +48 -0
- data/app/views/newsletter/newsletters/_newsletter.html.erb +6 -0
- data/app/views/newsletter/newsletters/_newsletter_area.html.erb +30 -0
- data/app/views/newsletter/newsletters/archive.html.erb +9 -0
- data/app/views/newsletter/newsletters/edit.html.erb +37 -0
- data/app/views/newsletter/newsletters/index.html.erb +29 -0
- data/app/views/newsletter/newsletters/new.html.erb +9 -0
- data/app/views/newsletter/newsletters/show.html.erb +6 -0
- data/app/views/newsletter/pieces/_form.html.erb +19 -0
- data/app/views/newsletter/pieces/edit.html.erb +11 -0
- data/app/views/newsletter/pieces/new.html.erb +10 -0
- data/app/views/newsletter/pieces/show.html.erb +2 -0
- data/assets/docs/index.html +45 -0
- data/assets/docs/newsletter_templates/index.html +45 -0
- data/config/routes.rb +33 -0
- data/db/migrate/001_newsletter_initial.rb +104 -0
- data/db/migrate/002_carrier_wave_for_assets.rb +9 -0
- data/db/pre-scoped/002_newsletter_scoped.rb +61 -0
- data/doc/README_FOR_APP +2 -0
- data/doc/newsletter.wiki.txt +130 -0
- data/doc/start_rails_engine_with_rspec.txt +43 -0
- data/engine_plan.rb +13 -0
- data/features/step_definitions/webrat_steps.rb +115 -0
- data/features/support/env.rb +17 -0
- data/features/support/paths.rb +27 -0
- data/lib/deleteable.rb +50 -0
- data/lib/newsletter/config.rb +50 -0
- data/lib/newsletter/engine.rb +62 -0
- data/lib/newsletter/settings.rb +50 -0
- data/lib/newsletter/version.rb +3 -0
- data/lib/newsletter.rb +7 -0
- data/lib/tasks/newsletter.rake +84 -0
- data/lib/tasks/newsletter_tasks.rake +4 -0
- data/lib/tasks/rspec.rake +158 -0
- data/newsletter.gemspec +30 -0
- data/newsletters/exports/example-export.yaml +213 -0
- data/script/rails +8 -0
- data/spec/rcov.opts +2 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +41 -0
- data/spec/test_app/README.rdoc +261 -0
- data/spec/test_app/Rakefile +7 -0
- data/spec/test_app/app/assets/javascripts/application.js +15 -0
- data/spec/test_app/app/assets/stylesheets/application.css +13 -0
- data/spec/test_app/app/controllers/application_controller.rb +3 -0
- data/spec/test_app/app/helpers/application_helper.rb +2 -0
- data/spec/test_app/app/mailers/.gitkeep +0 -0
- data/spec/test_app/app/models/.gitkeep +0 -0
- data/spec/test_app/app/views/layouts/application.html.erb +14 -0
- data/spec/test_app/config/application.rb +65 -0
- data/spec/test_app/config/boot.rb +10 -0
- data/spec/test_app/config/database.yml +25 -0
- data/spec/test_app/config/environment.rb +5 -0
- data/spec/test_app/config/environments/development.rb +37 -0
- data/spec/test_app/config/environments/production.rb +67 -0
- data/spec/test_app/config/environments/test.rb +37 -0
- data/spec/test_app/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/test_app/config/initializers/inflections.rb +15 -0
- data/spec/test_app/config/initializers/mime_types.rb +5 -0
- data/spec/test_app/config/initializers/secret_token.rb +7 -0
- data/spec/test_app/config/initializers/session_store.rb +8 -0
- data/spec/test_app/config/initializers/wrap_parameters.rb +14 -0
- data/spec/test_app/config/locales/en.yml +5 -0
- data/spec/test_app/config/newsletter.yml +9 -0
- data/spec/test_app/config/routes.rb +4 -0
- data/spec/test_app/config.ru +4 -0
- data/spec/test_app/db/migrate/20131222171229_newsletter_initial.rb +104 -0
- data/spec/test_app/db/schema.rb +116 -0
- data/spec/test_app/lib/assets/.gitkeep +0 -0
- data/spec/test_app/log/.gitkeep +0 -0
- data/spec/test_app/public/404.html +26 -0
- data/spec/test_app/public/422.html +26 -0
- data/spec/test_app/public/500.html +25 -0
- data/spec/test_app/public/favicon.ico +0 -0
- data/spec/test_app/script/rails +6 -0
- data/zeus.json +22 -0
- metadata +352 -0
@@ -0,0 +1,115 @@
|
|
1
|
+
=begin rdoc
|
2
|
+
Author:: Chris Hauboldt (mailto:biz@lnstar.com)
|
3
|
+
Copyright:: 2009 Lone Star Internet Inc.
|
4
|
+
|
5
|
+
Defines the data behaviour of a Newsletter::Piece's fields. Tied to Newsletter::Pieces through Newsletter::Elements.
|
6
|
+
|
7
|
+
This is a base object and is never directly used, but is used to define different fields through Single Table Inheritance.
|
8
|
+
|
9
|
+
=end
|
10
|
+
|
11
|
+
module Newsletter
|
12
|
+
class Field < ActiveRecord::Base
|
13
|
+
self.table_name = "#{::Newsletter.table_prefix}fields"
|
14
|
+
belongs_to :element, :class_name => 'Newsletter::Element'
|
15
|
+
has_many :field_values, :class_name => 'Newsletter::FieldValue'
|
16
|
+
belongs_to :updated_by, :class_name => 'User'
|
17
|
+
|
18
|
+
validates_presence_of :name
|
19
|
+
=begin
|
20
|
+
FIXME: make this work with deletable or convert to auditable, and extend it to access destroyed records
|
21
|
+
validates_uniqueness_of :name, :scope => [:element_id], :unless => Proc.new { |field|
|
22
|
+
field.new_record? ? Field.find(:first,:conditions =>
|
23
|
+
["name=? and element_id=? and deleted_at is null", field.element_id]).nil? :
|
24
|
+
Field.find(:first,:conditions =>
|
25
|
+
["name=? and element_id=? and id!=? and deleted_at is null", field.element_id,
|
26
|
+
field.id]).nil? }
|
27
|
+
=end
|
28
|
+
attr_accessor :should_destroy
|
29
|
+
|
30
|
+
attr_protected :id
|
31
|
+
|
32
|
+
include Deleteable
|
33
|
+
|
34
|
+
# returns field data so that Newsletter::Design.export(instance) can export itself to a YAML file
|
35
|
+
def export_fields
|
36
|
+
{
|
37
|
+
:name => name,
|
38
|
+
:label => label,
|
39
|
+
:sequence => sequence,
|
40
|
+
:is_required => is_required,
|
41
|
+
:description => description,
|
42
|
+
:type => self[:type],
|
43
|
+
}
|
44
|
+
end
|
45
|
+
|
46
|
+
# returns field data so that Newsletter::Design.export(instance) can export itself to a YAML file
|
47
|
+
def self.import(element,data)
|
48
|
+
return unless data[:type]
|
49
|
+
field = data[:type].constantize.new(
|
50
|
+
:name => data[:name],
|
51
|
+
:label => data[:label],
|
52
|
+
:sequence => data[:sequence],
|
53
|
+
:is_required => data[:is_required],
|
54
|
+
:description => data[:description]
|
55
|
+
)
|
56
|
+
field.element = element
|
57
|
+
field.save
|
58
|
+
end
|
59
|
+
|
60
|
+
# returns a pieces value for a given Newsletter::Field
|
61
|
+
def value_for_piece(piece)
|
62
|
+
get_value(piece).to_s.html_safe
|
63
|
+
end
|
64
|
+
|
65
|
+
# sets a pieces value for a Newsletter::Field
|
66
|
+
def set_value_for_piece(piece,params)
|
67
|
+
params.each_pair do |key,value|
|
68
|
+
set_value(piece,key,value)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def set_value(piece,key,value)
|
73
|
+
field_value = field_values.by_piece(piece).by_key(key).first
|
74
|
+
if field_value
|
75
|
+
field_value.update_attribute(:value,value)
|
76
|
+
else
|
77
|
+
field_value = piece.field_values.build(:key => key,:value => value)
|
78
|
+
field_value.field_id = self.id
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def get_value(piece,key=nil)
|
83
|
+
return field_values.by_piece(piece).by_key(key).first.try(:value) if key
|
84
|
+
field_values.by_piece(piece).first.try(:value)
|
85
|
+
end
|
86
|
+
|
87
|
+
# morph into a different Newsletter::Field type(if a user changes the type of a field)
|
88
|
+
def self.morph(field,type)
|
89
|
+
return field if field.class.name.eql?(type.to_s)
|
90
|
+
return type.constantize.new(field.attributes) if field.new_record?
|
91
|
+
field.update_attribute('type',type)
|
92
|
+
Field.find(field.id)
|
93
|
+
end
|
94
|
+
|
95
|
+
# used in figuring out whether to destroy a field as we are editing fields inside an element form
|
96
|
+
def should_destroy?
|
97
|
+
should_destroy.to_i == 1
|
98
|
+
end
|
99
|
+
|
100
|
+
# find the partial to display a form for creating a Newsletter::Piece
|
101
|
+
def form_partial_path
|
102
|
+
"newsletter/fields/#{type.to_s.gsub(/.*Newsletter::Field::/,'').gsub(/([^A-Z])([A-Z])/,'\\1_\\2').downcase}"
|
103
|
+
end
|
104
|
+
|
105
|
+
# helper for selecting type of field to use when creating/editing a Newsletter::Field.
|
106
|
+
def self.valid_types
|
107
|
+
@valid_types = ['Newsletter::Field::Text','Newsletter::Field::TextArea','Newsletter::Field::InlineAsset']
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
require 'newsletter/field/inline_asset'
|
113
|
+
require 'newsletter/field/text'
|
114
|
+
require 'newsletter/field/text_area'
|
115
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
=begin rdoc
|
2
|
+
Author:: Chris Hauboldt (mailto:biz@lnstar.com)
|
3
|
+
Copyright:: 2009 Lone Star Internet Inc.
|
4
|
+
|
5
|
+
Used to store key-value pairs for a NewsletterPiece's NewsletterFields
|
6
|
+
|
7
|
+
=end
|
8
|
+
|
9
|
+
module Newsletter
|
10
|
+
class FieldValue < ActiveRecord::Base
|
11
|
+
self.table_name = "#{::Newsletter.table_prefix}field_values"
|
12
|
+
belongs_to :piece, :class_name => 'Newsletter::Piece'
|
13
|
+
belongs_to :field, :class_name => 'Newsletter::Field'
|
14
|
+
scope :by_piece, lambda{|piece| where("piece_id IS NOT NULL AND piece_id=?",piece.try(:id))}
|
15
|
+
scope :by_field, lambda{|field| where("field_id IS NOT NULL AND field_id=?",field.try(:id))}
|
16
|
+
scope :by_key, lambda{|key| where(key: key)}
|
17
|
+
|
18
|
+
attr_protected :id
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
=begin rdoc
|
2
|
+
Author:: Chris Hauboldt (mailto:biz@lnstar.com)
|
3
|
+
Copyright:: 2009 Lone Star Internet Inc.
|
4
|
+
|
5
|
+
Newsletter ties user-added data to a Newsletter::Design through Newsletter::Pieces.
|
6
|
+
|
7
|
+
Newsletter also registers itself to be Mailable through the List Manager of Mailing if the plugin exists. (MLM isn't a nice term apparently) It is also the starting point for rendering a newsletter for both the web archive and when being sent as a mailable. A newsletter will show up on an archives page and be available to send in an email after it is "Published". Newsletters may also be Un-Published.
|
8
|
+
|
9
|
+
=end
|
10
|
+
|
11
|
+
module Newsletter
|
12
|
+
class Newsletter < ActiveRecord::Base
|
13
|
+
Rails.logger.info "Loading Newsletter Object"
|
14
|
+
self.table_name = "#{::Newsletter.table_prefix}newsletters"
|
15
|
+
belongs_to :design, :class_name => 'Newsletter::Design'
|
16
|
+
has_many :pieces, :order => 'sequence', :class_name => 'Newsletter::Piece',
|
17
|
+
:conditions => "#{::Newsletter.table_prefix}pieces.deleted_at is null"
|
18
|
+
|
19
|
+
scope :published, {:conditions => "#{::Newsletter.table_prefix}newsletters.published_at is not null",
|
20
|
+
:order => "#{::Newsletter.table_prefix}newsletters.created_at desc"}
|
21
|
+
scope :active, {:conditions => "#{::Newsletter.table_prefix}newsletters.deleted_at is null",
|
22
|
+
:order => "#{::Newsletter.table_prefix}newsletters.created_at desc"}
|
23
|
+
|
24
|
+
validates_presence_of :name
|
25
|
+
|
26
|
+
acts_as_list :column => :sequence
|
27
|
+
|
28
|
+
attr_protected :id
|
29
|
+
|
30
|
+
def headlines
|
31
|
+
pieces.select{|piece| piece.respond_to?(:headline)}
|
32
|
+
end
|
33
|
+
|
34
|
+
# returns the newsletter's content as plain text
|
35
|
+
def email_text
|
36
|
+
"Get the new Newsletter from here: " + public_url + "\n" +
|
37
|
+
'-'*30 + "\n\n" + generate_plain_text('email')
|
38
|
+
end
|
39
|
+
|
40
|
+
# returns the newsletter's content as html text with unsubscribe data(this should be encapsulated in an
|
41
|
+
# "if is_email" block in your design)
|
42
|
+
def email_html
|
43
|
+
generate('email')
|
44
|
+
end
|
45
|
+
|
46
|
+
# Currently using lynx to generate newsletter as text
|
47
|
+
def generate_plain_text(mode='')
|
48
|
+
IO.popen('lynx -stdin --dump','w+') do |lynx|
|
49
|
+
lynx.write generate(mode)
|
50
|
+
lynx.close_write
|
51
|
+
lynx.readlines.join
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def published?
|
56
|
+
!published_at.nil?
|
57
|
+
end
|
58
|
+
|
59
|
+
# sets published_at to current time, so it will be shown in the archives page
|
60
|
+
def publish
|
61
|
+
update_attribute(:published_at, Time.now)
|
62
|
+
end
|
63
|
+
|
64
|
+
# clears published_at so that it is removed from the archives page
|
65
|
+
def unpublish
|
66
|
+
update_attribute(:published_at, nil)
|
67
|
+
end
|
68
|
+
|
69
|
+
# generates a public url for the newsletter
|
70
|
+
def public_url(mode='')
|
71
|
+
"#{::Newsletter.site_url}/newsletters/#{self[:id]}#{mode.blank? ? '' : "/#{mode}"}"
|
72
|
+
end
|
73
|
+
|
74
|
+
# used to generate the newsletter from a model or someplace other than the web stack
|
75
|
+
# FIXME: There has to be a better way, where railsy stuff works ... erb doesn't seem
|
76
|
+
# to be enough, so we're just using the web app
|
77
|
+
# Parameters:
|
78
|
+
# mode: if set to 'email', is_email? will be true as a helper in the designs, for
|
79
|
+
# useage to not include javascript, show/hide subscription links, etc.
|
80
|
+
# substitutions: data to substitute out of the content such as "UNSUBSCRIBE_URL"
|
81
|
+
def generate(mode='')
|
82
|
+
fetch(public_url(mode))
|
83
|
+
end
|
84
|
+
|
85
|
+
# retrieve a newsletter area by name
|
86
|
+
def area(name)
|
87
|
+
design.areas.by_name(name).first
|
88
|
+
end
|
89
|
+
|
90
|
+
# retrieve a list of locals to send to the main layout of the newsletter design view
|
91
|
+
# this is so that we can just "<%= area %>" without putting anything fancy in the view
|
92
|
+
def locals
|
93
|
+
variables = Hash.new
|
94
|
+
design.areas.each do |area|
|
95
|
+
variables[area.name.to_sym] = area
|
96
|
+
end
|
97
|
+
variables
|
98
|
+
end
|
99
|
+
|
100
|
+
def piece_attributes=(piece_attributes)
|
101
|
+
piece_attributes.each do |attributes|
|
102
|
+
pieces.build(attributes)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def areas
|
107
|
+
design.try(:areas).to_a
|
108
|
+
end
|
109
|
+
|
110
|
+
def fetch(uri_str, limit = 10)
|
111
|
+
# You should choose better exception.
|
112
|
+
Rails.logger.debug "Fetching #{uri_str}"
|
113
|
+
raise ArgumentError, 'HTTP redirect too deep' if limit == 0
|
114
|
+
|
115
|
+
response = Net::HTTP.get_response(URI.parse(uri_str))
|
116
|
+
case response
|
117
|
+
when Net::HTTPSuccess then response.body
|
118
|
+
when Net::HTTPRedirection then fetch(response['location'], limit - 1)
|
119
|
+
else
|
120
|
+
response.error!
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
=begin rdoc
|
2
|
+
Author:: Chris Hauboldt (mailto:biz@lnstar.com)
|
3
|
+
Copyright:: 2009 Lone Star Internet Inc.
|
4
|
+
|
5
|
+
NewsletterPieces are the glue that tie data together to form a piece in an area of a design.
|
6
|
+
|
7
|
+
=end
|
8
|
+
|
9
|
+
module Newsletter
|
10
|
+
class Piece < ActiveRecord::Base
|
11
|
+
self.table_name = "#{::Newsletter.table_prefix}pieces"
|
12
|
+
belongs_to :newsletter, :class_name => 'Newsletter::Newsletter'
|
13
|
+
belongs_to :area, :class_name => 'Newsletter::Area'
|
14
|
+
belongs_to :element, :class_name => 'Newsletter::Element'
|
15
|
+
# has_many :fields, :through => :element, :class_name => 'Newsletter::Field'
|
16
|
+
has_many :field_values, :class_name => 'Newsletter::FieldValue'
|
17
|
+
has_many :assets, :dependent => :destroy, :class_name => 'Newsletter::Asset'
|
18
|
+
|
19
|
+
acts_as_list :scope => :newsletter, :column => :sequence
|
20
|
+
|
21
|
+
scope :active, where(:deleted_at => nil)
|
22
|
+
scope :by_area, lambda {|area| {:conditions => {:area_id => area.id, :deleted_at => nil}}}
|
23
|
+
scope :by_newsletter, lambda {|newsletter| {:conditions => {:newsletter_id => newsletter.id, :deleted_at => nil}}}
|
24
|
+
|
25
|
+
# accepts_nested_attributes_for :field_values
|
26
|
+
attr_accessor :field_values_attributes
|
27
|
+
|
28
|
+
attr_protected :id
|
29
|
+
|
30
|
+
# returns locals to be used in its Newsletter::Element design
|
31
|
+
def locals
|
32
|
+
return @locals if @locals.present?
|
33
|
+
@locals = Hash.new
|
34
|
+
fields.each do |field|
|
35
|
+
@locals[field.name.to_sym] = field.value_for_piece(self)
|
36
|
+
define_getter_for_locals(field.name.to_sym) unless respond_to?(field.name.to_sym)
|
37
|
+
end
|
38
|
+
@locals
|
39
|
+
end
|
40
|
+
|
41
|
+
def fields
|
42
|
+
element.try(:fields).try(:uniq) || []
|
43
|
+
end
|
44
|
+
|
45
|
+
def newsletter
|
46
|
+
@newsletter ||= ::Newsletter::Newsletter.find(newsletter_id)
|
47
|
+
end
|
48
|
+
|
49
|
+
# used to save out a piece's fields, since they are inline in a piece's form
|
50
|
+
def field_values_attributes=(values)
|
51
|
+
@field_values_attributes = values
|
52
|
+
end
|
53
|
+
|
54
|
+
def set_field_values
|
55
|
+
return unless defined?(@field_values_attributes) && @field_values_attributes.present?
|
56
|
+
@field_values_attributes.each_pair do |field_id,key_value_pairs|
|
57
|
+
field = Field.find(field_id)
|
58
|
+
field.set_value_for_piece(self,key_value_pairs)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def respond_to?(my_method,use_private=false)
|
63
|
+
return true if super
|
64
|
+
if locals.keys.include?(my_method.to_sym)
|
65
|
+
define_getter_for_locals(my_method)
|
66
|
+
true
|
67
|
+
else
|
68
|
+
false
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def method_missing(*args)
|
73
|
+
if args.length == 1 and locals.has_key?(args[0].to_sym)
|
74
|
+
define_getter_for_locals(args[0])
|
75
|
+
else
|
76
|
+
super
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def save(*args)
|
81
|
+
transaction do
|
82
|
+
set_field_values
|
83
|
+
super
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
def define_getter_for_locals(key)
|
89
|
+
class_eval %Q|
|
90
|
+
def #{key}
|
91
|
+
locals[:#{key}]
|
92
|
+
end|
|
93
|
+
eval key.to_s
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Newsletter
|
2
|
+
end
|
3
|
+
|
4
|
+
require 'newsletter/area'
|
5
|
+
require 'newsletter/asset'
|
6
|
+
require 'newsletter/element'
|
7
|
+
require 'newsletter/field'
|
8
|
+
require 'newsletter/field_value'
|
9
|
+
require 'newsletter/newsletter'
|
10
|
+
require 'newsletter/piece'
|
11
|
+
require 'newsletter/design'
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
class Newsletter::AssetUploader < CarrierWave::Uploader::Base
|
4
|
+
|
5
|
+
# Include RMagick or MiniMagick support:
|
6
|
+
# include CarrierWave::RMagick
|
7
|
+
# include CarrierWave::MiniMagick
|
8
|
+
|
9
|
+
# Choose what kind of storage to use for this uploader:
|
10
|
+
storage :file
|
11
|
+
# storage :fog
|
12
|
+
|
13
|
+
# Override the directory where uploaded files will be stored.
|
14
|
+
# This is a sensible default for uploaders that are meant to be mounted:
|
15
|
+
def store_dir
|
16
|
+
Newsletter::Asset.build_public_dirname(model.id)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Provide a default URL as a default if there hasn't been a file uploaded:
|
20
|
+
# def default_url
|
21
|
+
# # For Rails 3.1+ asset pipeline compatibility:
|
22
|
+
# # ActionController::Base.helpers.asset_path("fallback/" + [version_name, "default.png"].compact.join('_'))
|
23
|
+
#
|
24
|
+
# "/images/fallback/" + [version_name, "default.png"].compact.join('_')
|
25
|
+
# end
|
26
|
+
|
27
|
+
# Process files as they are uploaded:
|
28
|
+
# process :scale => [200, 300]
|
29
|
+
#
|
30
|
+
# def scale(width, height)
|
31
|
+
# # do something
|
32
|
+
# end
|
33
|
+
|
34
|
+
# Create different versions of your uploaded files:
|
35
|
+
# version :thumb do
|
36
|
+
# process :scale => [50, 50]
|
37
|
+
# end
|
38
|
+
|
39
|
+
# Add a white list of extensions which are allowed to be uploaded.
|
40
|
+
# For images you might use something like this:
|
41
|
+
# def extension_white_list
|
42
|
+
# %w(jpg jpeg gif png)
|
43
|
+
# end
|
44
|
+
|
45
|
+
# Override the filename of the uploaded files:
|
46
|
+
# Avoid using model.id or version_name here, see uploader/store.rb for details.
|
47
|
+
# def filename
|
48
|
+
# "something.jpg" if original_filename
|
49
|
+
# end
|
50
|
+
|
51
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<!-- iReach template -->
|
5
|
+
<title><%= h(yield(:title) || "iReach Newsletter Manager") %></title>
|
6
|
+
<link rel="stylesheet" href="https://code.jquery.com/ui/1.10.3/themes/le-frog/jquery-ui.css">
|
7
|
+
<script src="/javascripts/jquery.js" type="text/javascript"></script>
|
8
|
+
<script src="/javascripts/jquery_ujs.js" type="text/javascript"></script>
|
9
|
+
<script src="/javascripts/jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
|
10
|
+
<%= csrf_meta_tags %>
|
11
|
+
|
12
|
+
<link href="/stylesheets/nav.css" media="screen" rel="stylesheet" type="text/css" />
|
13
|
+
<link href="/stylesheets/admin.css" media="screen" rel="stylesheet" type="text/css" />
|
14
|
+
|
15
|
+
<% begin %>
|
16
|
+
<% if admin_nav_menu.present? %>
|
17
|
+
<script>
|
18
|
+
function showMenu(id, id2) {
|
19
|
+
<% for menuitem in admin_nav_menu %>
|
20
|
+
<% if menuitem.admin_menu && menuitem.admin_menu.length > 0 && menuitem.is_visible %>
|
21
|
+
elem = document.getElementById("menu_<%= menuitem.id %>")
|
22
|
+
if (elem) elem.style.display = "none";
|
23
|
+
<% for submenuitem in menuitem.admin_menu.order("menu_order ASC") %>
|
24
|
+
<% if submenuitem.admin_menu && submenuitem.admin_menu.length > 0 && submenuitem.is_visible %>
|
25
|
+
elem = document.getElementById("menu_<%= submenuitem.id %>")
|
26
|
+
if (elem) elem.style.display = "none";
|
27
|
+
<% end %>
|
28
|
+
<% end %>
|
29
|
+
<% end %>
|
30
|
+
<% end %>
|
31
|
+
document.getElementById("menu_"+id).style.display = "inline";
|
32
|
+
if (id2) document.getElementById("menu_"+id2).style.display = "inline";
|
33
|
+
}
|
34
|
+
</script>
|
35
|
+
<% end %>
|
36
|
+
<% rescue => e %>
|
37
|
+
<!-- Error building JS Menu <%= e.message %> <%= e.backtrace.join("\n") %> -->
|
38
|
+
<% end %>
|
39
|
+
|
40
|
+
<%= content_for :head %>
|
41
|
+
</head>
|
42
|
+
<body>
|
43
|
+
|
44
|
+
|
45
|
+
|
46
|
+
<table width="100%" border="0" cellspacing="0" cellpadding="0">
|
47
|
+
<tr>
|
48
|
+
<td><img src="/images/1topLeft.gif" width="269" height="85" /></td>
|
49
|
+
<td width="100%" align="left" background="/images/topMid.gif"><img src="/images/1topMidR.gif" width="435" height="85" /></td>
|
50
|
+
<td align="right"><img src="/images/1topRight.gif" width="62" height="85" /></td>
|
51
|
+
</tr>
|
52
|
+
</table>
|
53
|
+
|
54
|
+
|
55
|
+
<table width="100%" border="0" cellspacing="0" cellpadding="0">
|
56
|
+
|
57
|
+
<tr>
|
58
|
+
<td valign="top" bgcolor="#FFFFFF">
|
59
|
+
<table border="0" cellspacing="0" cellpadding="0" class="menu">
|
60
|
+
<tr>
|
61
|
+
<td valign="top"><img src="/images/menuMainLeft.gif" width="7" height="257" /></td>
|
62
|
+
<td valign="top" class="table-bk">
|
63
|
+
<!-- Start Admin Menu <%= @admin_nav_menu.inspect %> -->
|
64
|
+
<% begin %>
|
65
|
+
<% if admin_nav_menu.present? %>
|
66
|
+
<% for menuitem in admin_nav_menu %>
|
67
|
+
<% if menuitem.admin_menu && menuitem.admin_menu.length > 0 && menuitem.is_visible && menuitem.authorized(session[:admin][:user_id]) %>
|
68
|
+
<span class="category" onClick="javascript:showMenu(<%= menuitem.id %>, 0)" style="cursor:pointer"><%= menuitem.description %></span><br/>
|
69
|
+
<div id="menu_<%= menuitem.id %>" style="display:<%= (params[:menu_id].to_i == menuitem.id) ? 'inline' : 'none' %>">
|
70
|
+
<% for submenuitem in menuitem.admin_menu %>
|
71
|
+
<% if submenuitem.is_visible and submenuitem.authorized(session[:admin][:user_id]) %>
|
72
|
+
<% if submenuitem.admin_menu && submenuitem.admin_menu.length > 0 && submenuitem.is_visible && submenuitem.authorized(session[:admin][:user_id]) %>
|
73
|
+
<span class="menuSM" onClick="javascript:showMenu(<%= menuitem.id %>, <%= submenuitem.id %>)" style="cursor:pointer"><%= submenuitem.description %></span><br/>
|
74
|
+
<div id="menu_<%= submenuitem.id %>" style="display:<%= (params[:submenu_id].to_i == submenuitem.id) ? 'inline' : 'none' %>">
|
75
|
+
<% for subsubmenuitem in submenuitem.admin_menu %>
|
76
|
+
<% if subsubmenuitem.is_visible and subsubmenuitem.authorized(session[:admin][:user_id]) %>
|
77
|
+
<span class="menuSM"><%= link_to menuitem.description, "/#{submenuitem.path}?menu_id=#{submenuitem.id}" %></span><br/>
|
78
|
+
<% end %>
|
79
|
+
<% end %>
|
80
|
+
</div>
|
81
|
+
<% elsif submenuitem.is_visible and submenuitem.authorized(session[:admin][:user_id]) %>
|
82
|
+
<span class="menuSM"><%= link_to submenuitem.description, "/#{submenuitem.path}?menu_id=#{menuitem.id}" %></span><br/>
|
83
|
+
<% end %>
|
84
|
+
<% end %>
|
85
|
+
<% end %>
|
86
|
+
</div>
|
87
|
+
<hr class="line" />
|
88
|
+
<% elsif menuitem.is_visible and menuitem.authorized(session[:admin][:user_id]) %>
|
89
|
+
<%= link_to menuitem.description, "/#{menuitem.path}" %><br/>
|
90
|
+
<hr class="line" />
|
91
|
+
<% end %>
|
92
|
+
<% end %>
|
93
|
+
<% end %>
|
94
|
+
<% rescue => e %>
|
95
|
+
<!-- Error building Menu <%= e.message %> <%= e.backtrace.join("\n") %> -->
|
96
|
+
<% end %>
|
97
|
+
</td>
|
98
|
+
</tr>
|
99
|
+
<tr>
|
100
|
+
<td><img src="/images/menuBotLeft.gif" width="7" height="8" /></td>
|
101
|
+
<td><img src="/images/menuBot.gif" width="176" height="8" /></td></tr>
|
102
|
+
</table>
|
103
|
+
|
104
|
+
|
105
|
+
|
106
|
+
|
107
|
+
</td>
|
108
|
+
<td width="100%" valign="top" bgcolor="#FFFFFF" class="main">
|
109
|
+
|
110
|
+
<% flash.each do |name, msg| -%>
|
111
|
+
<%= content_tag :div, msg, :id => "flash_#{name}" %>
|
112
|
+
<% end -%>
|
113
|
+
|
114
|
+
<% if show_title? -%>
|
115
|
+
<h1><%=h yield(:title) %></h1>
|
116
|
+
<% end -%>
|
117
|
+
|
118
|
+
<%= yield %>
|
119
|
+
|
120
|
+
<%= yield :page_navigation %>
|
121
|
+
</td>
|
122
|
+
|
123
|
+
<td background="/images/1midRight.gif"><img src="/images/1midRight.gif" width="62" height="19"/></td>
|
124
|
+
</tr>
|
125
|
+
</table>
|
126
|
+
|
127
|
+
<table width="100%" border="0" cellspacing="0" cellpadding="0">
|
128
|
+
<tr>
|
129
|
+
<td width="100%" background="/images/1botMid.gif"><img src="/images/1botMid.gif" width="13" height="39" /></td>
|
130
|
+
<td align="right"><img src="/images/1botRight.gif" width="62" height="39" /></td>
|
131
|
+
</tr>
|
132
|
+
</table>
|
133
|
+
|
134
|
+
</body>
|
135
|
+
</html>
|
@@ -0,0 +1,49 @@
|
|
1
|
+
<% unless params[:editor] %>
|
2
|
+
<% area.pieces.by_newsletter(@newsletter).each do |piece| %>
|
3
|
+
<% begin %>
|
4
|
+
<%= render :inline => piece.element.html_text, :locals => piece.locals %>
|
5
|
+
<% rescue => e %>
|
6
|
+
<!-- Error rendering piece: <%= piece.element.html_text %>/ <%= piece.id %> with data <%=h piece.locals.inspect %> and template: <%=h piece.element.html_text %> <br/> <%= e.message %><br/> <%= e.backtrace.join(",") %> -->
|
7
|
+
<% end %>
|
8
|
+
<% end %>
|
9
|
+
<% else %>
|
10
|
+
<%= div_for area do %>
|
11
|
+
<% area.pieces.by_newsletter(@newsletter).each do |piece| %>
|
12
|
+
<%= div_for piece, {:onMouseOver => "\$('#modify_piece_#{piece.id}').show()",
|
13
|
+
:onMouseOut => "\$('#modify_piece_#{piece.id}').hide()",
|
14
|
+
:style => "position: relative;"} do %>
|
15
|
+
<!--Element Template - <%= piece.element.view_path %> -->
|
16
|
+
<%= content_tag :div, link_to("Delete", piece, :confirm => 'Are you sure?',
|
17
|
+
:method => :delete, :class=> "delete_link") +
|
18
|
+
" | " +
|
19
|
+
link_to("Edit", edit_piece_path(piece), :target => '_top', :class=> "edit_link"),
|
20
|
+
:id=>"modify_piece_#{piece.id}",:style => "display:none", :class => "piece_tool" %>
|
21
|
+
<% begin %>
|
22
|
+
<%= render :inline => piece.element.html_text, :locals => piece.locals %>
|
23
|
+
<% rescue => e %>
|
24
|
+
Error rendering piece: <%= piece.element.html_text %>/ <%= piece.id %> with data <%=h piece.locals.inspect %> and template: <%=h piece.element.html_text %> <br/> <%= e.message %><br/> <%= e.backtrace.join(",") %>
|
25
|
+
<% end %>
|
26
|
+
<% end %>
|
27
|
+
<% end %>
|
28
|
+
<script>
|
29
|
+
jQuery(document).ready(function() {
|
30
|
+
jQuery("#area_<%= area.id %>").sortable({
|
31
|
+
axis: 'y',
|
32
|
+
stop: function (event, ui) {
|
33
|
+
var data = $(this).sortable('serialize');
|
34
|
+
|
35
|
+
// POST to server using $.post or $.ajax
|
36
|
+
$.ajax({
|
37
|
+
data: data,
|
38
|
+
type: 'POST',
|
39
|
+
url: '<%= sort_area_path(newsletter_id: @newsletter.id, id: area.id) %>'
|
40
|
+
});
|
41
|
+
}
|
42
|
+
});
|
43
|
+
});
|
44
|
+
</script>
|
45
|
+
|
46
|
+
<!--%= sortable_element("area_#{area.id}", :tag=>:div,
|
47
|
+
:constraint => :vertical, :url => sort_area_path(:id=>area.id,:newsletter_id=>@newsletter.id)) %-->
|
48
|
+
<% end %>
|
49
|
+
<% end %>
|
@@ -0,0 +1,41 @@
|
|
1
|
+
<%= nested_form_for @design do |f| %>
|
2
|
+
<%= f.error_messages %>
|
3
|
+
<table cellspacing='1' cellpadding='0' border='0'>
|
4
|
+
<tr>
|
5
|
+
<td width='150'><%= f.label :name %></td>
|
6
|
+
<td><%= f.text_field :name %></td>
|
7
|
+
</tr>
|
8
|
+
<tr>
|
9
|
+
<td width='150'><%= f.label :description %></td>
|
10
|
+
<td><%= f.text_field :description %></td>
|
11
|
+
</tr>
|
12
|
+
<% unless @design.name.blank? %>
|
13
|
+
<tr>
|
14
|
+
<td width='150'><label>Design Filename</label></td>
|
15
|
+
<td><%= @design.view_path.gsub(/^#{::Newsletter.designs_path}/,'') %></td>
|
16
|
+
</tr>
|
17
|
+
<% end %>
|
18
|
+
<tr valign='top'>
|
19
|
+
<td width='150'><%= f.label :html_text %></td>
|
20
|
+
<td><%= f.text_area :html_text %></td>
|
21
|
+
</tr>
|
22
|
+
<tr valign='top'>
|
23
|
+
<td colspan='2'>
|
24
|
+
<div class='newsletter_parts_group'>
|
25
|
+
<h2 style='padding-bottom: 0; margin-bottom: 0; font-size: 10pt; '>Areas</h2>
|
26
|
+
<div id="areas">
|
27
|
+
<%= f.fields_for :areas %>
|
28
|
+
</div>
|
29
|
+
<%= f.link_to_add('Add Area', :areas, {class: 'button'}) %><div class='linkbuttonEnding'></div>
|
30
|
+
</div>
|
31
|
+
</td>
|
32
|
+
</tr>
|
33
|
+
<tr>
|
34
|
+
<td align="right" colspan="2"><%= f.submit "Submit", :class => 'button' %><div class="buttonEnding"></div></td>
|
35
|
+
</tr>
|
36
|
+
</table>
|
37
|
+
|
38
|
+
<% end %>
|
39
|
+
<% content_for :head do %>
|
40
|
+
<%= javascript_include_tag '/javascripts/nested_form.js' %>
|
41
|
+
<% end %>
|
@@ -0,0 +1,17 @@
|
|
1
|
+
<% div_for area do %>
|
2
|
+
<% fields_for 'design[area_attributes][]', area do |naf| %>
|
3
|
+
<%= naf.text_field :name, :index => nil %>
|
4
|
+
<% if area.new_record? %>
|
5
|
+
<%= link_to_function "Remove", "$(this).up('.area').remove()", :class => 'button' %><div class='linkbuttonEnding' style='margin-top: 7px;'></div><br/>
|
6
|
+
<% else %>
|
7
|
+
<%= link_to_function "Remove", "$(this).next('.should_destroy').value = 1; $(this).hide();"+
|
8
|
+
"$('area_restore_#{area.id}').show()", :class => 'button',
|
9
|
+
:id => "area_remove_#{area.id}" %>
|
10
|
+
<%= link_to_function "Restore", "$(this).next('.should_destroy').value = 0; $(this).hide();"+
|
11
|
+
"$('area_remove_#{area.id}').show()", :class => 'button',
|
12
|
+
:id => "area_restore_#{area.id}", :style => 'display:none' %><div class='linkbuttonEnding' style='margin-top: 5px;'></div><br/>
|
13
|
+
<%= naf.hidden_field :id, :index => nil %>
|
14
|
+
<%= naf.hidden_field :should_destroy, :index => nil, :class => 'should_destroy' %>
|
15
|
+
<% end %>
|
16
|
+
<% end %>
|
17
|
+
<% end %>
|