tandem 0.2.0.rc

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 (82) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.rdoc +205 -0
  3. data/Rakefile +31 -0
  4. data/app/assets/images/tandem/blank_image.jpg +0 -0
  5. data/app/assets/images/tandem/colorbox/border1.png +0 -0
  6. data/app/assets/images/tandem/colorbox/border2.png +0 -0
  7. data/app/assets/images/tandem/colorbox/ie6/borderBottomCenter.png +0 -0
  8. data/app/assets/images/tandem/colorbox/ie6/borderBottomLeft.png +0 -0
  9. data/app/assets/images/tandem/colorbox/ie6/borderBottomRight.png +0 -0
  10. data/app/assets/images/tandem/colorbox/ie6/borderMiddleLeft.png +0 -0
  11. data/app/assets/images/tandem/colorbox/ie6/borderMiddleRight.png +0 -0
  12. data/app/assets/images/tandem/colorbox/ie6/borderTopCenter.png +0 -0
  13. data/app/assets/images/tandem/colorbox/ie6/borderTopLeft.png +0 -0
  14. data/app/assets/images/tandem/colorbox/ie6/borderTopRight.png +0 -0
  15. data/app/assets/images/tandem/colorbox/loading.gif +0 -0
  16. data/app/assets/images/tandem/ic-delete_image.png +0 -0
  17. data/app/assets/images/tandem/ic-star.png +0 -0
  18. data/app/assets/images/tandem/loader.gif +0 -0
  19. data/app/assets/images/tandem/loader_stripe.png +0 -0
  20. data/app/assets/images/tandem/tandem-editImage2.jpg +0 -0
  21. data/app/assets/images/tandem/tandem_logo.png +0 -0
  22. data/app/assets/images/tandem/tandem_logo_nav.png +0 -0
  23. data/app/assets/javascripts/tandem/contents.js +2 -0
  24. data/app/assets/javascripts/tandem/current_image.jst.eco +19 -0
  25. data/app/assets/javascripts/tandem/gallery_image.jst.eco +10 -0
  26. data/app/assets/javascripts/tandem/images.coffee +42 -0
  27. data/app/assets/javascripts/tandem/pages.js.coffee +32 -0
  28. data/app/assets/javascripts/tandem/popup.js +23 -0
  29. data/app/assets/javascripts/tandem.js +16 -0
  30. data/app/assets/stylesheets/tandem/colorbox.css.erb +83 -0
  31. data/app/assets/stylesheets/tandem/contents.scss +541 -0
  32. data/app/assets/stylesheets/tandem/scaffold.scss +61 -0
  33. data/app/assets/stylesheets/tandem/variables.scss +38 -0
  34. data/app/assets/stylesheets/tandem.css +7 -0
  35. data/app/controllers/tandem/application_controller.rb +19 -0
  36. data/app/controllers/tandem/contents_controller.rb +94 -0
  37. data/app/controllers/tandem/images_controller.rb +92 -0
  38. data/app/controllers/tandem/pages_controller.rb +108 -0
  39. data/app/helpers/tandem/application_helper.rb +29 -0
  40. data/app/helpers/tandem/contents_helper.rb +19 -0
  41. data/app/helpers/tandem/images_helper.rb +10 -0
  42. data/app/helpers/tandem/pages_helper.rb +243 -0
  43. data/app/models/tandem/ability.rb +6 -0
  44. data/app/models/tandem/content/image.rb +7 -0
  45. data/app/models/tandem/content/text.rb +7 -0
  46. data/app/models/tandem/content.rb +41 -0
  47. data/app/models/tandem/image.rb +27 -0
  48. data/app/models/tandem/page.rb +41 -0
  49. data/app/views/layouts/tandem/image.html.slim +19 -0
  50. data/app/views/layouts/tandem/popup.html.slim +17 -0
  51. data/app/views/tandem/contents/_form.html.slim +36 -0
  52. data/app/views/tandem/contents/edit.html.slim +3 -0
  53. data/app/views/tandem/contents/index.html.slim +29 -0
  54. data/app/views/tandem/contents/new.html.slim +3 -0
  55. data/app/views/tandem/contents/show.html.slim +20 -0
  56. data/app/views/tandem/images/_form.html.slim +24 -0
  57. data/app/views/tandem/images/_gallery.html.slim +4 -0
  58. data/app/views/tandem/images/edit.html.slim +8 -0
  59. data/app/views/tandem/images/index.html.slim +9 -0
  60. data/app/views/tandem/images/new.html.slim +4 -0
  61. data/app/views/tandem/images/show.html.slim +10 -0
  62. data/app/views/tandem/images/thumb.html.slim +1 -0
  63. data/app/views/tandem/pages/_form.html.slim +56 -0
  64. data/app/views/tandem/pages/edit.html.slim +1 -0
  65. data/app/views/tandem/pages/index.html.slim +35 -0
  66. data/app/views/tandem/pages/new.html.slim +1 -0
  67. data/app/views/tandem/pages/show.html.slim +16 -0
  68. data/config/cucumber.yml +8 -0
  69. data/config/routes.rb +17 -0
  70. data/db/migrate/20111122221549_create_tandem_pages.rb +20 -0
  71. data/db/migrate/20111122222037_create_tandem_contents.rb +21 -0
  72. data/db/migrate/20111215001943_create_tandem_images.rb +12 -0
  73. data/db/migrate/30000000000000_create_default_page.rb +10 -0
  74. data/db/migrate/30000000000001_add_request_key_to_tandem_contents.rb +44 -0
  75. data/lib/generators/tandem_generator.rb +26 -0
  76. data/lib/generators/templates/initializer.rb +70 -0
  77. data/lib/tandem/engine.rb +23 -0
  78. data/lib/tandem/version.rb +3 -0
  79. data/lib/tandem.rb +46 -0
  80. data/lib/tasks/cucumber.rake +65 -0
  81. data/lib/tasks/tandem_tasks.rake +4 -0
  82. metadata +354 -0
@@ -0,0 +1,94 @@
1
+ module Tandem
2
+ class ContentsController < ::Tandem::ApplicationController
3
+ load_and_authorize_resource
4
+ layout 'tandem/popup'
5
+
6
+ =begin ### default scaffold actions not currently used
7
+ # GET /contents
8
+ # GET /contents.json
9
+ def index
10
+ @contents = @page.contents.all
11
+ authorize_content!
12
+
13
+ respond_to do |format|
14
+ format.html # index.html.erb
15
+ format.json { render json: @contents }
16
+ end
17
+ end
18
+
19
+ # GET /contents/1
20
+ # GET /contents/1.json
21
+ def show
22
+ @content = @page.contents.find(params[:id])
23
+ authorize_content!
24
+
25
+ respond_to do |format|
26
+ format.html # show.html.erb
27
+ format.json { render json: @content }
28
+ end
29
+ end
30
+
31
+ # GET /contents/new
32
+ # GET /contents/new.json
33
+ def new
34
+ @content = @page.contents.new
35
+ authorize_content!
36
+
37
+ respond_to do |format|
38
+ format.html # new.html.erb
39
+ format.json { render json: @content }
40
+ end
41
+ end
42
+
43
+ # POST /contents
44
+ # POST /contents.json
45
+ def create
46
+ @content = Content::Text.new(params[:content].merge(:page_id => @page.id))
47
+ authorize_content!
48
+
49
+ respond_to do |format|
50
+ if @content.save
51
+ format.html { render action: "success", notice: 'Content was successfully created.' }
52
+ format.json { render json: @content, status: :created, location: @content }
53
+ else
54
+ format.html { render action: "new" }
55
+ format.json { render json: @content.errors, status: :unprocessable_entity }
56
+ end
57
+ end
58
+ end
59
+
60
+ # DELETE /contents/1
61
+ # DELETE /contents/1.json
62
+ def destroy
63
+ @content = @page.contents.find(params[:id])
64
+ authorize_content!
65
+
66
+ @content.destroy
67
+
68
+ respond_to do |format|
69
+ format.html { redirect_to page_contents_url }
70
+ format.json { head :ok }
71
+ end
72
+ end
73
+
74
+ =end
75
+
76
+ # GET /contents/1/edit
77
+ def edit
78
+ end
79
+
80
+ # PUT /contents/1
81
+ # PUT /contents/1.json
82
+ def update
83
+ param_key = ActiveModel::Naming.param_key(@content)
84
+
85
+ respond_to do |format|
86
+ if @content.update_attributes(params[param_key])
87
+ format.json { render json: @content }
88
+ else
89
+ format.json { render json: @content.errors, status: :unprocessable_entity }
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,92 @@
1
+ module Tandem
2
+ class ImagesController < ::Tandem::ApplicationController
3
+ load_and_authorize_resource
4
+ skip_load_and_authorize_resource :only => :thumb
5
+ skip_authorization_check :only => :thumb
6
+ layout 'tandem/image'
7
+
8
+ # GET /images
9
+ # GET /images.json
10
+ def index
11
+ respond_to do |format|
12
+ format.html # index.html.erb
13
+ format.json { render json: @images }
14
+ end
15
+ end
16
+
17
+ # GET /images/1
18
+ # GET /images/1.json
19
+ def show
20
+ respond_to do |format|
21
+ format.html #show.html.erb
22
+ format.json { render json: @image }
23
+ end
24
+ end
25
+
26
+ # GET /images/1/thumb
27
+ # GET /images/1/thumb.json
28
+ #this method is deliberately forgiving to image id's that don't exist
29
+ def thumb
30
+ @image = Tandem::Image.find_by_id(params[:id])
31
+ @content = Tandem::Content::Image.new(image: @image)
32
+ respond_to do |format|
33
+ format.html { render layout: nil }# thumb.html.erb
34
+ end
35
+ end
36
+
37
+ # GET /images/new
38
+ # GET /images/new.json
39
+ def new
40
+ @update_gallery = params['update_gallery'].present?
41
+ respond_to do |format|
42
+ format.html # new.html.erb
43
+ format.json { render json: @image }
44
+ end
45
+ end
46
+
47
+ # GET /images/1/edit
48
+ def edit
49
+ end
50
+
51
+ # POST /images
52
+ # POST /images.json
53
+ def create
54
+ respond_to do |format|
55
+ if @image.save
56
+ format.html { redirect_to new_image_path(update_gallery: true), notice: 'Image was successfully created.' }
57
+ format.json { render json: @image, status: :created, location: @image }
58
+ else
59
+ format.html { render action: "new" }
60
+ format.json { render json: @image.errors, status: :unprocessable_entity }
61
+ end
62
+ end
63
+ end
64
+
65
+ # PUT /images/1
66
+ # PUT /images/1.json
67
+ def update
68
+ respond_to do |format|
69
+ if @image.update_attributes(params[:image])
70
+ format.html { redirect_to @image, notice: 'Image was successfully updated.' }
71
+ format.json { head :ok }
72
+ else
73
+ format.html { render action: "edit" }
74
+ format.json { render json: @image.errors, status: :unprocessable_entity }
75
+ end
76
+ end
77
+ end
78
+
79
+ # DELETE /images/1
80
+ # DELETE /images/1.json
81
+ def destroy
82
+ @image.destroy
83
+
84
+ respond_to do |format|
85
+ format.html { redirect_to images_url }
86
+ format.json { head :ok }
87
+ end
88
+ end
89
+
90
+ private
91
+ end
92
+ end
@@ -0,0 +1,108 @@
1
+ module Tandem
2
+ class PagesController < ::Tandem::ApplicationController
3
+ load_and_authorize_resource :except => :home, :find_by => :slug
4
+ layout :pages_layout
5
+
6
+ # GET /pages/home
7
+ # GET /pages.home.json
8
+ def home
9
+ @page = Page.where(is_default: true).first || Page.first || create_default_page
10
+ authorize!(:show, Page)
11
+ respond_to do |format|
12
+ format.html { render (@page.template.present? ? @page.template : 'show'), notice: @page.new_record? ? 'No Pages Found.' : '' }
13
+ format.json { render json: @page }
14
+ end
15
+ end
16
+
17
+ # GET /pages
18
+ # GET /pages.json
19
+ def index
20
+ respond_to do |format|
21
+ format.html # index.html.erb
22
+ format.json { render json: @pages }
23
+ end
24
+ end
25
+
26
+ # GET /pages/1
27
+ # GET /pages/1.json
28
+ def show
29
+ respond_to do |format|
30
+ format.html { render @page.template if @page.template.present? }
31
+ format.json { render json: @page }
32
+ end
33
+ end
34
+
35
+ # GET /pages/new
36
+ # GET /pages/new.json
37
+ def new
38
+ @page.parent ||= Page.where(id: params['parent_id']).first
39
+ respond_to do |format|
40
+ format.html { render layout: false if request.xhr? }
41
+ format.json { render json: @page }
42
+ end
43
+ end
44
+
45
+ # GET /pages/1/edit
46
+ def edit
47
+ respond_to do |format|
48
+ format.html { render layout: false if request.xhr? }
49
+ end
50
+ end
51
+
52
+ # POST /pages
53
+ # POST /pages.json
54
+ def create
55
+ respond_to do |format|
56
+ if @page.save
57
+ format.html { redirect_to @page, notice: 'Page was successfully created.' }
58
+ format.json { render json: @page, status: :created, location: @page }
59
+ else
60
+ format.html { render action: "new" }
61
+ format.json { render json: @page.errors, status: :unprocessable_entity }
62
+ end
63
+ end
64
+ end
65
+
66
+ # PUT /pages/1
67
+ # PUT /pages/1.json
68
+ def update
69
+ respond_to do |format|
70
+ if @page.update_attributes(params[:page])
71
+ format.html { redirect_to @page, notice: 'Page was successfully updated.' }
72
+ format.json { head :no_content }
73
+ else
74
+ format.html { render action: "edit" }
75
+ format.json { render json: @page.errors, status: :unprocessable_entity }
76
+ end
77
+ end
78
+ end
79
+
80
+ # DELETE /pages/1
81
+ # DELETE /pages/1.json
82
+ def destroy
83
+ @page.destroy
84
+
85
+ respond_to do |format|
86
+ format.html { redirect_to pages_url }
87
+ format.json { head :ok }
88
+ end
89
+ end
90
+
91
+ private
92
+
93
+ def pages_layout
94
+ case params[:action]
95
+ when 'show', 'home'
96
+ @page.layout.present? ? @page.layout : 'application'
97
+ when 'new', 'create', 'edit', 'update'
98
+ 'tandem/popup'
99
+ else
100
+ 'application'
101
+ end
102
+ end
103
+
104
+ def create_default_page
105
+ Page.create!(link_label: 'Sample Page', slug: 'sample', is_default: true)
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,29 @@
1
+ module Tandem
2
+ module ApplicationHelper
3
+ def self.included(base)
4
+ main_app_url_helpers.each do |helper|
5
+ base.send(:define_method, helper) do |*arguments|
6
+ arguments = nil if arguments.empty?
7
+ main_app.send(helper, arguments)
8
+ end
9
+ end
10
+ end
11
+
12
+
13
+ def render_eco_template(template, locals = {})
14
+ @eco_templates ||= {}
15
+
16
+ if @eco_templates[template].nil?
17
+ template_file = File.read("#{Tandem::Engine.config.root}/app/assets/javascripts/tandem/#{template}.jst.eco")
18
+ @eco_templates[template] = Eco.context_for(template_file)
19
+ end
20
+
21
+ @eco_templates[template].call("render", locals).html_safe
22
+ end
23
+
24
+ private
25
+ def self.main_app_url_helpers
26
+ ::Rails.application.routes.url_helpers.methods.select{|method| method.match(/^(?!hash\_for).*_(path|url)$/)}
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,19 @@
1
+ module Tandem
2
+ module ContentsHelper
3
+
4
+ def image_content_url(image_content, format = :original)
5
+ image_content.image.present? ? image_content.image.resource.url(format) : 'tandem/blank_image.jpg'
6
+ end
7
+
8
+ def image_content_tag(image_content, options = {}, format = :original)
9
+ options = {style: ""}.merge(options) if format == :thumb
10
+ image_tag image_content_url(image_content, format), options
11
+ end
12
+
13
+ def text_content_text_area_options(editor = 'plain', options = {})
14
+ options.tap do |options|
15
+ options[:class] = 'wymeditor' if editor == 'wysiwyg'
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,10 @@
1
+ module Tandem
2
+ module ImagesHelper
3
+
4
+ def tandem_image_tag(image, options = {}, format = nil)
5
+ options = {style: "height:#{Tandem::Image::THUMB_HEIGHT}px;width:#{Tandem::Image::THUMB_WIDTH}px;border:5px solid #ccc;"}.merge(options) if format == :thumb
6
+ image_tag image.resource.url(format), options
7
+ end
8
+
9
+ end
10
+ end
@@ -0,0 +1,243 @@
1
+ module Tandem
2
+ module PagesHelper
3
+
4
+ include ContentsHelper
5
+
6
+ def self.included(base)
7
+ Tandem::Content.subclasses.each do |klass|
8
+ # tandem_#{klass.simple_type}_tag is provided as an alias of tandem_content_tag,
9
+ # specifying the type of content through the method name. For example:
10
+ #
11
+ # <%= tandem_image_tag(:test_image) %>
12
+ #
13
+ # is equivalent to:
14
+ #
15
+ # <%= tandem_content_tag(:test_image, :image) %>
16
+ #
17
+ base.send(:define_method, "tandem_#{klass.simple_type}_tag") do |identifier, options = {}, html_options = {}|
18
+ tandem_content_tag identifier, klass.simple_type, options, html_options
19
+ end
20
+ end
21
+ end
22
+
23
+ # tandem_content_tag creates an HTML DIV element with id and class parameters that
24
+ # relate to the specified Tandem Content object and contains an internal content element.
25
+ # If a Tandem::Content record doesn't exist for the unique combination of:
26
+ # request_key, identifier and type, one will be created automatically. For example:
27
+ #
28
+ # <%= tandem_content_tag(:test_image, :image) %>
29
+ #
30
+ # would find_or_create a Tandem::Content::Image object and produce the following HTML:
31
+ #
32
+ # <div class="tandem_content" id="test_image">...<img id="tandem_content_image_test_image" class="tandem_content_image bar"...</div>
33
+ #
34
+ # If the user is authorized to update content then an edit link will be generated in
35
+ # conjunction with the content. For example:
36
+ #
37
+ # <%= tandem_content_tag(:test_text, :text) %>
38
+ #
39
+ # would produce the following HTML (assuming `can? :update, tandem_content` => true):
40
+ #
41
+ # <div class="tandem_content" id="test_text">
42
+ # <div class="tandem_toolbar" id="tandem_toolbar_test_text">
43
+ # <a href="#" id="tandem_edit_link_test_text" title="Edit test_text" class="tandem_edit_link" editor_options="{&quot;identifier&quot;:&quot;test_text&quot;,&quot;type&quot;:&quot;text&quot;}">Edit</a>
44
+ # </div>
45
+ # ....
46
+ # </div>
47
+ #
48
+ # If a user specifies link information for the corresponding Tandem::Content object,
49
+ # then an A HTML element will be generated surrounding the content. For example:
50
+ #
51
+ # <%= tandem_content_tag(:test_image, :image) %>
52
+ #
53
+ # produces (assuming that tandem_content.link? => true):
54
+ #
55
+ # <div class="tandem_content" id="test_image">
56
+ # ...
57
+ # <a id="tandem_content_link_test_image" class="tandem_content_link" ...
58
+ # <img id="tandem_content_image_test_image" class="tandem_content_image" ...
59
+ # </a>
60
+ # </div>
61
+ #
62
+ # tandem_content_tag accepts a hash of html_options, which will be converted to
63
+ # additional HTML attributes of the generated container div. If you specify a <tt>:class</tt> value, it will be combined
64
+ # with the default class name for your object. For example:
65
+ #
66
+ # <%= tandem_content_tag(:test_image, :image, {}, :class => "bar", :style => 'display:none') %>...
67
+ #
68
+ # produces:
69
+ #
70
+ # <div class="tandem_content bar" id="test_image" style = "display:none">...
71
+ #
72
+ # tandem_content_tag also accepts a hash of options, which will be converted to
73
+ # additional HTML attributes of the internal asset type. If you specify a <tt>:class</tt> value, it will be combined
74
+ # with the default class name for your object. For example:
75
+ #
76
+ # <%= tandem_content_tag(:test_image, :image, :class => "bar", :width => 80) %>...
77
+ #
78
+ # produces:
79
+ #
80
+ # ...<img id="tandem_content_image_test_image" class="tandem_content_image bar" width = "80"...
81
+ #
82
+ # Finally, text tandem_content_tags support an <tt>:editor</tt> option, which defaults to <tt>:plain</tt>, but
83
+ # can also be changed to <tt>:wysiwyg</tt> to enable a WYSIWYG editor, e.g.
84
+ #
85
+ # <%= tandem_content_tag(:main_body, :text, editor: :wysiwyg) %>
86
+ def tandem_content_tag(identifier, type, options = {}, html_options = {})
87
+ options[:editor] ||= :plain
88
+
89
+ using_tandem_abilities do
90
+ tandem_content = Content.scoped_type(type).constantize.find_or_create_by_request_key_and_tag(request_key, identifier)
91
+
92
+ content = case tandem_content
93
+ when Content::Text
94
+ content_tag(:div, tandem_content, options.merge(
95
+ id: "#{dom_class(tandem_content)}_#{identifier}",
96
+ class: "#{dom_class(tandem_content)} #{options[:class]}".strip
97
+ )) {
98
+ tandem_content.formatted_content.html_safe
99
+ }
100
+ when Content::Image
101
+ image_content_tag(tandem_content,options.merge(
102
+ id: "#{dom_class(tandem_content)}_#{identifier}",
103
+ class: "#{dom_class(tandem_content)} #{options[:class]}".strip
104
+ ))
105
+ else
106
+ raise "Unable to create #{tandem_content.class.name}: #{tandem_content.errors.inspect}" if tandem_content.new_record?
107
+ raise "Rendering behavior not defined for: #{tandem_content.class.name}"
108
+ end
109
+
110
+ content = link_to(content,tandem_content.link_url,{
111
+ id: "tandem_content_link_#{identifier}",
112
+ class: "tandem_content_link",
113
+ target: tandem_content.link_target
114
+ }) if tandem_content.link?
115
+
116
+ content = content_tag(:div, tandem_content, {
117
+ id: "tandem_toolbar_#{identifier}",
118
+ class: "tandem_toolbar #{options[:class]}".strip
119
+ }) {
120
+ link_to("Edit", tandem.edit_content_path(tandem_content.id, editor: options[:editor]),{
121
+ id: "tandem_edit_link_#{identifier}",
122
+ class: "tandem_edit_link #{options[:class]}".strip,
123
+ title: "editing #{identifier}"
124
+ })
125
+ } + content if can? :update, tandem_content
126
+
127
+ html_options.merge! id: identifier
128
+ html_options[:class] ||= ''
129
+ html_options[:class] << ' tandem_content' if can? :update, tandem_content
130
+ html_options[:class].strip!
131
+
132
+ content_tag(:div, tandem_content, html_options) { content }
133
+ end
134
+ end
135
+
136
+ #todo... document this
137
+ def tandem_navigation_tag(active_page, pages_collection = nil, html_options = {})
138
+ html_options, pages_collection = pages_collection, nil if pages_collection.is_a?(Hash)
139
+
140
+ html_options[:class] ||= 'nav'
141
+
142
+ page_groups = (pages_collection || Page.all).inject({}) do |groups, page|
143
+ if groups[page.parent_id.to_s]
144
+ groups[page.parent_id.to_s] << page
145
+ else
146
+ groups[page.parent_id.to_s] = [page]
147
+ end
148
+ groups
149
+ end
150
+
151
+ # generate must be in scope for the iterate proc declaration, but must be defined below iterate, so that iterate is recursively in scope
152
+ generate = nil
153
+
154
+ iterate = Proc.new do |parent_id|
155
+ #very important to delete the group from the collection, or it is possible users can create looping relationships
156
+ (page_groups.delete(parent_id.to_s) || {}).inject(''.html_safe) do |buffer, page|
157
+ buffer + generate.call(page)
158
+ end
159
+ end
160
+
161
+ generate = Proc.new do |page|
162
+ content_tag_for(:li, page, :tandem, class: "#{page == active_page ? 'active' : ''}") do
163
+ link_to(page.link_label, tandem.page_path(page), class: "#{page == active_page ? 'active' : ''}") +
164
+ content_tag(:ul) do
165
+ iterate.call(page.id)
166
+ end
167
+ end
168
+ end
169
+
170
+ content_tag(:ul, html_options) do
171
+ iterate.call(page_groups.keys.first)
172
+ end
173
+ end
174
+
175
+ def tandem_page_links(options ={})
176
+ return if cannot?(:create, @page) && cannot?(:update, @page) && cannot?(:destroy, @page)
177
+
178
+ options[:id] ||= 'tandem_page_links'
179
+
180
+ content_tag(:ul, options) do
181
+ links = []
182
+
183
+ if can?(:create, @page)
184
+ links << link_to('New Page', tandem.new_page_path(parent_id: @page.id), :class => :page_link, :id => :page_new_link)
185
+ end
186
+
187
+ if @page.persisted? && can?(:update, @page)
188
+ links << link_to('Edit Page', tandem.edit_page_path(@page), :class => :page_link, :id => :page_edit_link)
189
+ end
190
+
191
+ if @page.persisted? && can?(:destroy, @page)
192
+ links << link_to('Destroy Page', tandem.page_path(@page), :confirm => 'Are you sure?', :method => :delete)
193
+ end
194
+
195
+ if can?(:index, ::Tandem::Page)
196
+ links << link_to('Page Listing', tandem.pages_path)
197
+ end
198
+
199
+ links.collect! do |link|
200
+ content_tag(:li, link)
201
+ end
202
+
203
+ raw(links.join)
204
+ end
205
+ end
206
+
207
+ def valid_layouts
208
+ @valid_layouts ||= Dir["#{::Rails.root}/app/views/layouts/**/*.html*"].collect do |layout|
209
+ name = layout.match(/layouts\/([\w\-\/]*)((\.\w*){2})$/)[1]
210
+ name unless name == 'application'
211
+ end.compact
212
+ end
213
+
214
+ def valid_templates
215
+ @valid_templates ||= Dir["#{::Rails.root}/app/views/tandem/pages/**/*.*.*"].collect do |template|
216
+ template_name = File.basename(template).split('.').first
217
+ template_name if valid_custom_template?(template_name)
218
+ end.compact
219
+ end
220
+
221
+ private
222
+ def invalid_templates
223
+ ['show', 'edit', 'index', 'new']
224
+ end
225
+
226
+ def valid_custom_template?(template_name)
227
+ template_name[0] != '_' && !invalid_templates.include?(template_name)
228
+ end
229
+
230
+ def request_key
231
+ key = "#{controller_path}-#{action_name}".gsub(/[^\w]|_/, '-')
232
+ key += "-#{params[:id]}" if key == 'tandem-pages-show'
233
+ key
234
+ end
235
+
236
+ def using_tandem_abilities
237
+ controller.instance_variable_set :@current_ability, ::Tandem::Ability.new(::Tandem::Configuration.current_user.call(request))
238
+ yield.tap do
239
+ controller.instance_variable_set :@current_ability, nil
240
+ end
241
+ end
242
+ end
243
+ end
@@ -0,0 +1,6 @@
1
+ module Tandem
2
+ class Ability
3
+ include CanCan::Ability
4
+ define_method :initialize, Configuration.user_abilities
5
+ end
6
+ end
@@ -0,0 +1,7 @@
1
+ module Tandem
2
+ class Content::Image < Content
3
+ #todo: the following line introduces an artifact field on the other types of Content. For now there is only one other field type, so this down and dirty association is quickest.
4
+ #todo: I've already added a polymorphic field "attachment" that is designed for this same purpose, but is currently not being used for anything, it should be removed as well^^.
5
+ belongs_to :image, class_name: 'Tandem::Image'
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module Tandem
2
+ class Content::Text < Content
3
+ def formatted_content
4
+ content.presence || '<p>Sample Content</p>'
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,41 @@
1
+ module Tandem
2
+ class Content < ActiveRecord::Base
3
+ validates :tag, presence: true
4
+ validates :request_key, presence: true, uniqueness: {:scope => [:tag, :type]}
5
+
6
+
7
+ #enforce abstract class architecture
8
+ validates :type, presence: true, exclusion: ['Tandem::Content']
9
+
10
+ attr_accessible :tag, :content, :details, :link_url, :link_target, :attachment_id, :attachment_type, :image_id, :request_key
11
+
12
+ def self.subclass_names
13
+ @subclass_names ||= subclasses.map { |sc| sc.name }
14
+ end
15
+
16
+ def self.simple_types
17
+ @simple_types ||= subclasses.map { |sc| sc.simple_type }
18
+ end
19
+
20
+ def self.valid_simple_type?(type)
21
+ simple_types.include?(type.to_s.downcase.to_sym)
22
+ end
23
+
24
+ def self.simple_type
25
+ name =~ /^Tandem\:\:Content\:\:(.*)/
26
+ ($1 or raise "Unable to determine simple type for abstract class: #{name}").downcase.to_sym
27
+ end
28
+
29
+ def self.scoped_type(type)
30
+ valid_simple_type?(type) or raise "Invalid Tandem::Content.simple_type: '#{type}'. Valid options #{simple_types.join(', ')}"
31
+ "Tandem::Content::#{type.to_s.camelize}"
32
+ end
33
+
34
+ def link?
35
+ link_url.present?
36
+ end
37
+ end
38
+ end
39
+
40
+ load 'image.rb'
41
+ load 'text.rb'
@@ -0,0 +1,27 @@
1
+ module Tandem
2
+ class Image < ActiveRecord::Base
3
+
4
+ THUMB_WIDTH = 150
5
+ THUMB_HEIGHT = 150
6
+
7
+ has_attached_file :resource, Tandem::Configuration.paperclip_options
8
+
9
+ validates_attachment_presence :resource
10
+ validates_attachment_size :resource, :less_than => 1.megabyte
11
+ validates_attachment_content_type :resource, :content_type => ['image/gif', 'image/jpg', 'image/jpeg', 'image/png']
12
+
13
+ has_many :content_images, class_name: 'Tandem::Content::Image'
14
+
15
+ default_scope order('created_at DESC')
16
+
17
+ attr_accessible :resource
18
+
19
+ def thumb_url
20
+ resource.url(:thumb)
21
+ end
22
+
23
+ def as_json(options={})
24
+ super(:methods => :thumb_url)
25
+ end
26
+ end
27
+ end