tandem 0.2.0.rc
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/README.rdoc +205 -0
- data/Rakefile +31 -0
- data/app/assets/images/tandem/blank_image.jpg +0 -0
- data/app/assets/images/tandem/colorbox/border1.png +0 -0
- data/app/assets/images/tandem/colorbox/border2.png +0 -0
- data/app/assets/images/tandem/colorbox/ie6/borderBottomCenter.png +0 -0
- data/app/assets/images/tandem/colorbox/ie6/borderBottomLeft.png +0 -0
- data/app/assets/images/tandem/colorbox/ie6/borderBottomRight.png +0 -0
- data/app/assets/images/tandem/colorbox/ie6/borderMiddleLeft.png +0 -0
- data/app/assets/images/tandem/colorbox/ie6/borderMiddleRight.png +0 -0
- data/app/assets/images/tandem/colorbox/ie6/borderTopCenter.png +0 -0
- data/app/assets/images/tandem/colorbox/ie6/borderTopLeft.png +0 -0
- data/app/assets/images/tandem/colorbox/ie6/borderTopRight.png +0 -0
- data/app/assets/images/tandem/colorbox/loading.gif +0 -0
- data/app/assets/images/tandem/ic-delete_image.png +0 -0
- data/app/assets/images/tandem/ic-star.png +0 -0
- data/app/assets/images/tandem/loader.gif +0 -0
- data/app/assets/images/tandem/loader_stripe.png +0 -0
- data/app/assets/images/tandem/tandem-editImage2.jpg +0 -0
- data/app/assets/images/tandem/tandem_logo.png +0 -0
- data/app/assets/images/tandem/tandem_logo_nav.png +0 -0
- data/app/assets/javascripts/tandem/contents.js +2 -0
- data/app/assets/javascripts/tandem/current_image.jst.eco +19 -0
- data/app/assets/javascripts/tandem/gallery_image.jst.eco +10 -0
- data/app/assets/javascripts/tandem/images.coffee +42 -0
- data/app/assets/javascripts/tandem/pages.js.coffee +32 -0
- data/app/assets/javascripts/tandem/popup.js +23 -0
- data/app/assets/javascripts/tandem.js +16 -0
- data/app/assets/stylesheets/tandem/colorbox.css.erb +83 -0
- data/app/assets/stylesheets/tandem/contents.scss +541 -0
- data/app/assets/stylesheets/tandem/scaffold.scss +61 -0
- data/app/assets/stylesheets/tandem/variables.scss +38 -0
- data/app/assets/stylesheets/tandem.css +7 -0
- data/app/controllers/tandem/application_controller.rb +19 -0
- data/app/controllers/tandem/contents_controller.rb +94 -0
- data/app/controllers/tandem/images_controller.rb +92 -0
- data/app/controllers/tandem/pages_controller.rb +108 -0
- data/app/helpers/tandem/application_helper.rb +29 -0
- data/app/helpers/tandem/contents_helper.rb +19 -0
- data/app/helpers/tandem/images_helper.rb +10 -0
- data/app/helpers/tandem/pages_helper.rb +243 -0
- data/app/models/tandem/ability.rb +6 -0
- data/app/models/tandem/content/image.rb +7 -0
- data/app/models/tandem/content/text.rb +7 -0
- data/app/models/tandem/content.rb +41 -0
- data/app/models/tandem/image.rb +27 -0
- data/app/models/tandem/page.rb +41 -0
- data/app/views/layouts/tandem/image.html.slim +19 -0
- data/app/views/layouts/tandem/popup.html.slim +17 -0
- data/app/views/tandem/contents/_form.html.slim +36 -0
- data/app/views/tandem/contents/edit.html.slim +3 -0
- data/app/views/tandem/contents/index.html.slim +29 -0
- data/app/views/tandem/contents/new.html.slim +3 -0
- data/app/views/tandem/contents/show.html.slim +20 -0
- data/app/views/tandem/images/_form.html.slim +24 -0
- data/app/views/tandem/images/_gallery.html.slim +4 -0
- data/app/views/tandem/images/edit.html.slim +8 -0
- data/app/views/tandem/images/index.html.slim +9 -0
- data/app/views/tandem/images/new.html.slim +4 -0
- data/app/views/tandem/images/show.html.slim +10 -0
- data/app/views/tandem/images/thumb.html.slim +1 -0
- data/app/views/tandem/pages/_form.html.slim +56 -0
- data/app/views/tandem/pages/edit.html.slim +1 -0
- data/app/views/tandem/pages/index.html.slim +35 -0
- data/app/views/tandem/pages/new.html.slim +1 -0
- data/app/views/tandem/pages/show.html.slim +16 -0
- data/config/cucumber.yml +8 -0
- data/config/routes.rb +17 -0
- data/db/migrate/20111122221549_create_tandem_pages.rb +20 -0
- data/db/migrate/20111122222037_create_tandem_contents.rb +21 -0
- data/db/migrate/20111215001943_create_tandem_images.rb +12 -0
- data/db/migrate/30000000000000_create_default_page.rb +10 -0
- data/db/migrate/30000000000001_add_request_key_to_tandem_contents.rb +44 -0
- data/lib/generators/tandem_generator.rb +26 -0
- data/lib/generators/templates/initializer.rb +70 -0
- data/lib/tandem/engine.rb +23 -0
- data/lib/tandem/version.rb +3 -0
- data/lib/tandem.rb +46 -0
- data/lib/tasks/cucumber.rake +65 -0
- data/lib/tasks/tandem_tasks.rake +4 -0
- 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="{"identifier":"test_text","type":"text"}">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,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,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
|