papermill 0.14.3 → 0.16.0
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.
- data/.gitignore +2 -0
- data/README.rdoc +125 -79
- data/TODO.txt +0 -2
- data/VERSION +1 -1
- data/app/controllers/papermill_controller.rb +41 -40
- data/app/views/papermill/_asset.html.erb +1 -1
- data/app/views/papermill/_form.html.erb +20 -13
- data/app/views/papermill/edit.html.erb +2 -7
- data/config/locales/papermill.yml +6 -0
- data/config/routes.rb +2 -2
- data/generators/papermill_initializer/USAGE +4 -0
- data/generators/papermill_initializer/papermill_initializer_generator.rb +15 -0
- data/generators/papermill_table/templates/migrate/papermill_migration.rb.erb +8 -3
- data/installation-template.txt +4 -4
- data/lib/{core_extensions.rb → extensions.rb} +23 -15
- data/lib/papermill.rb +11 -4
- data/lib/papermill/form_builder.rb +62 -49
- data/lib/papermill/papermill.rb +70 -0
- data/lib/papermill/papermill_asset.rb +57 -15
- data/lib/papermill/papermill_options.rb +125 -0
- data/public/papermill/README +13 -0
- data/public/papermill/images/delete.png +0 -0
- data/public/papermill/images/mass-delete.png +0 -0
- data/public/papermill/images/mass-edit.png +0 -0
- data/public/papermill/papermill.css +8 -2
- data/public/papermill/papermill.js +42 -12
- data/test/fixtures/12k.png +0 -0
- data/test/fixtures/50x50.png +0 -0
- data/test/fixtures/5k.png +0 -0
- data/test/fixtures/bad.png +1 -0
- data/test/fixtures/s3.yml +8 -0
- data/test/fixtures/text.txt +0 -0
- data/test/fixtures/twopage.pdf +0 -0
- data/test/papermill_test.rb +109 -19
- metadata +19 -5
- data/lib/papermill/papermill_module.rb +0 -219
data/.gitignore
CHANGED
data/README.rdoc
CHANGED
@@ -1,125 +1,171 @@
|
|
1
1
|
= Papermill
|
2
2
|
|
3
|
-
Asset management made easy.
|
3
|
+
Asset management made easy. Now in pre-1.0.0 release!
|
4
4
|
|
5
|
-
== Install the
|
5
|
+
== Install the gems
|
6
6
|
|
7
|
-
$ gem source -a http://gemcutter.org
|
7
|
+
$ gem source -a http://gemcutter.org
|
8
8
|
$ sudo gem install papermill
|
9
9
|
|
10
10
|
== Try the demo
|
11
11
|
|
12
12
|
$ sudo gem install sqlite3-ruby
|
13
13
|
$ rails -m http://github.com/bbenezech/papermill/raw/master/installation-template.txt papermill-example
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
14
|
+
|
15
|
+
== Features
|
16
|
+
|
17
|
+
Loads of them
|
18
|
+
|
19
|
+
=== Ajax uploading form helpers through SWFUpload:
|
20
|
+
|
21
|
+
* image_upload => unique image upload field, with preview
|
22
|
+
* images_upload => sortable image gallery upload field
|
23
|
+
* asset_upload => simple one asset field
|
24
|
+
* assets_upload => sortable asset list field
|
25
|
+
|
26
|
+
=== Choose thumbnail size for images previews :
|
27
|
+
|
28
|
+
* {:thumbnail => {:width => 100, :height => 100}}
|
29
|
+
* {:thumbnail => {:style => "100x100>"}}
|
30
|
+
* {:thumbnail => {:width => 100, :aspect_ratio => 4.0/3.0 }}
|
31
|
+
|
32
|
+
=== Asset edit form:
|
33
|
+
|
34
|
+
* double-click on any asset in any helper to access&edit his properties
|
35
|
+
* with pop-up/shadowbox/facebox, out of the box (or use your own pop-up system, dead-easy)
|
36
|
+
|
37
|
+
=== Lazy created thumbnails
|
38
|
+
|
39
|
+
* thumbnails are generated the first time they are asked-for, and only in the requested size.
|
40
|
+
* no need to register thumbnail size anywhere: my_asset.url("100x100>")
|
41
|
+
|
42
|
+
=== Alias handling, declaration application-wide
|
43
|
+
|
44
|
+
* :big_alias => {:geometry => "1000x>"}
|
45
|
+
* :other_alias => "100x>"
|
46
|
+
* :third_alias => {:geometry => '100:122', :my_other_keys => 'blblabla'} # if you have a customed Paperclip::Thumbnail processor, you can pass any values you need.
|
47
|
+
* and use them when you need them : my_asset.url(:big_alias)
|
48
|
+
|
18
49
|
== Papermill comes in 2 flavors:
|
19
50
|
|
20
51
|
=== Generic catch-all declaration
|
21
52
|
|
22
|
-
papermill
|
23
|
-
|
24
|
-
f.input :my_key, :as => :assets_upload, my_option_hash # if you are using formtastic
|
25
|
-
@assetable.assets(:my_key) # data access in your view
|
53
|
+
papermill {options} # in your papermilled assetable model
|
54
|
+
@article.assets(:any_key, options_hash) # data access
|
26
55
|
|
27
56
|
=== Association specific declaration
|
28
57
|
|
29
|
-
papermill :my_association,
|
30
|
-
|
31
|
-
|
58
|
+
papermill :my_association, options_hash # in your papermilled assetable model
|
59
|
+
@article.my_association # data access
|
60
|
+
|
61
|
+
== Usage:
|
32
62
|
|
63
|
+
=== Model declaration
|
33
64
|
|
34
|
-
|
65
|
+
You can have a generic association and as many declarative associations as you want in your model. Papermill will always use specific if found.
|
66
|
+
|
67
|
+
article.rb
|
68
|
+
class Article < ActiveRecord::Base
|
69
|
+
papermill :class_name => ColorAsset, other_options..
|
70
|
+
end
|
71
|
+
|
72
|
+
entry.rb
|
73
|
+
class Entry < ActiveRecord::Base
|
74
|
+
papermill :mug_shot, other_options.. # default class_name is built-in PapermillAsset
|
75
|
+
papermill :diaporama, :class_name => ColorAsset, other_options..
|
76
|
+
end
|
35
77
|
|
36
|
-
You
|
78
|
+
color_asset.rb # You should add columns to papermill_assets and use STI on PapermillAsset to extend defaults capabilities (or re-open PapermillAsset and monkey-patch it..)
|
79
|
+
class ColorAsset < PapermillAsset
|
80
|
+
named_scope :red, :conditions => {:color => 'red'}
|
81
|
+
end
|
37
82
|
|
38
|
-
|
83
|
+
=== Form helpers
|
39
84
|
|
40
|
-
|
85
|
+
FormHelpers
|
86
|
+
form_for @article do
|
87
|
+
f.image_upload :cover_image, options_hash
|
88
|
+
f.images_upload :illustrations, options_hash
|
89
|
+
f.asset_upload :pdf, options_hash
|
90
|
+
f.image_upload :other_ressources, options_hash
|
91
|
+
end
|
92
|
+
|
93
|
+
Or with formtastic :
|
94
|
+
semantic_form_for @article do |f|
|
95
|
+
f.input @article, :cover_image, options_hash, :as => :image_upload
|
96
|
+
f.input @article, :illustrations, options_hash, :as => :images_upload
|
97
|
+
f.input @article, :pdf, options_hash, :as => :asset_upload
|
98
|
+
f.input @article, :other_ressources, options_hash, :as => :image_upload
|
99
|
+
end
|
41
100
|
|
101
|
+
FormTagHelpers
|
102
|
+
image_upload_tag @article, :cover_image, options_hash
|
103
|
+
images_upload_tag @article, :illustrations, options_hash
|
104
|
+
asset_upload_tag @article, :pdf, options_hash
|
105
|
+
image_upload_tag @article, :other_ressources, options_hash
|
106
|
+
# For resources not linked to any assetable model :
|
107
|
+
image_upload_tag #{current_organization.name}_logo
|
108
|
+
|
109
|
+
=== Resources access
|
110
|
+
|
111
|
+
With generic papermill association, Papermill generates an #assets(:key, *args) named_scope
|
112
|
+
@article.assets(:illustrations)
|
113
|
+
@article.assets(:illustrations, :order => "created_at DESC")
|
114
|
+
@article.illustrations.red.first
|
115
|
+
@article.assets(:illustrations, :order => "created_at DESC").red.first
|
116
|
+
# etc.
|
117
|
+
|
118
|
+
With declarative papermill associations, Papermill generates an #<association_key>(*args) named_scope
|
119
|
+
@entry.mug_shot.first
|
120
|
+
@entry.diaporama
|
121
|
+
@entry.diaporama(:order => "created_at DESC")
|
122
|
+
@entry.diaporama.red
|
123
|
+
# === @entry.diaporama(:conditions => {:color => "red"})
|
124
|
+
# etc.
|
125
|
+
|
126
|
+
Or for non-assetable resources :
|
127
|
+
PapermillAsset.all(:conditions => { :assetable_key => "#{current_organization.name}_logo" }).first
|
128
|
+
ColorAsset.all.red
|
129
|
+
|
130
|
+
=== Using PapermillAsset
|
131
|
+
|
132
|
+
@asset = @entry.mug_shot.first
|
133
|
+
image_tag @asset.url # original
|
134
|
+
image_tag @asset.url("100x>")
|
135
|
+
image_tag @asset.url(:big) # assuming you have a :big alias in your environment.rb
|
136
|
+
@asset.name
|
137
|
+
@asset.content_type
|
138
|
+
@asset.path # original
|
139
|
+
@asset.path("100x>")
|
140
|
+
# etc.
|
141
|
+
|
42
142
|
== Installation
|
43
143
|
|
44
|
-
=== Once
|
144
|
+
=== Once gem is installed :
|
45
145
|
|
46
|
-
# Generate
|
146
|
+
# Generate migration :
|
47
147
|
$ ./script/generate papermill_table PapermillMigration
|
48
148
|
$ rake db:migrate
|
49
|
-
# copy
|
149
|
+
# copy static assets to your public directory:
|
50
150
|
$ ./script/generate papermill_assets
|
151
|
+
# create the option hash in config/initializers/papermill.rb
|
152
|
+
$ ./script/generate papermill_initializer
|
51
153
|
|
52
154
|
=== Then in environment.rb:
|
53
155
|
|
54
156
|
...
|
55
|
-
|
56
157
|
Rails::Initializer.run do |config|
|
57
158
|
...
|
58
159
|
config.gem papermill
|
59
|
-
|
60
|
-
# You can set application-wide options inside or before Rails::Initializer :
|
61
|
-
module Papermill
|
62
|
-
OPTIONS = {
|
63
|
-
:thumbnail => {
|
64
|
-
:width => 150,
|
65
|
-
:height => 100
|
66
|
-
},
|
67
|
-
:aliases => {
|
68
|
-
:big => "500x500>",
|
69
|
-
:small => "100x100>"
|
70
|
-
},
|
71
|
-
:public_root => ":rails_root/public", # already a default
|
72
|
-
:papermill_prefix => "system/papermill" # already a default
|
73
|
-
}
|
74
|
-
end
|
75
|
-
# see lib/papermill/papermill_module.rb
|
76
|
-
|
77
|
-
# You can use stringex's String#to_url (papermill will use its own String#to_url if none exists)
|
78
|
-
config.gem 'stringex'
|
79
|
-
...
|
80
|
-
# You can use Mime-Type to get the correct mime type from upload - flash garbles it.. (default is a UNIX call to "file --mime")
|
81
|
-
# needed for Windows OS
|
160
|
+
# Needed for Windows OS (mime type from file extension):
|
82
161
|
config.gem "mime-types", :lib => "mime/types"
|
83
162
|
end
|
84
|
-
|
85
|
-
=== In your assetable model:
|
86
|
-
|
87
|
-
# You can set a catch-all papermill association :
|
88
|
-
papermill :class_name => MyAssetClass
|
89
|
-
|
90
|
-
# or create an association for the specific :my_gallery key
|
91
|
-
papermill :my_gallery_assets, :class_name => MyGalleryAsset
|
92
163
|
|
93
164
|
=== In your layout:
|
94
165
|
|
95
166
|
<%= papermill_stylesheet_tag %>
|
96
167
|
<%= papermill_javascript_tag :with_jquery => "no_conflict" %>
|
97
|
-
# you
|
98
|
-
|
99
|
-
=== In your edit form:
|
100
|
-
|
101
|
-
f.images_upload(:my_gallery) # use specific papermill :my_gallery declaration
|
102
|
-
f.assets_upload(:my_assets) # use catch-all
|
103
|
-
f.asset_upload(:my_other_asset) # use catch-all
|
104
|
-
|
105
|
-
=== Access them with:
|
106
|
-
|
107
|
-
@assetable.my_gallery_assets.each{ |image| image_tag image.url("100x100") }
|
108
|
-
# equivalent to:
|
109
|
-
@assetable.assets(:my_gallery_assets).each{ |image| image_tag image.url("100x100") }
|
110
|
-
# also equivalent to:
|
111
|
-
@assetable.assets(:conditions => {:assetable_key => 'my_gallery_assets'}).each{ |image| image_tag image.url("100x100") }
|
112
|
-
|
113
|
-
@assetable.assets(:my_assets).each{ |asset| asset.url }
|
114
|
-
# if your association name is singularizable, you can do smtg like :
|
115
|
-
@assetable.asset(:my_other_asset).try(:url)
|
116
|
-
# equivalent to:
|
117
|
-
@assetable.assets(:my_other_asset).first.try(:url)
|
118
|
-
|
119
|
-
# You can change assets/asset with :base_association_name in Papermill::OPTIONS (choose a plural word and you'll get singular association for free)
|
120
|
-
|
121
|
-
Also see http://github.com/bbenezech/papermill/raw/master/installation-template.txt
|
122
|
-
Have a look at the API here http://rdoc.info/projects/bbenezech/papermill
|
168
|
+
# you don't need :with_jquery if you already had it loaded.
|
123
169
|
|
124
170
|
=== Translations:
|
125
171
|
|
data/TODO.txt
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.16.0
|
@@ -4,72 +4,48 @@ class PapermillController < ApplicationController
|
|
4
4
|
skip_before_filter :verify_authenticity_token, :only => [:create]
|
5
5
|
|
6
6
|
def show
|
7
|
-
|
8
|
-
|
9
|
-
asset
|
10
|
-
|
11
|
-
|
12
|
-
raise unless style
|
13
|
-
style = {:geometry => style} unless style.is_a? Hash
|
14
|
-
|
15
|
-
if asset.image?
|
16
|
-
temp_thumbnail = Paperclip::Thumbnail.make(asset_file = asset.file, style)
|
17
|
-
new_parent_folder_path = File.dirname(new_image_path = asset_file.path(params[:style]))
|
18
|
-
FileUtils.mkdir_p new_parent_folder_path unless File.exists? new_parent_folder_path
|
19
|
-
FileUtils.cp temp_thumbnail.path, new_image_path
|
20
|
-
redirect_to asset.url(params[:style])
|
21
|
-
else
|
22
|
-
redirect_to asset.url
|
23
|
-
end
|
24
|
-
rescue
|
25
|
-
render :text => t('papermill.not-found'), :status => "404"
|
7
|
+
@asset = PapermillAsset.find_by_id_partition params
|
8
|
+
if @asset.create_thumb_file(params[:style])
|
9
|
+
redirect_to @asset.url(params[:style])
|
10
|
+
else
|
11
|
+
render :nothing => true, :status => 500
|
26
12
|
end
|
27
13
|
end
|
28
14
|
|
29
15
|
def destroy
|
30
|
-
@asset = PapermillAsset.
|
16
|
+
@asset = PapermillAsset.find params[:id]
|
31
17
|
render :update do |page|
|
32
|
-
if @asset
|
18
|
+
if @asset.destroy
|
33
19
|
page << "jQuery('#papermill_asset_#{params[:id]}').remove()"
|
34
20
|
else
|
35
21
|
page << "jQuery('#papermill_asset_#{params[:id]}').show()"
|
36
|
-
page << %{ notify("#{t(
|
22
|
+
page << %{ notify("#{ escape_javascript t("papermill.not-deleted", :ressource => @asset.name) }", "error") }
|
37
23
|
end
|
38
24
|
end
|
39
25
|
end
|
40
26
|
|
41
27
|
def update
|
42
|
-
@asset = PapermillAsset.
|
28
|
+
@asset = PapermillAsset.find params[:id]
|
43
29
|
render :update do |page|
|
44
|
-
if @asset
|
45
|
-
page << %{ notify("#{t("papermill.updated", :ressource => @asset.name)}", "notice") }
|
30
|
+
if @asset.update_attributes(params[:papermill_asset])
|
31
|
+
page << %{ notify("#{ escape_javascript t("papermill.updated", :ressource => @asset.name)}", "notice") }
|
46
32
|
else
|
47
|
-
page << %{ notify("#{
|
33
|
+
page << %{ notify("#{ escape_javascript @asset.errors.full_messages.to_sentence }", "warning") }
|
48
34
|
end
|
49
35
|
end
|
50
36
|
end
|
51
37
|
|
52
38
|
def edit
|
53
39
|
@asset = PapermillAsset.find params[:id]
|
40
|
+
render :action => "edit", :layout => (params[:layout] || "none")
|
54
41
|
end
|
55
42
|
|
56
43
|
def create
|
57
|
-
|
58
|
-
|
59
|
-
params[:assetable_type] = params[:assetable_type].try :camelize
|
60
|
-
params[:assetable_key] = params[:assetable_key].try :to_s
|
61
|
-
params[:swfupload_file] = params.delete(:Filedata)
|
62
|
-
unless params[:gallery]
|
63
|
-
@old_asset = asset_class.find(:first, :conditions => params.reject{|k, v| !["assetable_key", "assetable_type", "assetable_id"].include?(k)})
|
64
|
-
end
|
65
|
-
@asset = asset_class.new(params.reject{|k, v| !(PapermillAsset.columns.map(&:name)+["swfupload_file"]).include?(k)})
|
66
|
-
@asset.position = asset_class.find(:first, :conditions => params.reject{|k, v| !["assetable_key", "assetable_type", "assetable_id"].include?(k)}, :order => "position DESC" ).try(:position).to_i + 1
|
67
|
-
|
68
|
-
if @asset.save
|
69
|
-
@old_asset.destroy if @old_asset
|
44
|
+
@asset = params[:asset_class].constantize.new(params.reject{|k, v| !(PapermillAsset.columns.map(&:name)+["Filedata", "Filename"]).include?(k)})
|
45
|
+
if @asset.save(:unique => !params[:gallery])
|
70
46
|
render :partial => "papermill/asset", :object => @asset, :locals => {:gallery => params[:gallery], :thumbnail_style => params[:thumbnail_style]}
|
71
47
|
else
|
72
|
-
render :text => @asset.errors.full_messages.join('<br />'), :status =>
|
48
|
+
render :text => @asset.errors.full_messages.join('<br />'), :status => 500
|
73
49
|
end
|
74
50
|
end
|
75
51
|
|
@@ -79,4 +55,29 @@ class PapermillController < ApplicationController
|
|
79
55
|
end
|
80
56
|
render :nothing => true
|
81
57
|
end
|
58
|
+
|
59
|
+
def mass_delete
|
60
|
+
render :update do |page|
|
61
|
+
(params[:papermill_asset] || []).each do |id|
|
62
|
+
@asset = PapermillAsset.find(id)
|
63
|
+
if @asset.destroy
|
64
|
+
page << "jQuery('#papermill_asset_#{id}').remove()"
|
65
|
+
else
|
66
|
+
page << %{ notify('#{ escape_javascript t("papermill.not-deleted", :ressource => @asset.name)}', 'error') }
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def mass_edit
|
73
|
+
message = []
|
74
|
+
(params[:papermill_asset] || []).each do |id|
|
75
|
+
@asset = PapermillAsset.find(id)
|
76
|
+
@asset.update_attribute(params[:attribute], params[:value])
|
77
|
+
message << t("papermill.updated", :ressource => @asset.name)
|
78
|
+
end
|
79
|
+
render :update do |page|
|
80
|
+
page << %{ notify('#{ escape_javascript message.join("<br />")}', "notice") } unless message.empty?
|
81
|
+
end
|
82
|
+
end
|
82
83
|
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
<%- dom_id = "papermill_asset_#{asset.id}" -%>
|
2
|
-
<%- delete_link = %{<a onclick="if(confirm('#{escape_javascript t("papermill.delete-confirmation", :resource => asset.name)}')){
|
2
|
+
<%- delete_link = %{<a onclick="if(confirm('#{escape_javascript t("papermill.delete-confirmation", :resource => asset.name)}')){ jQuery.ajax({async:true, beforeSend:function(request){jQuery('##{dom_id}').hide();}, dataType:'script', error:function(request){jQuery('##{dom_id}').show();}, type:'delete', url:'#{papermill_url(asset)}'})}; return false;" href="#" class="delete"><img title="#{escape_javascript t("papermill.delete", :ressource => asset.name)}" src="/papermill/images/delete.png" alt="delete"/></a>} %>
|
3
3
|
|
4
4
|
<li id="<%= dom_id %>" title="<%= t("papermill.#{thumbnail_style ? "thumbnail-" : ""}edit-title", :ressource => asset.name) %>" onDblClick="popup(jQuery(this).attr('rel')); return false;" rel="<%= edit_papermill_url(asset) %>">
|
5
5
|
<%= delete_link %>
|
@@ -1,13 +1,20 @@
|
|
1
|
-
<
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
<p>
|
6
|
-
|
7
|
-
|
8
|
-
</p>
|
9
|
-
<p>
|
10
|
-
|
11
|
-
|
12
|
-
</p>
|
13
|
-
|
1
|
+
<form onsubmit="jQuery.ajax({data:jQuery.param(jQuery(this).serializeArray()), dataType:'script', type:'post', url:'/papermill/<%= @asset.id %>'}); try {jQuery(document).trigger('close.facebox');} catch (e) {}; try{Shadowbox.close();} catch (e) {}; try{self.close();} catch (e) {}; return false;" method="post" action="/papermill/<%= @asset.id %>">
|
2
|
+
<input type="hidden" value="put" name="_method"/>
|
3
|
+
|
4
|
+
<% fields_for :papermill_asset, @asset do |form| %>
|
5
|
+
<p>
|
6
|
+
<%= form.label :title, t("papermill.title") %><br />
|
7
|
+
<%= form.text_field :title, :class => "text_field" %>
|
8
|
+
</p>
|
9
|
+
<p>
|
10
|
+
<%= form.label :copyright, t("papermill.copyright") %><br />
|
11
|
+
<%= form.text_field :copyright, :class => "text_field" %>
|
12
|
+
</p>
|
13
|
+
<p>
|
14
|
+
<%= form.label :description, t("papermill.description") %><br />
|
15
|
+
<%= form.text_area :description %>
|
16
|
+
</p>
|
17
|
+
<%= submit_tag t('papermill.save') %>
|
18
|
+
<% end %>
|
19
|
+
</form>
|
20
|
+
<%= javascript_include_tag("http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js") if params[:with_jquery] %>
|
@@ -1,6 +1,6 @@
|
|
1
1
|
<div id="papermill-box">
|
2
2
|
<div id="left">
|
3
|
-
<%= link_to(@asset.image? ? image_tag(@asset.url("400x
|
3
|
+
<%= link_to(@asset.image? ? image_tag(@asset.url("400x>")) : t("file_type", :type => @asset.content_type, :scope => 'papermill'), @asset.url, :popup => true) %>
|
4
4
|
</div>
|
5
5
|
<div id="right">
|
6
6
|
<div id="read-only">
|
@@ -17,12 +17,7 @@
|
|
17
17
|
</table>
|
18
18
|
</div>
|
19
19
|
<div id="read-write">
|
20
|
-
|
21
|
-
<input type="hidden" value="put" name="_method"/>
|
22
|
-
<% fields_for :papermill_asset, @asset do |form| %>
|
23
|
-
<%= render :partial => 'form', :object => form %>
|
24
|
-
<% end %>
|
25
|
-
</form>
|
20
|
+
<%= render :partial => 'form' %>
|
26
21
|
</div>
|
27
22
|
</div>
|
28
23
|
<div style="clear:both;"></div>
|