honey-cms 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/cms.rb +13 -0
- data/lib/cms/attribute.rb +86 -0
- data/lib/cms/configuration.rb +40 -0
- data/lib/cms/controller_stub.rb +3 -0
- data/lib/cms/engine.rb +5 -0
- data/lib/cms/form_builder.rb +276 -0
- data/lib/cms/helper.rb +65 -0
- data/lib/cms/orderable.rb +24 -0
- data/lib/cms/routes.rb +14 -0
- data/lib/cms/type.rb +44 -0
- data/lib/cms/uploader.rb +7 -0
- data/lib/cms/view_tags.rb +25 -0
- data/lib/generators/cms/USAGE +8 -0
- data/lib/generators/cms/cms_generator.rb +44 -0
- data/lib/generators/cms/templates/cms_base_controller.rb +78 -0
- data/lib/generators/cms/templates/cms_model.rb +3 -0
- data/lib/generators/cms/templates/migration.rb +19 -0
- data/lib/generators/cms/templates/type_controller.rb +9 -0
- data/lib/generators/cms/templates/type_model.rb +23 -0
- data/lib/generators/cms/templates/views/edit.html.haml +19 -0
- data/lib/generators/cms/templates/views/index.html.haml +34 -0
- data/lib/generators/cms/templates/views/new.html.haml +19 -0
- data/lib/generators/cms/templates/views/show.html.haml +13 -0
- data/lib/honey-cms.rb +2 -0
- metadata +68 -0
data/lib/cms.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
module CMS
|
2
|
+
autoload :Routes, 'cms/routes'
|
3
|
+
autoload :Configuration, 'cms/configuration'
|
4
|
+
autoload :Type, 'cms/type'
|
5
|
+
autoload :Attribute, 'cms/attribute'
|
6
|
+
autoload :Helper, 'cms/helper'
|
7
|
+
autoload :Orderable, 'cms/orderable'
|
8
|
+
autoload :Uploader, 'cms/Uploader'
|
9
|
+
autoload :ViewTags, 'cms/view_tags'
|
10
|
+
autoload :FormBuilder, 'cms/form_builder'
|
11
|
+
end
|
12
|
+
|
13
|
+
Cms = CMS
|
@@ -0,0 +1,86 @@
|
|
1
|
+
class CMS::Attribute
|
2
|
+
attr_accessor :name, :format, :options
|
3
|
+
|
4
|
+
def initialize name, format, options
|
5
|
+
@name = name
|
6
|
+
@format = format
|
7
|
+
@options = options
|
8
|
+
@attr_options = {
|
9
|
+
# null: false
|
10
|
+
}
|
11
|
+
end
|
12
|
+
|
13
|
+
def format
|
14
|
+
@format_enquirer ||= ActiveSupport::StringInquirer.new(@format)
|
15
|
+
end
|
16
|
+
|
17
|
+
def reference? ; format.reference? end
|
18
|
+
def orderable? ; format.orderable? end
|
19
|
+
def file? ; format.file? end
|
20
|
+
|
21
|
+
def has_index?
|
22
|
+
false
|
23
|
+
end
|
24
|
+
|
25
|
+
def has_uniq_index?
|
26
|
+
false
|
27
|
+
end
|
28
|
+
|
29
|
+
def index_name
|
30
|
+
format.reference? ? "#{name}_id" : name
|
31
|
+
end
|
32
|
+
|
33
|
+
def migration_type
|
34
|
+
if format.reference?
|
35
|
+
'belongs_to'
|
36
|
+
elsif format.orderable?
|
37
|
+
'integer'
|
38
|
+
elsif format.file?
|
39
|
+
'string'
|
40
|
+
elsif format.html?
|
41
|
+
'text'
|
42
|
+
else
|
43
|
+
format
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def form_type
|
48
|
+
if format.orderable? || format.reference?
|
49
|
+
'select'
|
50
|
+
elsif format.text? or format.html?
|
51
|
+
'text_area'
|
52
|
+
elsif format.file?
|
53
|
+
'file_field'
|
54
|
+
elsif format.boolean?
|
55
|
+
'check_box'
|
56
|
+
else
|
57
|
+
'text_field'
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def field_name
|
62
|
+
if reference?
|
63
|
+
name + '_id'
|
64
|
+
else
|
65
|
+
name
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def inject_options
|
70
|
+
"".tap { |s| @attr_options.each { |k,v| s << ", :#{k} => #{v.inspect}" } }
|
71
|
+
end
|
72
|
+
|
73
|
+
def inject_index_options
|
74
|
+
has_uniq_index? ? ", :unique => true" : ''
|
75
|
+
end
|
76
|
+
|
77
|
+
def reference_to
|
78
|
+
if reference?
|
79
|
+
@reference_to ||= CMS::Configuration.types.find{|t| t.name == @options['reference_to']}
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def to_s
|
84
|
+
name
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module CMS::Configuration
|
2
|
+
def data
|
3
|
+
@data = YAML.load(File.read(Rails.root.join 'config/cms.yml'))
|
4
|
+
end
|
5
|
+
|
6
|
+
def scoped_types options
|
7
|
+
if options[:only]
|
8
|
+
[types.find{|t| options[:only] == t.name}]
|
9
|
+
else
|
10
|
+
types.reject{|t| options[:except].include?(t.name)}
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def types
|
15
|
+
if defined?(@types) then return @types end
|
16
|
+
|
17
|
+
@types = data['content_types'].map do |name, config|
|
18
|
+
CMS::Type.new(name, config.delete('attributes'), config)
|
19
|
+
end
|
20
|
+
|
21
|
+
@types.each do |type|
|
22
|
+
type.attributes = attributes(type.attributes, type)
|
23
|
+
end
|
24
|
+
|
25
|
+
@types
|
26
|
+
end
|
27
|
+
|
28
|
+
def attributes attributes, type
|
29
|
+
attributes.map do |args|
|
30
|
+
options = args.extract_options!
|
31
|
+
name = args.shift
|
32
|
+
format = args.pop || options.delete('format')
|
33
|
+
attribute = CMS::Attribute.new(name, format, options)
|
34
|
+
attribute.reference_to.references << type if attribute.reference?
|
35
|
+
attribute
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
extend self
|
40
|
+
end
|
data/lib/cms/engine.rb
ADDED
@@ -0,0 +1,276 @@
|
|
1
|
+
class CMS::FormBuilder < ActionView::Helpers::FormBuilder ; end
|
2
|
+
|
3
|
+
ActionView::Base.field_error_proc = Proc.new do |html, instance|
|
4
|
+
if html =~ /<label/
|
5
|
+
html
|
6
|
+
else
|
7
|
+
message = instance.error_message.map{|m| "#{instance.method_name.humanize} #{m}"}.join(', ')
|
8
|
+
"#{html}<div class=\"help-inline\">#{message}</div>".html_safe
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module CMS::FormBuilder::Fields
|
13
|
+
def radio name, *args
|
14
|
+
args = _apply_field_defaults(args)
|
15
|
+
options = args.extract_options!
|
16
|
+
values = args
|
17
|
+
field_wrapper :radio, name do
|
18
|
+
out = ''.html_safe
|
19
|
+
out.concat label(name) if options[:label]
|
20
|
+
|
21
|
+
value_div = @template.content_tag(:span, class: 'values') do
|
22
|
+
values.map do |value|
|
23
|
+
@template.content_tag :span, class: 'value' do
|
24
|
+
if value.is_a? Array
|
25
|
+
radio_button(name, value[1]) + label(name, value[0], value: value[1])
|
26
|
+
else
|
27
|
+
radio_button(name, value) + label(name, value, value: value)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end.join("\n").html_safe
|
31
|
+
end
|
32
|
+
out.concat value_div
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def location name, options
|
37
|
+
autocomplete name, options.merge(prepend: @template.content_tag(:div, '', class: 'geolocate'))
|
38
|
+
end
|
39
|
+
|
40
|
+
def autocomplete name, *args
|
41
|
+
args = _apply_field_defaults(args)
|
42
|
+
options = args.extract_options!
|
43
|
+
options.reverse_merge!(waiting: 'Searching...')
|
44
|
+
|
45
|
+
option_text = '<div class="options"><div class="inner">'.html_safe
|
46
|
+
option_text.concat @template.content_tag(:div, options[:waiting], class: 'waiting')
|
47
|
+
option_text.concat '<div class="results"></div>'.html_safe
|
48
|
+
option_text.concat '</div></div>'.html_safe
|
49
|
+
|
50
|
+
field_wrapper(:autocomplete, name) do
|
51
|
+
out = ''.html_safe
|
52
|
+
if options[:label] == true
|
53
|
+
out.concat label(name)
|
54
|
+
elsif options[:label]
|
55
|
+
out.concat label(name, options[:label])
|
56
|
+
end
|
57
|
+
out.concat @template.link_to 'learn more', '#', class: 'tipsy', title: send("#{name}_help_text") if options[:help_text]
|
58
|
+
options[:help_text] = false
|
59
|
+
out.concat string(name, options.merge(autocomplete: false, label: false, append: option_text))
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def text_with_counter name, *args
|
64
|
+
args = _apply_field_defaults(args)
|
65
|
+
options = args.extract_options!
|
66
|
+
|
67
|
+
unless options[:length].blank?
|
68
|
+
counter = "<div class=\"counter\"><span class=\"count\">0</span>/<span class=\"maximum\">#{options[:length]}</span></div>".html_safe
|
69
|
+
end
|
70
|
+
|
71
|
+
text(name, options.merge(label: options[:label], append: counter))
|
72
|
+
end
|
73
|
+
|
74
|
+
def boolean *args
|
75
|
+
field :boolean, *_apply_default_options(args, label_first: false)
|
76
|
+
end
|
77
|
+
|
78
|
+
def string *args
|
79
|
+
field :string, *args
|
80
|
+
end
|
81
|
+
|
82
|
+
def search *args
|
83
|
+
field :search, *args
|
84
|
+
end
|
85
|
+
|
86
|
+
def text *args
|
87
|
+
field :text, *args
|
88
|
+
end
|
89
|
+
|
90
|
+
def email *args
|
91
|
+
field :email, *args
|
92
|
+
end
|
93
|
+
|
94
|
+
def password *args
|
95
|
+
field :password, *args
|
96
|
+
end
|
97
|
+
|
98
|
+
def hidden *args
|
99
|
+
field :hidden, *_apply_default_options(args, label: false, wrap_field: false)
|
100
|
+
end
|
101
|
+
|
102
|
+
# slider is weird enough that we will not use the default field helper.
|
103
|
+
# instead, we will construct our own field that looks like a regular field.
|
104
|
+
def slider name, *args
|
105
|
+
args = _apply_field_defaults(args)
|
106
|
+
options = args.extract_options!
|
107
|
+
out = ''.html_safe
|
108
|
+
|
109
|
+
field_wrapper :slider, name do
|
110
|
+
out.concat label(name, options[:label]) if options[:label]
|
111
|
+
out.concat @template.content_tag :div, '', class: 'slider-edit'
|
112
|
+
out.concat hidden(name)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def toggle name, *args
|
117
|
+
args = _apply_field_defaults(args)
|
118
|
+
options = args.extract_options!
|
119
|
+
out = ''.html_safe
|
120
|
+
|
121
|
+
field_wrapper :toggle, name, :'data-default' => options[:default] do
|
122
|
+
out.concat label(name) if options[:label]
|
123
|
+
out.concat(@template.content_tag(:div, class: (object.send(name) ? 'visible' : 'hidden')) do
|
124
|
+
check_box(name) << @template.label_tag(object.send(name) ? 'visible' : 'hidden')
|
125
|
+
end)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def actions options = {}
|
130
|
+
options.reverse_merge! save: 'Save', saving: 'Saving...', class: 'form-actions'
|
131
|
+
@template.content_tag(:div, class: options.delete(:class)) do
|
132
|
+
actions = ''.html_safe
|
133
|
+
actions << submit(options[:save], disable_with: options[:saving], class: 'btn btn-primary')
|
134
|
+
actions << status
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def modal_actions options = {}
|
139
|
+
options.reverse_merge! save: 'Save', saving: 'Saving...', class: 'modal-footer'
|
140
|
+
@template.content_tag(:div, class: options.delete(:class)) do
|
141
|
+
actions = ''.html_safe
|
142
|
+
actions << @template.link_to('Close', '#close', class: 'btn', data: {dismiss: 'modal'})
|
143
|
+
actions << submit(options[:save], disable_with: options[:saving], class: 'btn btn-primary')
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def status options = {}
|
148
|
+
options.reverse_merge! success: 'Saved!', error: 'Failed!'
|
149
|
+
out = @template.content_tag(:div, class: 'status') do
|
150
|
+
status = ''.html_safe
|
151
|
+
status << @template.content_tag(:div, '', class: 'spinner')
|
152
|
+
# status << @template.content_tag(:div, options[:success], class: 'success')
|
153
|
+
# status << @template.content_tag(:div, options[:error], class: 'error')
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def field_wrapper type, name, options = {}
|
158
|
+
classes = "field #{type} #{name.to_s.dasherize} control-group"
|
159
|
+
classes << options[:classes] if options[:classes]
|
160
|
+
classes << ' error' if object.errors.include? name
|
161
|
+
options.merge! class: classes
|
162
|
+
@template.content_tag :div, options do
|
163
|
+
yield
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
# def _input_options options
|
168
|
+
# [:autocomplete, :placeholder]
|
169
|
+
# end
|
170
|
+
|
171
|
+
def field *args, &block
|
172
|
+
type, name, options = _extract_field_args(args)
|
173
|
+
out = ''.html_safe
|
174
|
+
|
175
|
+
input_options = {}
|
176
|
+
|
177
|
+
unless options[:autocomplete].nil?
|
178
|
+
options.delete(:autocomplete)
|
179
|
+
input_options[:autocomplete] = 'off'
|
180
|
+
end
|
181
|
+
|
182
|
+
unless options[:placeholder].nil?
|
183
|
+
input_options[:placeholder] = options.delete(:placeholder)
|
184
|
+
end
|
185
|
+
|
186
|
+
unless options[:hidden].nil?
|
187
|
+
input_options[:class] = 'hidden' if options[:hidden] == true
|
188
|
+
end
|
189
|
+
|
190
|
+
unless options[:required].nil?
|
191
|
+
input_options[:required] = 'required' if options[:required] == true
|
192
|
+
end
|
193
|
+
|
194
|
+
out.concat options[:prepend] if options[:prepend]
|
195
|
+
|
196
|
+
|
197
|
+
label_html = label(name, options[:label], class: 'control-label')
|
198
|
+
|
199
|
+
out.concat label_html if options[:label] && options[:label_first]
|
200
|
+
|
201
|
+
if options[:help_text]
|
202
|
+
help_text = send("#{name}_help_text")
|
203
|
+
help_html = "<a class=\"tipsy\" title=\"#{help_text}\" href=\"#\">learn more</a>".html_safe
|
204
|
+
out.concat help_html
|
205
|
+
end
|
206
|
+
|
207
|
+
out.concat(@template.content_tag(:div, class: 'controls') do
|
208
|
+
controls = send(_field_types(type), name, input_options)
|
209
|
+
controls.concat @template.content_tag(:div, options[:help_block], class: 'help-block') if options[:help_block].present?
|
210
|
+
controls
|
211
|
+
end)
|
212
|
+
|
213
|
+
out.concat label_html if options[:label] && !options[:label_first]
|
214
|
+
|
215
|
+
out.concat options[:append] if options[:append]
|
216
|
+
out.concat yield if block_given?
|
217
|
+
|
218
|
+
if options[:wrap_field]
|
219
|
+
field_wrapper(type, name) { out }
|
220
|
+
else
|
221
|
+
out
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
# simple helper method for extracting and applying default options.
|
226
|
+
def _apply_default_options args, defaults
|
227
|
+
options = args.extract_options!
|
228
|
+
args << options.reverse_merge!(defaults)
|
229
|
+
end
|
230
|
+
|
231
|
+
# apply the default options for all fields.
|
232
|
+
def _apply_field_defaults args
|
233
|
+
_apply_default_options args, label: true, wrap_field: true, label_first: true
|
234
|
+
end
|
235
|
+
|
236
|
+
# single use method for parsing options provided by the +field+ helper
|
237
|
+
def _extract_field_args args
|
238
|
+
args = _apply_field_defaults(args)
|
239
|
+
options = args.extract_options!
|
240
|
+
name = args.pop
|
241
|
+
type = args.pop
|
242
|
+
options[:label] = name.to_s.humanize if options[:label].is_a? TrueClass
|
243
|
+
[type, name, options]
|
244
|
+
end
|
245
|
+
|
246
|
+
def _field_types(type)
|
247
|
+
case type
|
248
|
+
when :string, :location
|
249
|
+
:text_field
|
250
|
+
when :search
|
251
|
+
:search_field
|
252
|
+
when :text
|
253
|
+
:text_area
|
254
|
+
when :email
|
255
|
+
:email_field
|
256
|
+
when :password
|
257
|
+
:password_field
|
258
|
+
when :hidden, :slider
|
259
|
+
:hidden_field
|
260
|
+
when :boolean, :radio
|
261
|
+
:radio_button
|
262
|
+
when :boolean, :check
|
263
|
+
:check_box
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
def error_messages
|
268
|
+
if object.errors.any?
|
269
|
+
@template.render partial: 'shared/form/error_messages', object: object.errors
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
class CMS::FormBuilder
|
275
|
+
include CMS::FormBuilder::Fields
|
276
|
+
end
|
data/lib/cms/helper.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
module CMS::Helper
|
2
|
+
def cms_file name, size = false
|
3
|
+
if file = CMS::FileUpload.find_by_name(name) then return file end
|
4
|
+
opts = {name: name}
|
5
|
+
opts[:description] = (size.sub('x', ' by ') << ' pixels')
|
6
|
+
CMS::FileUpload.create(opts, without_protection: true)
|
7
|
+
end
|
8
|
+
|
9
|
+
def cms_image name, size = false, width = '', height = ''
|
10
|
+
width, height = size.split('x') if size
|
11
|
+
image = cms_file(name, size)
|
12
|
+
|
13
|
+
if image.file?
|
14
|
+
image_tag image.file.url, class: 'cms-image', style: "width: #{width}px ; height: #{height}px"
|
15
|
+
else
|
16
|
+
if current_user && current_user.role.admin?
|
17
|
+
link_to(name, edit_cms_file_upload_path(image), class: 'cms-image missing-cms-image', style: "width: #{width}px ; height: #{height}px ")
|
18
|
+
else
|
19
|
+
content_tag :div, name, class: 'cms-image missing-cms-image', style: "width: #{width}px ; height: #{height}px"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def cms_page_area name, &block
|
25
|
+
page_area = if area = CMS::PageArea.find_by_name(name) then area else CMS::PageArea.new({name: name}, without_protection: true) end
|
26
|
+
|
27
|
+
content_tag :div, role: 'html-editor' do
|
28
|
+
out = content_tag(:div, class: 'cms-page-area', role: 'display') do
|
29
|
+
if page_area.content.present? && !page_area.default
|
30
|
+
content = page_area.content
|
31
|
+
elsif block_given?
|
32
|
+
content = capture(&block)
|
33
|
+
page_area.content = ''.concat(content)
|
34
|
+
page_area.save!
|
35
|
+
end
|
36
|
+
|
37
|
+
display = ''.html_safe
|
38
|
+
display << cms_page_area_edit_link if admin?
|
39
|
+
display << content_tag(:div, class: 'content') do
|
40
|
+
cms_content_parse(content) if content.present?
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
out << content_tag(:div, role: 'editor') do
|
45
|
+
form_for([:cms, page_area], format: 'json', remote: true) do |f|
|
46
|
+
form = f.hidden_field(:name)
|
47
|
+
form << f.hidden_field(:content, class: 'content')
|
48
|
+
form << f.actions(save: 'done')
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
rescue Exception => e
|
53
|
+
if Rails.env.production? then '' else raise e end
|
54
|
+
end
|
55
|
+
|
56
|
+
def cms_page_area_edit_link
|
57
|
+
content_tag :div, class: 'cms-edit-link' do
|
58
|
+
link_to('Edit', '#edit')
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def cms_content_parse content
|
63
|
+
CMS::ViewTags.instance.parse content, context: self
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module CMS::Orderable
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
def order_scope
|
5
|
+
self.class
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
def orderable name
|
10
|
+
default_scope order(name)
|
11
|
+
after_save :"order_#{name}"
|
12
|
+
|
13
|
+
define_method :"order_#{name}" do
|
14
|
+
order_scope.where("#{name} >= #{send(name)}").where("id != #{id}").select(:id).select(name).inject(send(name)) do |i, record|
|
15
|
+
record.update_column name, (i += 1) ; i
|
16
|
+
end
|
17
|
+
|
18
|
+
order_scope.all.inject(1) do |i, record|
|
19
|
+
record.update_column name, i ; i + 1
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/cms/routes.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
class CMS::Routes < SimpleDelegator
|
2
|
+
def draw
|
3
|
+
namespace :cms do
|
4
|
+
get 'description' => 'root#description'
|
5
|
+
get '' => 'root#index'
|
6
|
+
|
7
|
+
CMS::Configuration.types.each do |type|
|
8
|
+
resources type.model_name.route_key
|
9
|
+
end
|
10
|
+
|
11
|
+
resources :topic_datas, :community_maps, :downtowns
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/cms/type.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
class CMS::Type
|
2
|
+
attr_accessor :name, :attributes, :options, :references
|
3
|
+
include ActiveModel::Naming # using include instead of extend on purpose
|
4
|
+
def parents ; [] end # quacks like a Module
|
5
|
+
|
6
|
+
def initialize name, attributes, options = {}
|
7
|
+
@name = name
|
8
|
+
@attributes = attributes
|
9
|
+
@options = options.symbolize_keys.merge(timestamps: true, indexes: true)
|
10
|
+
@references = []
|
11
|
+
end
|
12
|
+
|
13
|
+
def accessible_attributes
|
14
|
+
attributes
|
15
|
+
end
|
16
|
+
|
17
|
+
def attributes_with_index
|
18
|
+
attributes.select { |a| a.has_index? || (a.reference? && options[:indexes]) }
|
19
|
+
end
|
20
|
+
|
21
|
+
def orderable_attributes
|
22
|
+
attributes.select(&:orderable?)
|
23
|
+
end
|
24
|
+
|
25
|
+
def file_attributes
|
26
|
+
attributes.select(&:file?)
|
27
|
+
end
|
28
|
+
|
29
|
+
def orderable?
|
30
|
+
!!order_attribute
|
31
|
+
end
|
32
|
+
|
33
|
+
def order_attribute
|
34
|
+
orderable_attributes.first
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_s
|
38
|
+
name
|
39
|
+
end
|
40
|
+
|
41
|
+
def subject
|
42
|
+
options[:subject] || name
|
43
|
+
end
|
44
|
+
end
|
data/lib/cms/uploader.rb
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
class CMS::Uploader < CarrierWave::Uploader::Base
|
2
|
+
def store_dir
|
3
|
+
base = File.join('cms-uploads', model.class.model_name.collection.gsub('_', '-'), model.id.try(:to_s))
|
4
|
+
base = File.join('uploads', base) if storage.is_a? CarrierWave::Storage::File
|
5
|
+
base
|
6
|
+
end
|
7
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class CMS::ViewTags
|
2
|
+
attr_accessor :context, :controller
|
3
|
+
delegate :current_user, :config, to: :context
|
4
|
+
|
5
|
+
include Singleton
|
6
|
+
include ApplicationHelper
|
7
|
+
include ActionView::Helpers::TextHelper
|
8
|
+
include ActionView::Helpers::UrlHelper
|
9
|
+
include ActionView::Helpers::AssetTagHelper
|
10
|
+
include ApplicationHelper
|
11
|
+
include CMS::Helper
|
12
|
+
include Rails.application.routes.url_helpers
|
13
|
+
|
14
|
+
def setup opts
|
15
|
+
@context = @controller = opts[:context]
|
16
|
+
end
|
17
|
+
|
18
|
+
def parse content, opts = {}
|
19
|
+
setup(opts)
|
20
|
+
content.gsub(/\{\{image [^}}]+\}\}/) do |tag|
|
21
|
+
str, name, size = tag.match(/\{\{image (\w+) (\w+)\}\}/).to_a
|
22
|
+
cms_image(name, size)
|
23
|
+
end.html_safe
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'rails/generators/active_record/migration'
|
2
|
+
|
3
|
+
class CmsGenerator < Rails::Generators::Base
|
4
|
+
source_root File.expand_path('../templates', __FILE__)
|
5
|
+
include Rails::Generators::Migration
|
6
|
+
extend ActiveRecord::Generators::Migration
|
7
|
+
|
8
|
+
class_option :except, type: :array, default: [],
|
9
|
+
desc: 'skip certain types.'
|
10
|
+
|
11
|
+
class_option :only, type: :string, default: false,
|
12
|
+
desc: 'run a specific type generator.'
|
13
|
+
|
14
|
+
class_option :migrate, type: :boolean, default: false,
|
15
|
+
desc: 'generate the migration'
|
16
|
+
|
17
|
+
class_option :controller, type: :boolean, default: false,
|
18
|
+
desc: 'generate the controller'
|
19
|
+
|
20
|
+
def create_migration_file
|
21
|
+
if options[:only].present?
|
22
|
+
migration_template 'migration.rb', "db/migrate/create_#{options[:only].underscore.pluralize}" if options[:migrate]
|
23
|
+
else
|
24
|
+
migration_template 'migration.rb', 'db/migrate/create_cms' if options[:migrate]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def copy_controller_file
|
29
|
+
template 'cms_base_controller.rb', 'app/controllers/cms/base_controller.rb' if options[:controller]
|
30
|
+
|
31
|
+
empty_directory 'app/controllers/cms'
|
32
|
+
empty_directory 'app/models/cms'
|
33
|
+
|
34
|
+
CMS::Configuration.scoped_types(options).each do |type|
|
35
|
+
@name = (@type = type).model_name
|
36
|
+
template 'type_controller.rb', "app/controllers/cms/#{@name.collection}_controller.rb"
|
37
|
+
template 'type_model.rb', "app/models/cms/#{@name.element}.rb"
|
38
|
+
|
39
|
+
%w(index new show edit).each do |view|
|
40
|
+
template "views/#{view}.html.haml", "app/views/cms/#{@name.collection}/#{view}.html.haml"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
class CMS::BaseController < ApplicationController
|
2
|
+
before_filter :admin!
|
3
|
+
before_filter :set_active
|
4
|
+
layout 'cms'
|
5
|
+
|
6
|
+
before_filter :find_record, only: [:show, :edit, :update, :destroy]
|
7
|
+
before_filter :set_element_variable, only: [:show, :edit, :update, :destroy]
|
8
|
+
respond_to :html, :json
|
9
|
+
|
10
|
+
def index
|
11
|
+
@records = if params[:search].blank?
|
12
|
+
subject.order('id asc') .page(params[:page]).per(100)
|
13
|
+
else
|
14
|
+
subject.search(params[:search]).page(params[:page]).per(100)
|
15
|
+
end
|
16
|
+
|
17
|
+
set_collection_variable
|
18
|
+
respond_with(@records)
|
19
|
+
end
|
20
|
+
|
21
|
+
def new
|
22
|
+
@record = subject.new
|
23
|
+
set_element_variable
|
24
|
+
respond_with(@record)
|
25
|
+
end
|
26
|
+
|
27
|
+
def create
|
28
|
+
@record = subject.create(params[subject.model_name.element])
|
29
|
+
set_element_variable
|
30
|
+
respond_with @record
|
31
|
+
end
|
32
|
+
|
33
|
+
def show
|
34
|
+
respond_with @record
|
35
|
+
end
|
36
|
+
|
37
|
+
def edit
|
38
|
+
respond_with @record
|
39
|
+
end
|
40
|
+
|
41
|
+
def update
|
42
|
+
@record.update_attributes params[subject.model_name.element]
|
43
|
+
respond_with @record
|
44
|
+
end
|
45
|
+
|
46
|
+
def destroy
|
47
|
+
@record.destroy
|
48
|
+
respond_with @record
|
49
|
+
end
|
50
|
+
|
51
|
+
protected
|
52
|
+
|
53
|
+
def respond_with object
|
54
|
+
super [:cms, object]
|
55
|
+
end
|
56
|
+
|
57
|
+
def find_record
|
58
|
+
@record = subject.find(params[:id])
|
59
|
+
end
|
60
|
+
|
61
|
+
def set_collection_variable
|
62
|
+
instance_variable_set :"@#{subject.model_name.collection}", @records
|
63
|
+
end
|
64
|
+
|
65
|
+
def set_element_variable
|
66
|
+
instance_variable_set :"@#{subject.model_name.element}", @record
|
67
|
+
end
|
68
|
+
|
69
|
+
def admin!
|
70
|
+
unless signed_in? && current_user.role.admin?
|
71
|
+
auth_failed
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def set_active
|
76
|
+
@active_page = /cms/
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class <%= migration_class_name %> < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
<% CMS::Configuration.scoped_types(options).each do |type| -%>
|
4
|
+
create_table :<%= type.model_name.collection %> do |t|
|
5
|
+
<% type.attributes.each do |attribute| -%>
|
6
|
+
t.<%= attribute.migration_type %> :<%= attribute.name %><%= attribute.inject_options %>
|
7
|
+
<% end -%>
|
8
|
+
<% if type.options[:timestamps] -%>
|
9
|
+
t.timestamps
|
10
|
+
<% end -%>
|
11
|
+
end
|
12
|
+
|
13
|
+
<% type.attributes_with_index.each do |attribute| -%>
|
14
|
+
add_index :<%= type.model_name.collection %>, :<%= attribute.index_name %><%= attribute.inject_index_options %>
|
15
|
+
<% end -%>
|
16
|
+
<% end -%>
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class CMS::<%= @name %> < ActiveRecord::Base
|
2
|
+
self.table_name = '<%= @name.collection %>'
|
3
|
+
<% @type.attributes.select {|attr| attr.reference? }.each do |attribute| -%>
|
4
|
+
belongs_to :<%= attribute.name %>, class_name: 'CMS::<%= attribute.reference_to %>'
|
5
|
+
<% end -%>
|
6
|
+
<% if @type.references.any? -%>
|
7
|
+
<% @type.references.each do |type| -%>
|
8
|
+
has_many :<%= type.model_name.collection %>, class_name: 'CMS::<%= type %>'
|
9
|
+
<% end -%>
|
10
|
+
<% end -%>
|
11
|
+
attr_accessible <%= @type.accessible_attributes.map {|a| ":#{a.field_name}" }.sort.join(', ') %>
|
12
|
+
<% if @type.orderable? -%>
|
13
|
+
include CMS::Orderable
|
14
|
+
orderable(:<%= @type.order_attribute.name %>)
|
15
|
+
<% end -%>
|
16
|
+
<% @type.file_attributes.each do |attribute| -%>
|
17
|
+
mount_uploader :<%= attribute.name %>, CMS::Uploader
|
18
|
+
<% end -%>
|
19
|
+
|
20
|
+
def self.name
|
21
|
+
'<%= @name %>'
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
%h2 Edit <%= @name.human.downcase %>
|
2
|
+
|
3
|
+
= form_for @<%= @name.element %>, url: cms_<%= @name.element %>_path(@<%= @name.element %>), html: {class: 'form-horizontal'} do |f|
|
4
|
+
<% @type.attributes.each do |attribute| -%>
|
5
|
+
.control-group
|
6
|
+
= f.label :<%= attribute.field_name %>, class: 'control-label'
|
7
|
+
<% if attribute.orderable? -%>
|
8
|
+
.controls
|
9
|
+
= f.<%= attribute.form_type %> :<%= attribute.name %>, 1.upto(CMS::<%= @name %>.count).to_a
|
10
|
+
<% elsif attribute.reference? -%>
|
11
|
+
.controls
|
12
|
+
= f.<%= attribute.form_type %> :<%= attribute.field_name %>, CMS::<%= attribute.reference_to %>.all.collect {|r| [ r.<%= attribute.options['reference_label'] %>, r.id ] }
|
13
|
+
<% else -%>
|
14
|
+
.controls
|
15
|
+
= f.<%= attribute.form_type %> :<%= attribute.name %>
|
16
|
+
<% end -%>
|
17
|
+
<% end -%>
|
18
|
+
.form-actions
|
19
|
+
= f.submit 'Save', class: 'btn btn-primary', :'data-disable-with' => 'Saving...'
|
@@ -0,0 +1,34 @@
|
|
1
|
+
%h2 <%= @name.human.pluralize %>
|
2
|
+
|
3
|
+
= link_to 'New <%= @name.human.downcase %>', new_cms_<%= @name.element %>_path
|
4
|
+
|
5
|
+
%table.table
|
6
|
+
%thead
|
7
|
+
%tr
|
8
|
+
%th Id
|
9
|
+
<% @type.attributes.each do |attribute| -%>
|
10
|
+
%th <%= attribute.name.humanize %>
|
11
|
+
<% end -%>
|
12
|
+
%th
|
13
|
+
|
14
|
+
%tbody
|
15
|
+
- @<%= @name.collection %>.each do |<%= @name.element %>|
|
16
|
+
%tr
|
17
|
+
%td= <%= @name.element %>.id
|
18
|
+
<% @type.attributes.each do |attribute| -%>
|
19
|
+
<% if attribute.reference? -%>
|
20
|
+
%td
|
21
|
+
- if <%= @name.element %>.<%= attribute.name %>.present?
|
22
|
+
= link_to <%= @name.element %>.<%= attribute.name %>.<%= attribute.options['reference_label'] %>, cms_<%= attribute.reference_to.model_name.element %>_path(<%= @name.element %>.<%= attribute.name %>)
|
23
|
+
<% elsif attribute.format.file? -%>
|
24
|
+
%td
|
25
|
+
%img{src: <%= @name.element %>.<%= attribute.name %>}
|
26
|
+
<% else -%>
|
27
|
+
%td= <%= @name.element %>.<%= attribute.name %>
|
28
|
+
<% end -%>
|
29
|
+
<% end -%>
|
30
|
+
%td
|
31
|
+
= link_to 'Edit', edit_cms_<%= @name.element %>_path(<%= @name.element %>)
|
32
|
+
= link_to 'Destroy', cms_<%= @name.element %>_path(<%= @name.element %>), confirm: 'are you sure?', method: 'delete'
|
33
|
+
|
34
|
+
= paginate @<%= @name.collection %>
|
@@ -0,0 +1,19 @@
|
|
1
|
+
%h2 New <%= @name.human.downcase %>
|
2
|
+
|
3
|
+
= form_for @<%= @name.element %>, url: cms_<%= @name.collection %>_path, html: {class: 'form-horizontal'} do |f|
|
4
|
+
<% @type.attributes.each do |attribute| -%>
|
5
|
+
.control-group
|
6
|
+
= f.label :<%= attribute.field_name %>, class: 'control-label'
|
7
|
+
<% if attribute.orderable? -%>
|
8
|
+
.controls
|
9
|
+
= f.<%= attribute.form_type %> :<%= attribute.name %>, 1.upto(CMS::<%= @name %>.count + 1).to_a
|
10
|
+
<% elsif attribute.reference? -%>
|
11
|
+
.controls
|
12
|
+
= f.<%= attribute.form_type %> :<%= attribute.field_name %>, CMS::<%= attribute.reference_to %>.all.collect {|r| [ r.<%= attribute.options['reference_label'] %>, r.id ] }
|
13
|
+
<% else -%>
|
14
|
+
.controls
|
15
|
+
= f.<%= attribute.form_type %> :<%= attribute.name %>
|
16
|
+
<% end -%>
|
17
|
+
<% end -%>
|
18
|
+
.form-actions
|
19
|
+
= f.submit 'Save', class: 'btn btn-primary', :'data-disable-with' => 'Saving...'
|
@@ -0,0 +1,13 @@
|
|
1
|
+
%h2== Showing <%= @name.human.downcase %> ##{@<%= @name.element %>.id}
|
2
|
+
|
3
|
+
%dl
|
4
|
+
<% @type.attributes.each do |attribute| -%>
|
5
|
+
%dt <%= attribute.name.humanize %>
|
6
|
+
<% if attribute.reference? -%>
|
7
|
+
%dd= @<%= @name.element %>.<%= attribute.name %>.try(:<%= attribute.options['reference_label'] %>)
|
8
|
+
<% elsif attribute.format.text? -%>
|
9
|
+
%dd= markdown @<%= @name.element %>.<%= attribute.name %>
|
10
|
+
<% else -%>
|
11
|
+
%dd= @<%= @name.element %>.<%= attribute.name %>
|
12
|
+
<% end -%>
|
13
|
+
<% end -%>
|
data/lib/honey-cms.rb
ADDED
metadata
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: honey-cms
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Quinn Shanahan
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-10-16 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: Some CMS functionality
|
15
|
+
email: quinn@tastehoneyco.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- lib/cms/attribute.rb
|
21
|
+
- lib/cms/configuration.rb
|
22
|
+
- lib/cms/controller_stub.rb
|
23
|
+
- lib/cms/engine.rb
|
24
|
+
- lib/cms/form_builder.rb
|
25
|
+
- lib/cms/helper.rb
|
26
|
+
- lib/cms/orderable.rb
|
27
|
+
- lib/cms/routes.rb
|
28
|
+
- lib/cms/type.rb
|
29
|
+
- lib/cms/uploader.rb
|
30
|
+
- lib/cms/view_tags.rb
|
31
|
+
- lib/cms.rb
|
32
|
+
- lib/generators/cms/cms_generator.rb
|
33
|
+
- lib/generators/cms/templates/cms_base_controller.rb
|
34
|
+
- lib/generators/cms/templates/cms_model.rb
|
35
|
+
- lib/generators/cms/templates/migration.rb
|
36
|
+
- lib/generators/cms/templates/type_controller.rb
|
37
|
+
- lib/generators/cms/templates/type_model.rb
|
38
|
+
- lib/generators/cms/templates/views/edit.html.haml
|
39
|
+
- lib/generators/cms/templates/views/index.html.haml
|
40
|
+
- lib/generators/cms/templates/views/new.html.haml
|
41
|
+
- lib/generators/cms/templates/views/show.html.haml
|
42
|
+
- lib/generators/cms/USAGE
|
43
|
+
- lib/honey-cms.rb
|
44
|
+
homepage: https://github.com/honeyco/honey-cms
|
45
|
+
licenses: []
|
46
|
+
post_install_message:
|
47
|
+
rdoc_options: []
|
48
|
+
require_paths:
|
49
|
+
- lib
|
50
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ! '>='
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
requirements: []
|
63
|
+
rubyforge_project:
|
64
|
+
rubygems_version: 1.8.11
|
65
|
+
signing_key:
|
66
|
+
specification_version: 3
|
67
|
+
summary: CMS
|
68
|
+
test_files: []
|