├── VERSION
├── install.rb
├── test
├── fixtures
│ ├── text.txt
│ ├── bad.png
│ ├── 12k.png
│ ├── 5k.png
│ ├── 50x50.png
│ ├── twopage.pdf
│ └── s3.yml
└── papermill_test.rb
├── rails
└── init.rb
├── tasks
└── papermill_tasks.rake
├── uninstall.rb
├── init.rb
├── public
├── .DS_Store
├── facebox
│ ├── b.png
│ ├── bl.png
│ ├── br.png
│ ├── tl.png
│ ├── tr.png
│ ├── loading.gif
│ ├── closelabel.gif
│ ├── README.txt
│ ├── facebox.css
│ └── facebox.js
├── Jcrop
│ ├── images
│ │ └── Jcrop.gif
│ ├── jquery.Jcrop.css
│ ├── jquery.Jcrop.min.js
│ └── jquery.Jcrop.js
├── swfupload
│ ├── swfupload.js
│ └── swfupload.swf
├── papermill
│ ├── images
│ │ ├── delete.png
│ │ ├── upload.png
│ │ ├── background.png
│ │ ├── mass-delete.png
│ │ ├── mass-edit.png
│ │ ├── upload-blank.png
│ │ ├── container-background.jpg
│ │ └── mass-thumbnail-reset.png
│ ├── README
│ ├── papermill.css
│ └── papermill.js
└── jgrowl
│ ├── jquery.jgrowl.css
│ ├── jquery.jgrowl_minimized.js
│ └── jquery.jgrowl.js
├── .gitignore
├── generators
├── papermill_table
│ ├── USAGE
│ ├── papermill_table_generator.rb
│ └── templates
│ │ └── migrate
│ │ └── papermill_migration.rb.erb
├── papermill_assets
│ ├── USAGE
│ └── papermill_assets_generator.rb
└── papermill_initializer
│ ├── USAGE
│ ├── papermill_initializer_generator.rb
│ └── papermill_initializer.rb
├── app
├── views
│ └── papermill
│ │ ├── _raw_asset.html.erb
│ │ ├── _thumbnail_asset.html.erb
│ │ ├── _asset.html.erb
│ │ ├── _form.html.erb
│ │ ├── browser.html.erb
│ │ ├── edit.html.erb
│ │ └── crop.html.erb
└── controllers
│ └── papermill_controller.rb
├── TODO.txt
├── lib
├── papermill
│ ├── papermill_association.rb
│ ├── flash_session_cookie_middleware.rb
│ ├── extensions.rb
│ ├── papermill_helper.rb
│ ├── papermill_options.rb
│ ├── papermill_paperclip_processor.rb
│ ├── papermill.rb
│ ├── papermill_asset.rb
│ └── form_builder.rb
└── papermill.rb
├── config
├── routes.rb
└── locales
│ └── papermill.yml
├── MIT-LICENSE
├── Rakefile
├── demo.txt
└── README.rdoc
/VERSION:
--------------------------------------------------------------------------------
1 | 2.1.1
--------------------------------------------------------------------------------
/install.rb:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/text.txt:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/rails/init.rb:
--------------------------------------------------------------------------------
1 | require 'papermill'
--------------------------------------------------------------------------------
/tasks/papermill_tasks.rake:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/uninstall.rb:
--------------------------------------------------------------------------------
1 | # Uninstall hook code here
2 |
--------------------------------------------------------------------------------
/test/fixtures/bad.png:
--------------------------------------------------------------------------------
1 | This is not an image.
2 |
--------------------------------------------------------------------------------
/init.rb:
--------------------------------------------------------------------------------
1 | require File.dirname(__FILE__) + "/rails/init.rb"
2 |
--------------------------------------------------------------------------------
/public/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bbenezech/papermill/HEAD/public/.DS_Store
--------------------------------------------------------------------------------
/public/facebox/b.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bbenezech/papermill/HEAD/public/facebox/b.png
--------------------------------------------------------------------------------
/public/facebox/bl.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bbenezech/papermill/HEAD/public/facebox/bl.png
--------------------------------------------------------------------------------
/public/facebox/br.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bbenezech/papermill/HEAD/public/facebox/br.png
--------------------------------------------------------------------------------
/public/facebox/tl.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bbenezech/papermill/HEAD/public/facebox/tl.png
--------------------------------------------------------------------------------
/public/facebox/tr.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bbenezech/papermill/HEAD/public/facebox/tr.png
--------------------------------------------------------------------------------
/test/fixtures/12k.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bbenezech/papermill/HEAD/test/fixtures/12k.png
--------------------------------------------------------------------------------
/test/fixtures/5k.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bbenezech/papermill/HEAD/test/fixtures/5k.png
--------------------------------------------------------------------------------
/test/fixtures/50x50.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bbenezech/papermill/HEAD/test/fixtures/50x50.png
--------------------------------------------------------------------------------
/public/facebox/loading.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bbenezech/papermill/HEAD/public/facebox/loading.gif
--------------------------------------------------------------------------------
/test/fixtures/twopage.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bbenezech/papermill/HEAD/test/fixtures/twopage.pdf
--------------------------------------------------------------------------------
/public/Jcrop/images/Jcrop.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bbenezech/papermill/HEAD/public/Jcrop/images/Jcrop.gif
--------------------------------------------------------------------------------
/public/facebox/closelabel.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bbenezech/papermill/HEAD/public/facebox/closelabel.gif
--------------------------------------------------------------------------------
/public/swfupload/swfupload.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bbenezech/papermill/HEAD/public/swfupload/swfupload.js
--------------------------------------------------------------------------------
/public/swfupload/swfupload.swf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bbenezech/papermill/HEAD/public/swfupload/swfupload.swf
--------------------------------------------------------------------------------
/public/papermill/images/delete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bbenezech/papermill/HEAD/public/papermill/images/delete.png
--------------------------------------------------------------------------------
/public/papermill/images/upload.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bbenezech/papermill/HEAD/public/papermill/images/upload.png
--------------------------------------------------------------------------------
/public/papermill/images/background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bbenezech/papermill/HEAD/public/papermill/images/background.png
--------------------------------------------------------------------------------
/public/papermill/images/mass-delete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bbenezech/papermill/HEAD/public/papermill/images/mass-delete.png
--------------------------------------------------------------------------------
/public/papermill/images/mass-edit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bbenezech/papermill/HEAD/public/papermill/images/mass-edit.png
--------------------------------------------------------------------------------
/public/papermill/images/upload-blank.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bbenezech/papermill/HEAD/public/papermill/images/upload-blank.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | test/*.log
3 | log/*.log
4 | tmp/**/*
5 | config/database.yml
6 | db/*.sqlite3
7 | test.sqlte3
8 | papermill.gemspec
9 |
--------------------------------------------------------------------------------
/generators/papermill_table/USAGE:
--------------------------------------------------------------------------------
1 | script/generate papermill_table PapermillMigration
2 |
3 | This will create the Papermill table migration file!
4 |
--------------------------------------------------------------------------------
/public/papermill/images/container-background.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bbenezech/papermill/HEAD/public/papermill/images/container-background.jpg
--------------------------------------------------------------------------------
/public/papermill/images/mass-thumbnail-reset.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bbenezech/papermill/HEAD/public/papermill/images/mass-thumbnail-reset.png
--------------------------------------------------------------------------------
/generators/papermill_assets/USAGE:
--------------------------------------------------------------------------------
1 | script/generate papermill_assets
2 |
3 | This will copy Papermill needed assets to public/papermill/
4 | The old public/papermill directory will be deleted if present!
5 |
--------------------------------------------------------------------------------
/app/views/papermill/_raw_asset.html.erb:
--------------------------------------------------------------------------------
1 | <%= link_to(raw_asset.name, edit_papermill_url(raw_asset), :class => "name", :onClick => "popup(jQuery(this).attr('rel')); return false;", :rel => edit_papermill_url(raw_asset)) -%>
--------------------------------------------------------------------------------
/generators/papermill_initializer/USAGE:
--------------------------------------------------------------------------------
1 | script/generate papermill_initializer
2 |
3 | This will copy Papermill option hash to your config/initializers directory
4 | The old file directory will be deleted if present!
5 |
--------------------------------------------------------------------------------
/public/facebox/README.txt:
--------------------------------------------------------------------------------
1 | Please visit http://famspam.com/facebox/ or open index.html in your favorite browser.
2 |
3 | Need help? Join our Google Groups mailing list:
4 | http://groups.google.com/group/facebox/
5 |
--------------------------------------------------------------------------------
/test/fixtures/s3.yml:
--------------------------------------------------------------------------------
1 | development:
2 | key: 54321
3 | production:
4 | key: 12345
5 | test:
6 | bucket: <%= ENV['S3_BUCKET'] %>
7 | access_key_id: <%= ENV['S3_KEY'] %>
8 | secret_access_key: <%= ENV['S3_SECRET'] %>
9 |
--------------------------------------------------------------------------------
/TODO.txt:
--------------------------------------------------------------------------------
1 | # extend watermark possibilities
2 | # refresh images when cropped
3 | # authorize images before edit/crop
4 | # documentation for papermill_associations
5 | # file search helper for papermill_associations
6 | # tests, loads of them
7 | # rails3 branch when RC is out
--------------------------------------------------------------------------------
/lib/papermill/papermill_association.rb:
--------------------------------------------------------------------------------
1 | class PapermillAssociation < ActiveRecord::Base
2 | belongs_to :papermill_asset
3 | belongs_to :assetable, :polymorphic => true
4 |
5 | def assetable_type=(sType)
6 | super(sType.to_s.classify.constantize.base_class.to_s)
7 | end
8 | end
--------------------------------------------------------------------------------
/public/papermill/README:
--------------------------------------------------------------------------------
1 | DO NOT !!
2 |
3 | Modify files in this folder, they are generated by papermill
4 |
5 |
6 | DO !!
7 |
8 | Load them before your own and override them in your application.
9 |
10 | And each time you update Papermill's gem :
11 |
12 | cd my_rails_app
13 | ./script/generate papermill_assets
14 |
--------------------------------------------------------------------------------
/app/views/papermill/_thumbnail_asset.html.erb:
--------------------------------------------------------------------------------
1 | <% if thumbnail_asset.image? %>
2 | <%= image_tag(thumbnail_asset.url(thumbnail_style)) %>
3 | <% else %>
4 | <%= truncate(thumbnail_asset.name, :length => 15) %>
5 | <%= thumbnail_asset.content_type %>
6 | <% end %>
7 |
--------------------------------------------------------------------------------
/config/routes.rb:
--------------------------------------------------------------------------------
1 | ActionController::Routing::Routes.draw do |map|
2 | map.resources :papermill, :collection => { :mass_edit => :post, :add_list => :post, :browser => :get }, :member => { :crop => :get }
3 | map.connect "#{Papermill::options[:papermill_url_prefix]}/#{Papermill::compute_paperclip_path.gsub(":id_partition", ":id0/:id1/:id2")}", :controller => "papermill", :action => "show", :requirements => { :style => /.*/ }
4 | end
--------------------------------------------------------------------------------
/generators/papermill_assets/papermill_assets_generator.rb:
--------------------------------------------------------------------------------
1 | class PapermillAssetsGenerator < Rails::Generator::Base
2 | def initialize(args, options = {})
3 | end
4 |
5 | def manifest
6 | puts "Copying papermill assets to your public directory..."
7 | FileUtils.cp_r(
8 | Dir[File.join(File.dirname(__FILE__), '../../public')],
9 | File.join(RAILS_ROOT)
10 | )
11 | puts "Done! Check public/ for result."
12 | exit
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/lib/papermill/flash_session_cookie_middleware.rb:
--------------------------------------------------------------------------------
1 | require 'rack/utils'
2 |
3 | class FlashSessionCookieMiddleware
4 | def initialize(app, session_key = '_session_id')
5 | @app = app
6 | @session_key = session_key
7 | end
8 |
9 | def call(env)
10 | if env['HTTP_USER_AGENT'] =~ /^(Adobe|Shockwave) Flash/
11 | params = ::Rack::Request.new(env).params
12 | env['HTTP_COOKIE'] = [ @session_key, params[@session_key] ].join('=').freeze unless params[@session_key].nil?
13 | end
14 | @app.call(env)
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/generators/papermill_initializer/papermill_initializer_generator.rb:
--------------------------------------------------------------------------------
1 | class PapermillInitializerGenerator < Rails::Generator::Base
2 | def initialize(args, options = {})
3 | end
4 |
5 | def manifest
6 | puts "Copying papermill initializer to config/initializers/..."
7 | FileUtils.rm_rf("#{RAILS_ROOT}/config/initializers/papermill.rb")
8 | FileUtils.cp_r(
9 | File.join(File.dirname(__FILE__), 'papermill_initializer.rb'),
10 | "#{RAILS_ROOT}/config/initializers/papermill.rb"
11 | )
12 | puts "Done! Check config/initializer/papermill.rb for result."
13 | exit
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/generators/papermill_table/papermill_table_generator.rb:
--------------------------------------------------------------------------------
1 | class PapermillTableGenerator < Rails::Generator::NamedBase
2 | attr_accessor :class_name, :migration_name
3 |
4 | def initialize(args, options = {})
5 | super
6 | @class_name = args[0]
7 | end
8 |
9 | def manifest
10 | @migration_name = file_name.camelize
11 |
12 | FileUtils.rm_rf("#{File.join(RAILS_ROOT)}/public/papermill/")
13 | FileUtils.cp_r(
14 | Dir[File.join(File.dirname(__FILE__), '../../public')],
15 | File.join(RAILS_ROOT),
16 | :verbose => true
17 | )
18 |
19 | record do |m|
20 | # Migration creation
21 | m.migration_template "migrate/papermill_migration.rb.erb", "db/migrate", :migration_file_name => migration_name.underscore
22 | end
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/public/Jcrop/jquery.Jcrop.css:
--------------------------------------------------------------------------------
1 | /* Fixes issue here http://code.google.com/p/jcrop/issues/detail?id=1 */
2 | .jcrop-holder { text-align: left; }
3 |
4 | .jcrop-vline, .jcrop-hline
5 | {
6 | font-size: 0;
7 | position: absolute;
8 | background: white url('/Jcrop/images/Jcrop.gif') top left repeat;
9 | }
10 | .jcrop-vline { height: 100%; width: 1px !important; }
11 | .jcrop-hline { width: 100%; height: 1px !important; }
12 | .jcrop-handle {
13 | font-size: 1px;
14 | width: 7px !important;
15 | height: 7px !important;
16 | border: 1px #eee solid;
17 | background-color: #333;
18 | *width: 9px;
19 | *height: 9px;
20 | }
21 |
22 | .jcrop-tracker { width: 100%; height: 100%; }
23 |
24 | .custom .jcrop-vline,
25 | .custom .jcrop-hline
26 | {
27 | background: yellow;
28 | }
29 | .custom .jcrop-handle
30 | {
31 | border-color: black;
32 | background-color: #C7BB00;
33 | -moz-border-radius: 3px;
34 | -webkit-border-radius: 3px;
35 | }
36 |
--------------------------------------------------------------------------------
/app/views/papermill/_asset.html.erb:
--------------------------------------------------------------------------------
1 | <%- dom_id = "#{field_id}_papermill_asset_#{asset.id}" -%>
2 | <%- delete_link = %{ asset.name)}" src="/papermill/images/delete.png" alt="delete"/> } %>
3 |
4 | asset.name) %>" onDblClick="popup(jQuery(this).attr('rel')); return false;" rel="<%= edit_papermill_url(asset, :targetted_size => targetted_size) %>">
5 | <%= delete_link %>
6 | <%- if thumbnail_style -%>
7 | <%= render :partial => "papermill/thumbnail_asset", :object => asset, :locals => {:thumbnail_style => thumbnail_style} %>
8 | <%- else -%>
9 | <%= render :partial => "papermill/raw_asset", :object => asset %>
10 | <%- end -%>
11 | <%= hidden_field_tag field_name, asset.id.to_s, :id => nil %>
12 |
13 |
--------------------------------------------------------------------------------
/app/views/papermill/_form.html.erb:
--------------------------------------------------------------------------------
1 |
24 |
--------------------------------------------------------------------------------
/MIT-LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2009 [Benoit Bénézech]
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/app/views/papermill/browser.html.erb:
--------------------------------------------------------------------------------
1 | <% params.delete(:action) %>
2 |
--------------------------------------------------------------------------------
/lib/papermill.rb:
--------------------------------------------------------------------------------
1 | require "rbconfig"
2 | require "mime/types"
3 | require "rubygems"
4 | gem "paperclip", "2.3.1.1"
5 | require "paperclip"
6 |
7 | I18n.load_path = [File.join(File.dirname(__FILE__), "../config/locales/papermill.yml")] + I18n.load_path
8 | require 'papermill/extensions'
9 | require 'papermill/flash_session_cookie_middleware.rb'
10 |
11 | Object.send :include, PapermillObjectExtensions
12 | Hash.send :include, PapermillHashExtensions
13 | Array.send :include, PapermillArrayExtensions
14 | String.send :include, StringToUrlNotFound unless String.instance_methods.include? "to_url"
15 | String.send :include, HtmlSafeBackwardCompatibilityFix unless String.instance_methods.include? "html_safe"
16 | Formtastic::SemanticFormBuilder.send(:include, PapermillFormtasticExtensions) rescue NameError
17 |
18 | require 'papermill/papermill_options.rb'
19 | begin
20 | require File.join(RAILS_ROOT, "config/initializers/papermill.rb")
21 | rescue LoadError, MissingSourceFile
22 | end
23 | require 'papermill/papermill_paperclip_processor'
24 | require 'papermill/papermill'
25 | require 'papermill/papermill_association'
26 | require 'papermill/papermill_asset'
27 | require 'papermill/form_builder'
28 | require 'papermill/papermill_helper'
29 | ActionView::Base.send :include, PapermillHelper
30 | ActiveRecord::Base.send :include, Papermill
31 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 | require 'rake'
3 | require 'rake/testtask'
4 | require 'rake/rdoctask'
5 |
6 | desc 'Default: run unit tests.'
7 | task :default => :test
8 |
9 | desc 'Test the papermill plugin.'
10 | Rake::TestTask.new(:test) do |t|
11 | t.libs << 'lib'
12 | t.libs << 'test'
13 | t.pattern = 'test/**/*_test.rb'
14 | t.verbose = true
15 | end
16 |
17 | desc 'Generate documentation for the papermill plugin.'
18 | Rake::RDocTask.new(:rdoc) do |rdoc|
19 | rdoc.rdoc_dir = 'rdoc'
20 | rdoc.title = 'Papermill'
21 | rdoc.options << '--line-numbers' << '--inline-source'
22 | rdoc.rdoc_files.include('README')
23 | rdoc.rdoc_files.include('lib/**/*.rb')
24 | end
25 |
26 | begin
27 | require 'jeweler'
28 | Jeweler::Tasks.new do |gemspec|
29 | gemspec.name = "papermill"
30 | gemspec.summary = "Paperclip Swfupload UploadHelper wrapper"
31 | gemspec.description = "Paperclip Swfupload UploadHelper wrapper"
32 | gemspec.email = "benoit.benezech@gmail.com"
33 | gemspec.homepage = "http://github.com/bbenezech/papermill"
34 | gemspec.authors = ["Benoit Bénézech"]
35 | gemspec.add_dependency('paperclip', '2.3.1.1')
36 | gemspec.add_dependency('mime-types')
37 | end
38 | rescue LoadError
39 | puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
40 | end
--------------------------------------------------------------------------------
/lib/papermill/extensions.rb:
--------------------------------------------------------------------------------
1 | class PapermillException < Exception; end
2 |
3 | module HtmlSafeBackwardCompatibilityFix
4 | def html_safe
5 | self
6 | end
7 | end
8 |
9 | module PapermillHashExtensions
10 | def deep_merge(hash)
11 | target = dup
12 | hash.keys.each do |key|
13 | if hash[key].is_a? Hash and self[key].is_a? Hash
14 | target[key] = target[key].deep_merge(hash[key])
15 | next
16 | end
17 | target[key] = hash[key]
18 | end
19 | target
20 | end
21 | end
22 |
23 | module PapermillObjectExtensions
24 | # Nil if empty.
25 | def nie
26 | self.blank? ? nil : self
27 | end
28 | end
29 |
30 | module PapermillArrayExtensions
31 | def map_with_index!
32 | each_with_index do |e, idx| self[idx] = yield(e, idx); end
33 | end
34 |
35 | def map_with_index(&block)
36 | dup.map_with_index!(&block)
37 | end
38 | end
39 |
40 | module PapermillFormtasticExtensions
41 | def image_upload_input(method, options)
42 | self.label(method, options_for_label(options)) +
43 | self.send(:image_upload, method, options)
44 | end
45 | def images_upload_input(method, options)
46 | self.label(method, options_for_label(options)) +
47 | self.send(:images_upload, method, options)
48 | end
49 | def asset_upload_input(method, options)
50 | self.label(method, options_for_label(options)) +
51 | self.send(:asset_upload, method, options)
52 | end
53 | def assets_upload_input(method, options)
54 | self.label(method, options_for_label(options)) +
55 | self.send(:assets_upload, method, options)
56 | end
57 | end
58 |
59 |
60 | module StringToUrlNotFound
61 | def to_url
62 | gsub(/[^a-zA-Z0-9]/, "-").gsub(/-+/, "-").gsub(/^-|-$/, "").downcase
63 | end
64 | end
65 |
--------------------------------------------------------------------------------
/public/facebox/facebox.css:
--------------------------------------------------------------------------------
1 | #facebox .b {
2 | background:url(/facebox/b.png);
3 | }
4 |
5 | #facebox .tl {
6 | background:url(/facebox/tl.png);
7 | }
8 |
9 | #facebox .tr {
10 | background:url(/facebox/tr.png);
11 | }
12 |
13 | #facebox .bl {
14 | background:url(/facebox/bl.png);
15 | }
16 |
17 | #facebox .br {
18 | background:url(/facebox/br.png);
19 | }
20 |
21 | #facebox {
22 | position: absolute;
23 | top: 0;
24 | left: 0;
25 | z-index: 100;
26 | text-align: left;
27 | }
28 |
29 | #facebox .popup {
30 | position: relative;
31 | }
32 |
33 | #facebox table {
34 | border-collapse: collapse;
35 | }
36 |
37 | #facebox td {
38 | border-bottom: 0;
39 | padding: 0;
40 | }
41 |
42 | #facebox .body {
43 | padding: 10px;
44 | background: #fff;
45 | width: 370px;
46 | }
47 |
48 | #facebox .loading {
49 | text-align: center;
50 | }
51 |
52 | #facebox .image {
53 | text-align: center;
54 | }
55 |
56 | #facebox img {
57 | border: 0;
58 | margin: 0;
59 | }
60 |
61 | #facebox .footer {
62 | border-top: 1px solid #DDDDDD;
63 | padding-top: 5px;
64 | margin-top: 10px;
65 | text-align: right;
66 | }
67 |
68 | #facebox .tl, #facebox .tr, #facebox .bl, #facebox .br {
69 | height: 10px;
70 | width: 10px;
71 | overflow: hidden;
72 | padding: 0;
73 | }
74 |
75 | #facebox_overlay {
76 | position: fixed;
77 | top: 0px;
78 | left: 0px;
79 | height:100%;
80 | width:100%;
81 | }
82 |
83 | .facebox_hide {
84 | z-index:-100;
85 | }
86 |
87 | .facebox_overlayBG {
88 | background-color: #000;
89 | z-index: 99;
90 | }
91 |
92 | * html #facebox_overlay { /* ie6 hack */
93 | position: absolute;
94 | height: expression(document.body.scrollHeight > document.body.offsetHeight ? document.body.scrollHeight : document.body.offsetHeight + 'px');
95 | }
96 |
--------------------------------------------------------------------------------
/app/views/papermill/edit.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 | <% if @asset.image? %>
4 | <%= image_tag(@asset.url("400x400>"), :onDblClick => "popup('#{crop_papermill_path(@asset, :target => "original", :targetted_size => params[:targetted_size])}'); return false;", :title => I18n.t("papermill.crop", :resource => @asset.name)) %>
5 |
6 | <% else %>
7 | <%= link_to t("file_type", :type => @asset.content_type, :scope => 'papermill'), @asset.url, :popup => true %>
8 | <% end -%>
9 |
10 |
11 |
12 |
13 | <%= t("papermill.name") %> <%= @asset.name %>
14 | <%= t("papermill.content_type") %> <%= @asset.content_type %>
15 | <%= t("papermill.size") %> <%= number_to_human_size(@asset.size.to_i) %>
16 | <% if @asset.image? %>
17 | <%= t("papermill.width") %> <%= @asset.width.to_i %>px
18 | <%= t("papermill.height") %> <%= @asset.height.to_i %>px
19 | <% end %>
20 | <%= t("papermill.created_at") %> <%= I18n.l(@asset.created_at) %>
21 | <%= t("papermill.updated_at") %> <%= I18n.l(@asset.updated_at) %>
22 |
23 |
24 |
25 | <%= render :partial => 'form' %>
26 |
27 |
28 |
29 |
30 |
33 |
34 |
--------------------------------------------------------------------------------
/lib/papermill/papermill_helper.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 |
3 | module PapermillHelper
4 |
5 | # Sets all the javascript needed for papermill.
6 | # If jQuery and JQueryUI (with Sortable) are already loaded, call papermill_javascript_tag
7 | # If you use some other JS Framework, call papermill_javascript_tag(:with_jquery => "no_conflict")
8 | # If you want to rely on this helper to load jQuery and JQueryUI and use them after, call papermill_javascript_tag(:with_jquery => true)
9 |
10 | def papermill_javascript_tag(options = {})
11 | html = []
12 | html << %{\
13 | } if options[:with_jquery]
14 | html << %{}
17 | html << javascript_include_tag("/facebox/facebox.js", "/jgrowl/jquery.jgrowl_minimized.js", "/Jcrop/jquery.Jcrop.min.js", "/swfupload/swfupload.js", "/papermill/papermill.js", :cache => "papermill")
18 | unless @content_for_papermill_inline_js.blank?
19 | html << %{}
22 | end
23 | html.join("\n")
24 | end
25 |
26 | # Sets the css tags needed for papermill.
27 | def papermill_stylesheet_tag(options = {})
28 | html = []
29 | html << stylesheet_link_tag("/facebox/facebox.css", "/jgrowl/jquery.jgrowl.css", "/Jcrop/jquery.Jcrop.css", "/papermill/papermill.css", :cache => "papermill")
30 | unless @content_for_papermill_inline_css.blank?
31 | html << %{}
34 | end
35 | html.join("\n")
36 | end
37 | end
--------------------------------------------------------------------------------
/config/locales/papermill.yml:
--------------------------------------------------------------------------------
1 | en:
2 | papermill:
3 | not-processed: "Error/resource not processed"
4 | updated: "'{{resource}}' updated"
5 | not-found: "'{{resource}}' not found"
6 | edit-title: "Click to edit '{{resource}}'"
7 | crop: "Double-click to crop '{{resource}}'"
8 | edit-with-pixlr: "Edit '{{resource}}' online @ pixlr.com"
9 | thumbnail-edit-title: "Double-click to edit '{{resource}}'"
10 | upload-button-wording: "Upload..."
11 | delete: "Remove {{resource}}"
12 | delete-confirmation: "Delete '{{resource}}'?"
13 | file_type: "{{type}} file"
14 | name: "Name"
15 | content_type: "Content-type"
16 | size: "File size"
17 | class_name: "Asset type"
18 | created_at: "Created at"
19 | updated_at: "Updated at"
20 | title: "Title"
21 | copyright: "Copyright"
22 | alt: "Alternative text"
23 | description: "Description"
24 | width: "Width"
25 | height: "Height"
26 | save: "Save"
27 | back: "Back"
28 | url: "URL : "
29 | modify-all: "Modify all: "
30 | delete-all: "Delete all"
31 | delete-all-confirmation: "Are you sure?"
32 | mass-thumbnail-reset: "Delete all thumbnails"
33 | mass-thumbnail-reset-confirmation: "Are you sure?"
34 | fr:
35 | papermill:
36 | not-processed: "Erreur/ressource non trouvée"
37 | updated: "'{{resource}}' modifiés(s)"
38 | not-found: "'{{resource}}' non trouvé"
39 | edit-title: "Cliquer pour éditer '{{resource}}'"
40 | crop: "Double-cliquer pour retailler '{{resource}}'"
41 | edit-with-pixlr: "Éditer '{{resource}}' en-ligne sur pixlr.com"
42 | thumbnail-edit-title: "Double-cliquer pour éditer '{{resource}}'"
43 | upload-button-wording: "Charger.."
44 | delete: "Supprimer {{resource}}"
45 | delete-confirmation: "Êtes-vous sûr de vouloir supprimer '{{resource}}' ?"
46 | file_type: "Fichier {{type}}"
47 | name: "Nom"
48 | content_type: "Content-type"
49 | size: "Taille du fichier"
50 | class_name: "Type d'asset"
51 | created_at: "Création"
52 | updated_at: "Mise à jour"
53 | title: "Titre"
54 | copyright: "Copyright"
55 | alt: "Texte alternatif"
56 | description: "Description"
57 | width: "Largeur"
58 | height: "Hauteur"
59 | save: "Modifier"
60 | back: "Retour"
61 | url: "Lien : "
62 | modify-all: "Modifier tout : "
63 | delete-all: "Supprimer tout"
64 | delete-all-confirmation: "Êtes-vous sûr ?"
65 | mass-thumbnail-reset: "Supprimer tous les thumbnails"
66 | mass-thumbnail-reset-confirmation: "Êtes-vous sûr ?"
67 |
--------------------------------------------------------------------------------
/lib/papermill/papermill_options.rb:
--------------------------------------------------------------------------------
1 | module Papermill
2 | BASE_OPTIONS = {
3 | :class_name => "PapermillAsset",
4 | :through => false,
5 | :inline_css => true,
6 | :use_content_for => true,
7 | :images_only => false,
8 | :form_helper_elements => [:upload_button, :container, :browser, :mass_edit],
9 | :mass_edit => true,
10 | :mass_editable_fields => ["title", "copyright", "description"],
11 | :editable_fields => [
12 | {:title => {:type => "string"}},
13 | {:alt => {:type => "string"}},
14 | {:copyright => {:type => "string"}},
15 | {:description => {:type => "text" }}
16 | ],
17 | :targetted_size => nil,
18 | :gallery => {
19 | :width => nil,
20 | :height => nil,
21 | :columns => 8,
22 | :lines => 2,
23 | :vpadding => 0,
24 | :hpadding => 0,
25 | :vmargin => 1,
26 | :hmargin => 1,
27 | :border_thickness => 2
28 | },
29 | :thumbnail => {
30 | :width => 100,
31 | :height => 100,
32 | :aspect_ratio => nil,
33 | :style => nil
34 | },
35 | :swfupload => {
36 | :flash_url => "'/swfupload/swfupload.swf'",
37 | :button_image_url => "'/papermill/images/upload-blank.png'",
38 | :button_width => 61,
39 | :button_height => 22,
40 | :button_text => %{'#{I18n.t("papermill.upload-button-wording")} '},
41 | :button_text_style => %{'.button-text { font-size: 12pt; font-weight: bold; }'},
42 | :button_text_top_padding => 4,
43 | :button_text_left_padding => 4,
44 | :debug => false,
45 | :prevent_swf_caching => true,
46 | :file_size_limit => "'10 MB'"
47 | },
48 | :copyright => nil,
49 | :copyright_text_transform => Proc.new {|c| c },
50 | :copyright_im_command => %{\\( -font Arial-Bold -pointsize 9 -fill '#FFFFFFE0' -border 3 -bordercolor '#50550080' -background '#00000000' label:' %s ' \\) -gravity South-West -geometry +0+0 -composite},
51 | :watermark => "/images/rails.png",
52 | :watermark_im_command => %{- | composite \\( %s -resize 100% \\) - -dissolve 20% -gravity center -geometry +0+0 },
53 | :alias_only => false,
54 | :aliases => {},
55 | :use_url_key => false,
56 | :url_key_salt => "change-me-please",
57 | :url_key_generator => Proc.new { |style, asset| Digest::SHA512.hexdigest("#{style}#{asset.id}#{Papermill::options[:url_key_salt]}")[0..10] },
58 | :use_id_partition => true,
59 | :papermill_url_prefix => "/system/papermill",
60 | :papermill_path_prefix => ":rails_root/public/system/papermill"
61 | }
62 | end
63 |
--------------------------------------------------------------------------------
/generators/papermill_table/templates/migrate/papermill_migration.rb.erb:
--------------------------------------------------------------------------------
1 | class <%= migration_name %> < ActiveRecord::Migration
2 | def self.up
3 | create_table :papermill_assets do |t|
4 |
5 | # Paperclip fields (required)
6 | t.string :file_file_name
7 | t.string :file_content_type
8 | t.integer :file_file_size
9 |
10 | # Papermill fields (required)
11 | t.integer :position # sets are ordered by position
12 |
13 | t.belongs_to :assetable, :polymorphic => true
14 | t.string :assetable_key
15 | t.string :type # PapermillAsset STI
16 | t.string :title # filename not transformed, without file extension, for your own use
17 |
18 | # Papermill magical fields (You'll need to configure :mass_editable_fields/:editable_fields accordingly to be able to modify them with Papermill helpers)
19 |
20 | t.string :copyright # copyright content
21 | t.string :copyright_im_command # copyright ImageMagick command
22 | t.string :watermark # watermark URI
23 | t.string :watermark_im_command # watermark ImageMagick content
24 |
25 | # Example additionals fields (configure :mass_editable_fields/:editable_fields accordingly to be able to modify them with Papermill helpers)
26 |
27 | t.string :alt
28 | t.text :description
29 | t.timestamps
30 | end
31 |
32 | change_table :papermill_assets do |t|
33 | t.index [:assetable_id, :assetable_type, :assetable_key, :position], { :name => "papermill_assets_index" }
34 | t.index [:assetable_key, :position] # for non assetable assets
35 | end
36 |
37 | # If you want to associate an asset to more than one assetable, use the (:through => true) option to use the join-table and get an assetable with smtg like
38 | # has_many :assets, :through => :papermill_associations
39 |
40 | create_table :papermill_associations, :force => true do |t|
41 | t.belongs_to :assetable, :polymorphic => true
42 | t.string :assetable_key
43 | t.integer :position
44 | t.belongs_to :papermill_asset
45 | t.timestamps
46 | end
47 |
48 | change_table :papermill_associations do |t|
49 | t.index [:assetable_id, :assetable_type, :assetable_key, :position], { :name => "papermill_associations_index" }
50 | t.index :papermill_asset_id
51 | end
52 |
53 | end
54 |
55 | def self.down
56 | drop_table :papermill_assets
57 | drop_table :papermill_associations
58 | end
59 | end
60 |
--------------------------------------------------------------------------------
/app/views/papermill/crop.html.erb:
--------------------------------------------------------------------------------
1 | <%= image_tag(@asset.url, :id => "cropbox") %>
2 |
3 |
24 |
25 | <%
26 | unless (@size = params[:targetted_size]).blank?
27 | @target_w, @target_h = @size.split("x").map(&:to_i)
28 | @init_x = (@asset.width - @target_w.to_i) / 2
29 | @init_y = (@asset.height - @target_h.to_i) / 2
30 | end
31 | %>
32 |
33 |
34 |
35 |
47 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/public/papermill/papermill.css:
--------------------------------------------------------------------------------
1 | #papermill-box { width:862px; margin-bottom:10px; }
2 | #papermill-box #left { float:left; width:400px; padding-bottom:10px; }
3 | #papermill-box #right { float:right; width:420px; margin-left:20px; border-left:2px solid GREY; padding-left:20px; padding-bottom:10px; }
4 | #papermill-box input.text_field { width:400px; margin:5px 0; }
5 | #papermill-box textarea { width:403px; height:100px; margin:5px 0; }
6 | #papermill-box label { }
7 | #papermill-box p { padding: 3px 0; margin:0;}
8 |
9 | #papermill-box #footer { text-align:center; font-size:10px; padding:5px }
10 | #papermill-box #read-write { margin:10px 0 0 5px; }
11 |
12 | #papermill-box #error { color:red; padding:10px; margin:10px; border:1px dashed green; background-color:#DDD; }
13 |
14 | #papermill-box .right-cell {}
15 | #papermill-box .left-cell {font-weight:bold; padding-right:10px; text-align:right;}
16 |
17 | .papermill { overflow:hidden; }
18 | .papermill a img { border:0px; }
19 | .papermill .asset:hover { border-color:blue; }
20 | .papermill .asset a { display:block; }
21 | .papermill .progress { display:block; border:1px solid #C2E3EF; text-align:left; height:6px; }
22 | .papermill .progress span { background:#7BB963; height:6px; width:0; display:block; }
23 |
24 | .papermill .dashboard li a { display:inline; padding-left:20px }
25 | .papermill .dashboard li.mass_edit a { background:transparent url(/papermill/images/mass-edit.png) no-repeat top left; }
26 |
27 | .papermill-thumb-container { position:relative; border:5px solid #EEE; padding:4px; overflow:hidden; }
28 | .papermill-thumb-container .asset { border:0px solid transparent; min-height:25px; min-width:25px; display:block; float:left; position:relative; }
29 | .papermill-thumb-container .asset span { display:block; }
30 | .papermill-thumb-container .asset .delete { position:absolute; bottom:5px; right:5px; }
31 | .papermill-thumb-container .asset .name { font-size:10px; overflow:hidden; font-weight:bold; }
32 | .papermill-thumb-container .asset .infos { font-size:8px; overflow:hidden; }
33 | .papermill-thumb-container .asset .status { margin-bottom:10px; }
34 |
35 | .papermill-asset-container { border:1px solid #EEE; padding:3px; }
36 | .papermill-asset-container .asset { display:block; height:22px; }
37 | .papermill-asset-container .asset .swfupload .name { margin-left:21px; }
38 | .papermill-asset-container .asset .name { float:left; }
39 | .papermill-asset-container .asset .status { margin-left:5px; float:left; }
40 | .papermill-asset-container .asset .progress { float:left; margin-top:6px; margin-left:5px; width:100px; }
41 | .papermill-asset-container .asset .delete { float:left; margin-top:2px; margin-right:5px; }
42 |
43 | .papermill-thumb-container.papermill-multiple-items .asset { cursor:move; }
44 |
45 | .papermill-asset-container.papermill-multiple-items { padding-left:10px; border-left:5px solid #EEE; min-height:44px; }
46 | .papermill-asset-container.papermill-multiple-items .asset { cursor:row-resize; }
47 |
48 | .papermill-asset-container.papermill-unique-item { padding-left:5px; min-height:22px; }
49 |
50 | /* Need some backgrounds?
51 | .papermill .asset { background:transparent url(/papermill/images/background.png) repeat top left; }
52 | .papermill-thumb-container { background:transparent url(/papermill/images/container-background.jpg) repeat top left; }
53 | */
54 |
--------------------------------------------------------------------------------
/lib/papermill/papermill_paperclip_processor.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 |
3 | module Paperclip
4 |
5 | # Handles thumbnailing images that are uploaded.
6 | class PapermillPaperclipProcessor < Thumbnail
7 |
8 | attr_accessor :crop_h, :crop_w, :crop_x, :crop_y, :copyright, :watermark_path
9 |
10 | def initialize(file, options = {}, attachment = nil)
11 | @crop_h, @crop_w, @crop_x, @crop_y = options[:crop_h], options[:crop_w], options[:crop_x], options[:crop_y]
12 |
13 | # copyright extraction
14 | if options[:geometry] =~ /©/ || options[:copyright]
15 | options[:geometry], *@copyright = options[:geometry].split("©", -1)
16 |
17 | @copyright = (
18 | (options[:copyright] != true && options[:copyright]) ||
19 | @copyright.join("©").nie ||
20 | file.instance.respond_to?(:copyright) && file.instance.copyright.nie ||
21 | file.instance.papermill_options[:copyright].nie)
22 |
23 | if @copyright
24 | @copyright = (options[:copyright_text_transform] || file.instance.papermill_options[:copyright_text_transform]).try(:call, @copyright) || @copyright
25 | end
26 | end
27 |
28 | # watermark extraction
29 | if options[:watermark] || options[:geometry] =~ /\-wm/
30 | options[:geometry] = options[:geometry].chomp("-wm")
31 | @watermark_path = options[:watermark].is_a?(String) && options[:watermark] || file.instance.papermill_options[:watermark]
32 | @watermark_path = "#{RAILS_ROOT}/public" + @watermark_path if @watermark_path.starts_with?("/")
33 | end
34 |
35 |
36 | if options[:geometry] =~ /#.+/
37 | # let's parse :
38 | # x#x::
39 | # x#x
40 | # x#:
41 |
42 | options[:geometry], manual_crop = options[:geometry].split("#")
43 | crop_dimensions, @crop_x, @crop_y = manual_crop.split("+")
44 |
45 | if crop_dimensions =~ /x/
46 | @crop_w, @crop_h = crop_dimensions.split("x")
47 | else
48 | @crop_x, @crop_y = crop_dimensions, @crop_x
49 | end
50 |
51 | options[:geometry] = (options[:geometry].nie || "#{@crop_x}x#{@crop_y}") + "#"
52 |
53 | unless @crop_w && @crop_h
54 | @target_geometry = Geometry.parse(options[:geometry])
55 | @crop_w ||= @target_geometry.try(:width).to_i
56 | @crop_h ||= @target_geometry.try(:height).to_i
57 | end
58 | end
59 | super
60 | end
61 |
62 | def transformation_command
63 | " -limit memory 1 -limit map 1 #{(crop_command ? super.sub(/ -crop \S+/, crop_command) : super)} #{copyright_command} #{watermark_command}".sub(%{-resize "0x" }, "")
64 | end
65 |
66 | def copyright_command
67 | (options[:copyright_im_command] || @file.instance.papermill_options[:copyright_im_command]).gsub(/%s/, @copyright.gsub(/'/, %{'"'"'}).sub("-slash-", "/").sub("-backslash-", %{\\\\\\})) if @copyright
68 | end
69 |
70 | def watermark_command
71 | (options[:watermark_im_command] || @file.instance.papermill_options[:watermark_im_command]).gsub(/%s/, @watermark_path) if @watermark_path
72 | end
73 |
74 | def crop_command
75 | if @crop_h || @crop_x
76 | " -crop '%dx%d+%d+%d'" % [ @crop_w, @crop_h, @crop_x, @crop_y ].map(&:to_i)
77 | end
78 | end
79 | end
80 |
81 |
82 | end
83 |
--------------------------------------------------------------------------------
/demo.txt:
--------------------------------------------------------------------------------
1 | gem 'papermill'
2 |
3 | generate :papermill_table, "PapermillMigration"
4 | generate :papermill_assets
5 | generate :papermill_initializer
6 | generate :scaffold, "article title:string"
7 | rake "db:migrate"
8 |
9 | file "app/models/article.rb", <<-END
10 | class Article < ActiveRecord::Base
11 | validates_presence_of :title
12 | papermill :image_gallery, :thumbnail => {:width => 75, :height => 100}, :images_only => true # only images, custom preview thumbnail size
13 | papermill :thumbnail, :swfupload => { :file_types => "*.jpg;*.jpeg" } # jpg only, cherry picking
14 | papermill :my_assets
15 | papermill :my_other_asset
16 | end
17 | END
18 |
19 |
20 | file "app/views/articles/edit.html.erb", <<-END
21 | Editing article
22 | <%= render :partial => "form" %>
23 | <%= link_to 'Show', @article %> |
24 | <%= link_to 'Back', articles_path %>
25 | END
26 |
27 | file "app/views/articles/new.html.erb", <<-END
28 | New article
29 | <%= render :partial => "form" %>
30 | <%= link_to 'Back', articles_path %>
31 | END
32 |
33 | file "app/views/articles/_form.html.erb", <<-END
34 | <% form_for(@article) do |f| %>
35 | <%= f.error_messages %>
36 | <%= f.label :title %>
37 | <%= f.text_field :title %>
38 | <%= f.label :image_gallery %>
39 | <%= f.images_upload(:image_gallery) %>
40 | <%= f.label :thumbnail %>
41 | <%= f.image_upload(:thumbnail) %>
42 | <%= f.label :my_assets %>
43 | <%= f.assets_upload(:my_assets) %>
44 | <%= f.label :my_other_asset %>
45 | <%= f.asset_upload(:my_other_asset) %>
46 | <%= f.submit 'Send' %>
47 | <% end %>
48 | END
49 |
50 | file "app/views/articles/show.html.erb", <<-END
51 |
52 | Title:
53 | <%=h @article.title %>
54 |
55 |
56 | @article.image_gallery.each :
57 |
58 | <% @article.image_gallery.each do |image| %>
59 | <%= link_to(image_tag(image.url("100x100#")), image.url) %>
60 | <% end %>
61 |
62 |
63 | @article.thumbnail.first :
64 |
65 | <% image = @article.thumbnail.first %>
66 | <%= link_to(image_tag(image.url("100x100#")), image.url) if image %>
67 |
68 |
69 | @article.my_assets.each :
70 |
71 |
72 | <% @article.my_assets.each do |asset| %>
73 | <%= link_to asset.name, asset.url %>
74 | <% end %>
75 |
76 |
77 |
78 | @article.my_other_asset.first :
79 |
80 | <% asset = @article.my_other_asset.first %>
81 | <%= link_to(asset.name, asset.url) if asset %>
82 |
83 |
84 | <%= link_to 'Edit', edit_article_path(@article) %> |
85 | <%= link_to 'Back', articles_path %>
86 | END
87 |
88 |
89 | file "app/views/layouts/application.html.erb", <<-END
90 |
92 |
93 |
94 |
95 |
96 | Papermill Demo
97 | <%= stylesheet_link_tag 'scaffold' %>
98 | <%= papermill_stylesheet_tag %>
99 |
100 |
101 | <%= yield %>
102 | <%= papermill_javascript_tag :with_jquery => true %>
103 |
104 |
105 | END
106 |
107 | run "rm app/views/layouts/articles.html.erb"
108 | run "rm public/index.html"
109 | route "map.root :controller => 'articles'"
110 |
--------------------------------------------------------------------------------
/test/papermill_test.rb:
--------------------------------------------------------------------------------
1 | require 'test/unit'
2 |
3 | require 'rubygems'
4 | gem 'rails'
5 | require 'active_record'
6 | require 'action_controller'
7 | require 'action_view'
8 | require 'active_support'
9 | ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/info.log")
10 | RAILS_ROOT = File.join( File.dirname(__FILE__), "../../../.." )
11 |
12 | module Papermill
13 | OPTIONS = {
14 | :aliases => {
15 | 'tall' => :"1000x1000",
16 | :small => "100x100",
17 | :hashed_style => {:geometry => "100x100"}
18 | }
19 | }
20 | end
21 |
22 | require File.join(File.dirname(__FILE__), '../init.rb')
23 |
24 | ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => "test.sqlite3")
25 | ActiveRecord::Schema.define(:version => 1) do
26 | create_table :papermill_assets, :force => true do |t|
27 | t.string :title, :file_file_name, :file_content_type, :assetable_type, :assetable_key, :type
28 | t.integer :file_file_size, :position, :assetable_id
29 | t.timestamps
30 | end
31 |
32 | create_table :articles, :force => true do |t|
33 | t.string :type
34 | end
35 | end
36 |
37 | class MyAsset < PapermillAsset
38 | end
39 |
40 | class Article < ActiveRecord::Base
41 | def self.table_name
42 | :articles
43 | end
44 | papermill :asset1
45 | papermill :my_assets, :class_name => "MyAsset"
46 | end
47 |
48 | class PapermillTest < Test::Unit::TestCase
49 | @article = Article.create!
50 | @decoy_article = Article.create!
51 | @file = File.new(File.join(File.dirname(__FILE__), "fixtures", "5k.png"), 'rb')
52 | PapermillAsset.create!(:file => @file, :assetable => @article, :assetable_key => "asset1", :position => 2)
53 | PapermillAsset.create!(:file => @file, :assetable => @article, :assetable_key => "asset1", :position => 1)
54 | MyAsset.create!(:file => @file, :assetable => @article, :assetable_key => "my_assets")
55 | MyAsset.create!(:file => @file, :assetable => @article, :assetable_key => "my_assets")
56 | MyAsset.create!(:file => @file, :assetable => @decoy_article, :assetable_key => "my_assets")
57 |
58 | def initialize(*args)
59 | super
60 | @file = File.new(File.join(File.dirname(__FILE__), "fixtures", "5k.png"), 'rb')
61 | @article = Article.find 1
62 | @decoy_article = Article.find 2
63 | @asset1 = PapermillAsset.find 1
64 | @asset2 = PapermillAsset.find 2
65 | @asset3 = PapermillAsset.find 3
66 | @asset4 = PapermillAsset.find 4
67 | @asset5 = PapermillAsset.find 5
68 | end
69 |
70 | def test_namedscopes_for_specific_associations
71 | assert_equal @article.my_assets.map(&:id), [3,4]
72 | end
73 |
74 | def test_namedscope_for_global_associations
75 | assert_equal @article.asset1.map(&:id), [2,1]
76 | end
77 |
78 | def test_id_partition
79 | assert_equal @asset1.id_partition, "000/000/001"
80 | end
81 |
82 | def test_name
83 | assert_equal @asset1.name, "5k.png"
84 | end
85 |
86 | def test_width
87 | assert_equal @asset1.width, 434.0
88 | end
89 |
90 | def test_height
91 | assert_equal @asset1.height, 66.0
92 | end
93 |
94 | def test_size
95 | assert_equal @asset1.size, 4456
96 | end
97 |
98 | def test_content_type
99 | assert_equal @asset1.content_type, "image/png"
100 | end
101 |
102 | def test_is_image
103 | assert @asset1.image?
104 | end
105 |
106 | def test_compute_style
107 | assert_equal PapermillAsset.compute_style(:tall), :geometry => "1000x1000"
108 | assert_equal PapermillAsset.compute_style("tall"), :geometry => "1000x1000"
109 | assert_equal PapermillAsset.compute_style(:small), :geometry => "100x100"
110 | assert_equal PapermillAsset.compute_style("small"), :geometry => "100x100"
111 | assert_equal PapermillAsset.compute_style("hashed_style"), :geometry => "100x100"
112 | assert_equal PapermillAsset.compute_style("100x100"), :geometry => "100x100"
113 | assert_equal PapermillAsset.compute_style(:"100x100"), :geometry => "100x100"
114 | Papermill::options[:alias_only] = true
115 | assert_equal PapermillAsset.compute_style("100x100"), false
116 | end
117 | end
118 |
--------------------------------------------------------------------------------
/app/controllers/papermill_controller.rb:
--------------------------------------------------------------------------------
1 | class PapermillController < ApplicationController
2 | unloadable
3 | prepend_before_filter :load_asset, :only => [ "show", "destroy", "update", "edit", "crop" ]
4 | skip_before_filter :verify_authenticity_token, :only => [:create] # not needed (Flash same origin policy)
5 |
6 | def show
7 | # first escaping is done by rails prior to route recognition, need to do a second one on MSWIN systems to get original one.
8 | params[:style] = CGI::unescape(params[:style]) if Papermill::MSWIN
9 | if @asset.has_valid_url_key?(params[:url_key], params[:style]) && @asset.create_thumb_file(params[:style])
10 | redirect_to @asset.url(params[:style])
11 | else
12 | render :nothing => true, :status => 404
13 | end
14 | end
15 |
16 | def create
17 | @asset = params[:asset_class].constantize.new(params.reject{|k, v| !(PapermillAsset.columns.map(&:name)+["Filedata", "Filename"]).include?(k)})
18 | if @asset.save
19 | output = render_to_string(:partial => "papermill/asset", :object => @asset, :locals => { :gallery => params[:gallery], :thumbnail_style => params[:thumbnail_style], :targetted_size => params[:targetted_size], :field_name => params[:field_name], :field_id => params[:field_id] })
20 | render :update do |page|
21 | page << %{ jQuery('##{params[:Fileid]}').replaceWith('#{escape_javascript output}') }
22 | page << %{ jQuery('##{params[:Oldfileid]}').remove() } if params[:Oldfileid]
23 | end
24 | else
25 | render :update do |page|
26 | page << %{ notify('#{@asset.name}', '#{escape_javascript @asset.errors.full_messages.join(" ")}', 'error') }
27 | page << %{ jQuery('##{params[:Fileid]}').remove() }
28 | page << %{ jQuery('##{params[:Oldfileid]}').show() } if params[:Oldfileid]
29 | end
30 | end
31 | end
32 |
33 | def edit
34 | render :action => "edit", :layout => false
35 | end
36 |
37 | def crop
38 | render :action => "crop", :layout => false
39 | end
40 |
41 | def update
42 | @asset.create_thumb_file(params[:target], params[:papermill_asset].merge({ :geometry => "#{params[:target]}#" })) if params[:target]
43 | render :update do |page|
44 | if @asset.update_attributes(params[:papermill_asset])
45 | page << %{ notify('#{@asset.name}', '#{ escape_javascript t("papermill.updated", :resource => @asset.name)}', 'notice'); close_popup(self); }
46 | else
47 | page << %{ jQuery('#error').html('#{ escape_javascript @asset.errors.full_messages.join(" ") }'); jQuery('#error').show(); }
48 | end
49 | end
50 | end
51 |
52 | def add_list
53 | @assets = PapermillAsset.find(params[:ids]).sort_by{|asset|params[:ids].index(asset.id.to_s)} unless params[:ids].blank?
54 | output = render_to_string(:partial => "papermill/asset", :collection => (@assets || []), :locals => { :gallery => params[:gallery], :thumbnail_style => params[:thumbnail_style], :targetted_size => params[:targetted_size], :field_name => params[:field_name], :field_id => params[:field_id] })
55 | render :update do |page|
56 | page << %{ close_popup(); jQuery('##{params[:field_id]}').html('#{escape_javascript output}'); }
57 | end
58 | end
59 |
60 | def browser
61 | ids_list = params[params[:field_id] + "_papermill_asset"]
62 | @selected_assets = ids_list.blank? ? [] : params[:asset_class].constantize.find(ids_list).sort_by{|asset| ids_list.index(asset.id.to_s)}
63 | @other_assets = params[:asset_class].constantize.all - @selected_assets
64 | render :action => "browser", :layout => false
65 | end
66 |
67 | def mass_edit
68 | @assets = (params[(params[:list_id] + "_papermill_asset").to_sym] || []).map{ |id| PapermillAsset.find(id, :include => "assetable") }
69 | @assets.each { |asset| asset.update_attribute(params[:attribute], params[:value]) }
70 | render :update do |page|
71 | page << %{ notify("", "#{ escape_javascript t("papermill.updated", :resource => @assets.map(&:name).to_sentence) }", "notice"); } unless @assets.blank?
72 | end
73 | end
74 |
75 | private
76 |
77 | def load_asset
78 | @asset = PapermillAsset.find(params[:id] || (params[:id0] + params[:id1] + params[:id2]).to_i)
79 | end
80 | end
--------------------------------------------------------------------------------
/lib/papermill/papermill.rb:
--------------------------------------------------------------------------------
1 | module Papermill
2 | MSWIN = (Config::CONFIG['host_os'] =~ /mswin|mingw/)
3 |
4 | def self.options
5 | @options ||= BASE_OPTIONS.deep_merge(defined?(OPTIONS) ? OPTIONS : {})
6 | end
7 |
8 | def self.compute_paperclip_path
9 | path = []
10 | path << (options[:use_id_partition] ? ":id_partition" : ":id")
11 | path << (":url_key" if options[:use_url_key])
12 | path << ":style"
13 | path << ":basename.:extension"
14 | path.compact.join("/")
15 | end
16 |
17 | def self.included(base)
18 | base.extend(ClassMethods)
19 | base.class_eval do
20 | def papermill(key, through = ((po = self.class.papermill_options) && (po[key.to_sym] || po[:default]) || Papermill::options)[:through])
21 | PapermillAsset.papermill(self.class.base_class.name, self.id, key.to_s, through)
22 | end
23 |
24 | def respond_to_with_papermill?(method, *args, &block)
25 | respond_to_without_papermill?(method, *args, &block) || (method.to_s =~ /^papermill_.+_ids=$/) == 0
26 | end
27 |
28 | def method_missing_with_papermill(method, *args, &block)
29 | if method.to_s =~ /^papermill_.+_ids=$/
30 | self.class.papermill(method.to_s[10..-6])
31 | self.send(method, *args, &block)
32 | else
33 | method_missing_without_papermill(method, *args, &block)
34 | end
35 | end
36 | end
37 | base.send :alias_method_chain, :method_missing, :papermill
38 | base.send :alias_method_chain, :respond_to?, :papermill
39 | ActionController::Dispatcher.middleware.insert_before(ActionController::Base.session_store, FlashSessionCookieMiddleware, ActionController::Base.session_options[:key]) unless ActionController::Dispatcher.middleware.include?(FlashSessionCookieMiddleware)
40 | end
41 |
42 | module ClassMethods
43 | attr_reader :papermill_options
44 |
45 | def inherited(subclass)
46 | subclass.instance_variable_set("@papermill_options", @papermill_options)
47 | super
48 | end
49 |
50 | def papermill(assoc_key, assoc_options = (@papermill_options && @papermill_options[:default] || {}))
51 | return if @papermill_options && @papermill_options[assoc_key.to_sym] # already defined
52 | raise PapermillException.new("Can't use '#{assoc_key}' association : #{self.name} instances already responds to it !") if self.new.respond_to?(assoc_key)
53 | (@papermill_options ||= {}).merge!( { assoc_key.to_sym => Papermill::options.deep_merge(assoc_options) } )
54 | return if assoc_key.to_sym == :default
55 | unless papermill_options[assoc_key.to_sym][:through]
56 | self.class_eval %{
57 | has_many :#{assoc_key}, :as => "assetable", :dependent => :destroy, :order => :position, :class_name => "PapermillAsset", :conditions => {:assetable_key => '#{assoc_key}'}, :before_add => Proc.new{|a, asset| asset.assetable_key = '#{assoc_key}'}
58 | def papermill_#{assoc_key}_ids=(ids)
59 | unless (assets_ids = ids.map(&:to_i).select{|i|i>0}) == self.#{assoc_key}.map(&:id)
60 | assets = PapermillAsset.find(assets_ids)
61 | self.#{assoc_key} = assets_ids.map{|asset_id| assets.select{|asset|asset.id==asset_id}.first}
62 | PapermillAsset.update_all("position = CASE id " + assets_ids.map_with_index{|asset_id, index| " WHEN " + asset_id.to_s + " THEN " + (index+1).to_s }.join + " END",
63 | :id => assets_ids) unless assets_ids.empty?
64 | end
65 | end
66 | }
67 | else
68 | self.class_eval %{
69 | has_many(:#{assoc_key}_associations, :as => "assetable", :class_name => "PapermillAssociation", :include => :papermill_asset, :dependent => :delete_all, :order => :position, :conditions => {:assetable_key => '#{assoc_key}'}, :before_add => Proc.new{|a, assoc| assoc.assetable_key = '#{assoc_key}'})
70 | has_many(:#{assoc_key}, :through => :#{assoc_key}_associations, :source => :papermill_asset)
71 | def papermill_#{assoc_key}_ids=(ids)
72 | unless (assets_ids = ids.map(&:to_i).select{|i|i>0}) == self.#{assoc_key}_associations.map(&:papermill_asset_id)
73 | self.#{assoc_key}_associations.delete_all
74 | self.#{assoc_key}_associations = assets_ids.map_with_index do |asset_id, index|
75 | PapermillAssociation.new(:papermill_asset_id => asset_id, :position => (index+1))
76 | end
77 | end
78 | end
79 | }
80 | end
81 | end
82 | end
83 | end
84 |
--------------------------------------------------------------------------------
/public/jgrowl/jquery.jgrowl.css:
--------------------------------------------------------------------------------
1 | div.jGrowl {
2 | padding: 10px;
3 | z-index: 9999;
4 | color: #fff;
5 | font-size: 12px;
6 | }
7 |
8 | /** Special IE6 Style Positioning **/
9 | div.ie6 {
10 | position: absolute;
11 | }
12 |
13 | div.ie6.top-right {
14 | right: auto;
15 | bottom: auto;
16 | left: expression( ( 0 - jGrowl.offsetWidth + ( document.documentElement.clientWidth ? document.documentElement.clientWidth : document.body.clientWidth ) + ( ignoreMe2 = document.documentElement.scrollLeft ? document.documentElement.scrollLeft : document.body.scrollLeft ) ) + 'px' );
17 | top: expression( ( 0 + ( ignoreMe = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop ) ) + 'px' );
18 | }
19 |
20 | div.ie6.top-left {
21 | left: expression( ( 0 + ( ignoreMe2 = document.documentElement.scrollLeft ? document.documentElement.scrollLeft : document.body.scrollLeft ) ) + 'px' );
22 | top: expression( ( 0 + ( ignoreMe = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop ) ) + 'px' );
23 | }
24 |
25 | div.ie6.bottom-right {
26 | left: expression( ( 0 - jGrowl.offsetWidth + ( document.documentElement.clientWidth ? document.documentElement.clientWidth : document.body.clientWidth ) + ( ignoreMe2 = document.documentElement.scrollLeft ? document.documentElement.scrollLeft : document.body.scrollLeft ) ) + 'px' );
27 | top: expression( ( 0 - jGrowl.offsetHeight + ( document.documentElement.clientHeight ? document.documentElement.clientHeight : document.body.clientHeight ) + ( ignoreMe = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop ) ) + 'px' );
28 | }
29 |
30 | div.ie6.bottom-left {
31 | left: expression( ( 0 + ( ignoreMe2 = document.documentElement.scrollLeft ? document.documentElement.scrollLeft : document.body.scrollLeft ) ) + 'px' );
32 | top: expression( ( 0 - jGrowl.offsetHeight + ( document.documentElement.clientHeight ? document.documentElement.clientHeight : document.body.clientHeight ) + ( ignoreMe = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop ) ) + 'px' );
33 | }
34 |
35 | div.ie6.center {
36 | left: expression( ( 0 + ( ignoreMe2 = document.documentElement.scrollLeft ? document.documentElement.scrollLeft : document.body.scrollLeft ) ) + 'px' );
37 | top: expression( ( 0 + ( ignoreMe = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop ) ) + 'px' );
38 | width: 100%;
39 | }
40 |
41 | /** Normal Style Positions **/
42 | div.jGrowl {
43 | position: absolute;
44 | }
45 |
46 | body > div.jGrowl {
47 | position: fixed;
48 | }
49 |
50 | div.jGrowl.top-left {
51 | left: 0px;
52 | top: 0px;
53 | }
54 |
55 | div.jGrowl.top-right {
56 | right: 0px;
57 | top: 0px;
58 | }
59 |
60 | div.jGrowl.bottom-left {
61 | left: 0px;
62 | bottom: 0px;
63 | }
64 |
65 | div.jGrowl.bottom-right {
66 | right: 0px;
67 | bottom: 0px;
68 | }
69 |
70 | div.jGrowl.center {
71 | top: 0px;
72 | width: 50%;
73 | left: 25%;
74 | }
75 |
76 | /** Cross Browser Styling **/
77 | div.center div.jGrowl-notification, div.center div.jGrowl-closer {
78 | margin-left: auto;
79 | margin-right: auto;
80 | }
81 |
82 | div.jGrowl div.jGrowl-notification, div.jGrowl div.jGrowl-closer {
83 | background-color: #000;
84 | opacity: .85;
85 | -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=85)";
86 | filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=85);
87 | zoom: 1;
88 | width: 235px;
89 | padding: 10px;
90 | margin-top: 5px;
91 | margin-bottom: 5px;
92 | font-family: Tahoma, Arial, Helvetica, sans-serif;
93 | font-size: 1em;
94 | text-align: left;
95 | display: none;
96 | -moz-border-radius: 5px;
97 | -webkit-border-radius: 5px;
98 | }
99 |
100 | div.jGrowl div.jGrowl-notification {
101 | min-height: 40px;
102 | }
103 |
104 | div.jGrowl div.jGrowl-notification div.jGrowl-header {
105 | font-weight: bold;
106 | font-size: .85em;
107 | }
108 |
109 | div.jGrowl div.jGrowl-notification div.jGrowl-close {
110 | z-index: 99;
111 | float: right;
112 | font-weight: bold;
113 | font-size: 1em;
114 | cursor: pointer;
115 | }
116 |
117 | div.jGrowl div.jGrowl-closer {
118 | padding-top: 4px;
119 | padding-bottom: 4px;
120 | cursor: pointer;
121 | font-size: .9em;
122 | font-weight: bold;
123 | text-align: center;
124 | }
125 |
126 | /** Hide jGrowl when printing **/
127 | @media print {
128 | div.jGrowl {
129 | display: none;
130 | }
131 | }
--------------------------------------------------------------------------------
/public/jgrowl/jquery.jgrowl_minimized.js:
--------------------------------------------------------------------------------
1 | (function($){$.jGrowl=function(m,o){if($('#jGrowl').size()==0)
2 | $('
').addClass($.jGrowl.defaults.position).appendTo('body');$('#jGrowl').jGrowl(m,o);};$.fn.jGrowl=function(m,o){if($.isFunction(this.each)){var args=arguments;return this.each(function(){var self=this;if($(this).data('jGrowl.instance')==undefined){$(this).data('jGrowl.instance',$.extend(new $.fn.jGrowl(),{notifications:[],element:null,interval:null}));$(this).data('jGrowl.instance').startup(this);}
3 | if($.isFunction($(this).data('jGrowl.instance')[m])){$(this).data('jGrowl.instance')[m].apply($(this).data('jGrowl.instance'),$.makeArray(args).slice(1));}else{$(this).data('jGrowl.instance').create(m,o);}});};};$.extend($.fn.jGrowl.prototype,{defaults:{pool:0,header:'',group:'',sticky:false,position:'top-right',glue:'after',theme:'default',corners:'10px',check:250,life:3000,speed:'normal',easing:'swing',closer:true,closeTemplate:'×',closerTemplate:'[ close all ]
',log:function(e,m,o){},beforeOpen:function(e,m,o){},open:function(e,m,o){},beforeClose:function(e,m,o){},close:function(e,m,o){},animateOpen:{opacity:'show'},animateClose:{opacity:'hide'}},notifications:[],element:null,interval:null,create:function(message,o){var o=$.extend({},this.defaults,o);this.notifications.push({message:message,options:o});o.log.apply(this.element,[this.element,message,o]);},render:function(notification){var self=this;var message=notification.message;var o=notification.options;var notification=$(''+'
'+o.closeTemplate+'
'+''+'
'+message+'
').data("jGrowl",o).addClass(o.theme).children('div.close').bind("click.jGrowl",function(){$(this).parent().trigger('jGrowl.close');}).parent();$(notification).bind("mouseover.jGrowl",function(){$('div.jGrowl-notification',self.element).data("jGrowl.pause",true);}).bind("mouseout.jGrowl",function(){$('div.jGrowl-notification',self.element).data("jGrowl.pause",false);}).bind('jGrowl.beforeOpen',function(){if(o.beforeOpen.apply(notification,[notification,message,o,self.element])!=false){$(this).trigger('jGrowl.open');}}).bind('jGrowl.open',function(){if(o.open.apply(notification,[notification,message,o,self.element])!=false){if(o.glue=='after'){$('div.jGrowl-notification:last',self.element).after(notification);}else{$('div.jGrowl-notification:first',self.element).before(notification);}
5 | $(this).animate(o.animateOpen,o.speed,o.easing,function(){if($.browser.msie&&(parseInt($(this).css('opacity'),10)===1||parseInt($(this).css('opacity'),10)===0))
6 | this.style.removeAttribute('filter');$(this).data("jGrowl").created=new Date();});}}).bind('jGrowl.beforeClose',function(){if(o.beforeClose.apply(notification,[notification,message,o,self.element])!=false)
7 | $(this).trigger('jGrowl.close');}).bind('jGrowl.close',function(){$(this).data('jGrowl.pause',true);$(this).animate(o.animateClose,o.speed,o.easing,function(){$(this).remove();var close=o.close.apply(notification,[notification,message,o,self.element]);if($.isFunction(close))
8 | close.apply(notification,[notification,message,o,self.element]);});}).trigger('jGrowl.beforeOpen');if($.fn.corner!=undefined)$(notification).corner(o.corners);if($('div.jGrowl-notification:parent',self.element).size()>1&&$('div.jGrowl-closer',self.element).size()==0&&this.defaults.closer!=false){$(this.defaults.closerTemplate).addClass('jGrowl-closer ui-state-highlight ui-corner-all').addClass(this.defaults.theme).appendTo(self.element).animate(this.defaults.animateOpen,this.defaults.speed,this.defaults.easing).bind("click.jGrowl",function(){$(this).siblings().children('div.close').trigger("click.jGrowl");if($.isFunction(self.defaults.closer)){self.defaults.closer.apply($(this).parent()[0],[$(this).parent()[0]]);}});};},update:function(){$(this.element).find('div.jGrowl-notification:parent').each(function(){if($(this).data("jGrowl")!=undefined&&$(this).data("jGrowl").created!=undefined&&($(this).data("jGrowl").created.getTime()+$(this).data("jGrowl").life)<(new Date()).getTime()&&$(this).data("jGrowl").sticky!=true&&($(this).data("jGrowl.pause")==undefined||$(this).data("jGrowl.pause")!=true)){$(this).trigger('jGrowl.beforeClose');}});if(this.notifications.length>0&&(this.defaults.pool==0||$(this.element).find('div.jGrowl-notification:parent').size()');this.interval=setInterval(function(){$(e).data('jGrowl.instance').update();},this.defaults.check);if($.browser.msie&&parseInt($.browser.version)<7&&!window["XMLHttpRequest"]){$(this.element).addClass('ie6');}},shutdown:function(){$(this.element).removeClass('jGrowl').find('div.jGrowl-notification').remove();clearInterval(this.interval);},close:function(){$(this.element).find('div.jGrowl-notification').each(function(){$(this).trigger('jGrowl.beforeClose');});}});$.jGrowl.defaults=$.fn.jGrowl.prototype.defaults;})(jQuery);
10 |
--------------------------------------------------------------------------------
/public/papermill/papermill.js:
--------------------------------------------------------------------------------
1 | /*
2 | If you have your own popup solution, override popup and close_popup in your application.js
3 |
4 | * e.g. facebox :
5 | */
6 | popup = function(url) {
7 | jQuery.facebox(function() {
8 | jQuery.get(url, function(data) {
9 | jQuery.facebox(data);
10 | })
11 | })
12 | return false;
13 | };
14 |
15 | close_popup = function(source) {
16 | jQuery(document).trigger('close.facebox');
17 | };
18 |
19 | jQuery(document).ajaxError(function(){
20 | switch (arguments[1].status) {
21 | case 500:
22 | notify(arguments[1].status + ": " + arguments[1].statusText, "Something bad happened on the server side. An email has been sent, we will have a look at it shortly", "error"); break;
23 | case 404:
24 | notify(arguments[1].status + ": " + arguments[1].statusText, "Resource not found on the server. Try to refresh the page and check if the resource still exists", "warning"); break;
25 | case 401:
26 | notify(arguments[1].status + ": " + arguments[1].statusText, "You are not allowed to access/modify this resource. Contact the website administrator", "warning"); break;
27 | case 0:
28 | notify(arguments[1].status + ": " + arguments[1].statusText, "Cannot access distant server. Are you connected?", "warning"); break;
29 | default:
30 | notify(arguments[1].status + ": " + arguments[1].statusText, "Something happened, code=[" + arguments[1].status + "], please see this reference for further details", "error");
31 | }
32 | });
33 |
34 | /*
35 | If you have your own notification solution, override notify
36 |
37 | * e.g. jGrowl :
38 | */
39 |
40 | notify = function(title, message, type) {
41 | if(type == "notice") { jQuery.jGrowl(message, { header: title, life: 4000, theme: type }) };
42 | if(type == "warning") { jQuery.jGrowl(message, { header: title, life: 15000, theme: type }) };
43 | if(type == "error") { jQuery.jGrowl(message, { header: title, sticky: true, theme: type }) };
44 | };
45 |
46 | var Papermill = {
47 | files_queued: 0,
48 | file_dialog_complete: function(num_selected, num_queued)
49 | {
50 | // SwfUpload doesn't order uploads out of the box...
51 | this.sorted_queue = [];
52 | this.index = 0;
53 | if (num_queued > 0) {
54 | file_queue = [];
55 | global_index = 0;
56 | index = 0;
57 | do {
58 | file = this.callFlash("GetFileByIndex", [global_index]);
59 | if(file != null && file.filestatus == -1) {
60 | file_queue[index] = file;
61 | index++;
62 | }
63 | global_index++;
64 | } while (file != null);
65 | this.sorted_queue = file_queue.sort(function(a,b){
66 | if(b.name < a.name) { return (1) } else { return (-1) }
67 | });
68 | var self = this;
69 | jQuery(this.sorted_queue).each( function(index, file) {
70 | div = jQuery('
').attr({ 'id': file.id, 'class': 'swfupload asset' });
71 | div.append(jQuery(' ').attr('class', 'name').html(file.name.substring(0, 10) + '...'));
72 | div.append(jQuery(' ').attr('class', 'progress').append(' '));
73 | if(self.settings.file_queue_limit == 1) {
74 | old_asset = jQuery("#" + self.settings.upload_id).children()[0];
75 | if(old_asset) {
76 | jQuery(old_asset).hide();
77 | }
78 | }
79 | jQuery("#" + self.settings.upload_id).append(div);
80 | });
81 | this.startUpload(this.sorted_queue[this.index++].id);
82 | }
83 | },
84 | upload_start: function(file)
85 | {
86 | this.addFileParam(file.id, "Fileid", file.id);
87 | if(this.settings.file_queue_limit == 1) {
88 | old_asset = jQuery("#" + this.settings.upload_id).children()[0];
89 | if (old_asset && old_asset.id != file.id) {
90 | this.addFileParam(file.id, "Oldfileid", old_asset.id);
91 | }
92 | }
93 | },
94 | upload_progress: function(file, bytes, total)
95 | {
96 | percent = Math.ceil((bytes / total) * 100);
97 | jQuery('#' + file.id + ' .progress span').width(percent + '%');
98 | },
99 | upload_error: function(file, errorCode, message) {
100 | switch (errorCode) {
101 | case SWFUpload.UPLOAD_ERROR.UPLOAD_LIMIT_EXCEEDED:
102 | notify("Error", "Too many files selected", "error"); break;
103 | default:
104 | notify("Error", "An error occurred while sending " + file.name, "error");
105 | }
106 | jQuery("#"+file.id).remove();
107 | },
108 | upload_success: function(file, data)
109 | {
110 | eval(data);
111 | },
112 | upload_complete: function(file)
113 | {
114 | Papermill.files_queued -= 1;
115 | if(this.sorted_queue[this.index]) {
116 | this.startUpload(this.sorted_queue[this.index++].id);
117 | }
118 | },
119 | file_queue_error: function(file, errorCode, message) {
120 | switch (errorCode) {
121 | case SWFUpload.QUEUE_ERROR.QUEUE_LIMIT_EXCEEDED:
122 | notify("Error", "Too many files selected", "error"); break;
123 | case SWFUpload.QUEUE_ERROR.FILE_EXCEEDS_SIZE_LIMIT:
124 | notify("Error", file.name + " is too big", "error"); break;
125 | case SWFUpload.QUEUE_ERROR.ZERO_BYTE_FILE:
126 | notify("Error", file.name + " is empty", "error"); break;
127 | case SWFUpload.QUEUE_ERROR.INVALID_FILETYPE:
128 | notify("Error", file.name + " is not an allowed file type", "error"); break;
129 | default:
130 | notify("Error", "An error occurred while sending " + file.name, "error");
131 | }
132 | jQuery("#"+file.id).remove();
133 | },
134 | modify_all: function(papermill_id) {
135 | container = jQuery("#" + papermill_id)[0];
136 | attribute_name = jQuery("#batch_" + papermill_id)[0].value;
137 | attribute_wording = jQuery("#batch_" + papermill_id + " option:selected").text();
138 | value = prompt(attribute_wording + ":");
139 | if(value != null) {
140 | jQuery.ajax({async:true, data:jQuery(container).sortable('serialize') + "&list_id=" + container.id, dataType:'script', type:'post', url:'/papermill/mass_edit?attribute=' + attribute_name + "&value=" + value});
141 | }
142 | }
143 | };
144 |
145 |
--------------------------------------------------------------------------------
/lib/papermill/papermill_asset.rb:
--------------------------------------------------------------------------------
1 | class PapermillAsset < ActiveRecord::Base
2 | before_destroy :destroy_files
3 |
4 | has_attached_file :file,
5 | :processors => [:papermill_paperclip_processor],
6 | :url => "#{Papermill::options[:papermill_url_prefix]}/#{Papermill::compute_paperclip_path.gsub(':style', ':escape_style_in_url')}",
7 | :path => "#{Papermill::options[:papermill_path_prefix]}/#{Papermill::compute_paperclip_path.gsub(':style', ':escape_style_in_path')}"
8 |
9 | before_post_process :set_file_name
10 |
11 | validates_attachment_presence :file
12 |
13 | belongs_to :assetable, :polymorphic => true
14 | has_many :papermill_associations, :dependent => :delete_all
15 |
16 | named_scope :papermill, lambda { |assetable_type, assetable_id, assetable_key, through|
17 | through ?
18 | { :joins => "INNER JOIN papermill_associations ON papermill_assets.id = papermill_associations.papermill_asset_id \
19 | AND papermill_associations.assetable_type = #{connection.quote assetable_type} \
20 | AND papermill_associations.assetable_id = #{assetable_id.to_i} \
21 | AND papermill_associations.assetable_key = #{connection.quote assetable_key}",
22 | :order => "papermill_associations.position" } :
23 | { :conditions => {
24 | :assetable_type => assetable_type,
25 | :assetable_id => assetable_id,
26 | :assetable_key => assetable_key.to_s },
27 | :order => "papermill_assets.position" }
28 | }
29 |
30 | def assetable_type=(sType)
31 | super(sType.to_s.classify.constantize.base_class.to_s)
32 | end
33 |
34 | Paperclip.interpolates :url_key do |attachment, style|
35 | attachment.instance.compute_url_key((style || "original").to_s)
36 | end
37 |
38 | Paperclip.interpolates :escape_style_in_url do |attachment, style|
39 | # double escaping needed for windows (complains about '< > " | / \' ), to match escaped filesystem from front webserver pov
40 | s = (style || "original").to_s
41 | Papermill::MSWIN ? CGI::escape(CGI::escape(s)) : CGI::escape(s)
42 | end
43 |
44 | Paperclip.interpolates :escape_style_in_path do |attachment, style|
45 | s = (style || "original").to_s
46 | Papermill::MSWIN ? CGI::escape(s) : s
47 | end
48 |
49 | attr_accessor :crop_h, :crop_w, :crop_x, :crop_y
50 |
51 | def Filedata=(data)
52 | if !Papermill::MSWIN && !(mime = `file --mime -br #{data.path}`).blank? && !mime.starts_with?("cannot open")
53 | data.content_type = mime.strip.split(";").first
54 | elsif (mime = MIME::Types.type_for(data.original_filename))
55 | data.content_type = mime.first.simplified
56 | end
57 | self.file = data
58 | end
59 |
60 | def Filename=(name)
61 | @real_file_name = name
62 | end
63 |
64 | def id_partition
65 | ("%09d" % self.id).scan(/\d{3}/).join("/")
66 | end
67 |
68 | def name
69 | file_file_name
70 | end
71 |
72 | def basename
73 | name.gsub(/#{extension}$/, "").strip
74 | end
75 |
76 | def extension
77 | File.extname(name)
78 | end
79 |
80 | def width
81 | @width ||= Paperclip::Geometry.from_file(file).width
82 | end
83 |
84 | def height
85 | @height ||= Paperclip::Geometry.from_file(file).height
86 | end
87 |
88 | def size
89 | file_file_size
90 | end
91 |
92 | def url(style = nil)
93 | return url!(style) if style.is_a?(Hash)
94 | file.url(style)
95 | end
96 |
97 | def path(style = nil)
98 | return path!(style) if style.is_a?(Hash)
99 | file.path(style)
100 | end
101 |
102 | def url!(style = nil)
103 | create_thumb_file(style_name = style_name(style), style) unless File.exists?(self.path(style_name))
104 | file.url(style_name)
105 | end
106 |
107 | def path!(style = nil)
108 | create_thumb_file(style_name = style_name(style), style) unless File.exists?(self.path(style_name))
109 | file.path(style_name)
110 | end
111 |
112 | def content_type
113 | file_content_type
114 | end
115 |
116 | def style_name(style)
117 | style.is_a?(Hash) ? (style[:name] || style.hash).to_s : (style || "original").to_s
118 | end
119 |
120 | def self.papermill_options(assetable_type, assetable_key)
121 | if assetable_type
122 | assoc = assetable_type.constantize.papermill_options
123 | assoc[assetable_key.try(:to_sym)] || assoc[:default] || Papermill::options
124 | else
125 | Papermill::options
126 | end
127 | end
128 |
129 | def papermill_options
130 | self.class.papermill_options(assetable_type, assetable_key)
131 | end
132 |
133 | def image?
134 | content_type.split("/")[0] == "image"
135 | end
136 |
137 | def create_thumb_file(style_name, style = nil)
138 | return false unless self.image?
139 | destroy_thumbnails if style_name.to_s == "original"
140 | style = self.class.compute_style(style_name) unless style.is_a?(Hash)
141 | FileUtils.mkdir_p File.dirname(new_path = file.path(style_name))
142 | FileUtils.cp((tmp_path = Paperclip::PapermillPaperclipProcessor.make(file, style).path), new_path)
143 | FileUtils.chmod(0644, new_path) unless Papermill::MSWIN
144 | File.delete(tmp_path)
145 | return true
146 | end
147 |
148 | def destroy_thumbnails
149 | thumbnail_folder_mask = Papermill::options[:use_url_key] ? "*/*/" : "*/"
150 | original_folder = "#{File.dirname(file.path)}/"
151 | Dir.glob("#{root_directory}/#{thumbnail_folder_mask}").each do |f|
152 | FileUtils.rm_r(f) unless f == original_folder
153 | end
154 | Dir.glob("#{root_directory}/*/").each do |f|
155 | FileUtils.rm_r(f) if Dir.entries(f) == [".", ".."]
156 | end
157 | end
158 |
159 | def self.destroy_orphans
160 | self.name != self.base_class.name ?
161 | PapermillAsset.delete_all(["created_at < ? AND assetable_id IS NULL AND type = ?", 1.hour.ago, self.name]) :
162 | PapermillAsset.delete_all(["created_at < ? AND assetable_id IS NULL AND type IS NULL", 1.hour.ago])
163 | end
164 |
165 | def compute_url_key(style)
166 | Papermill::options[:url_key_generator].call(style, self)
167 | end
168 |
169 | def has_valid_url_key?(key, style)
170 | !Papermill::options[:use_url_key] || compute_url_key(style) == key
171 | end
172 |
173 | private
174 |
175 | def root_directory
176 | deepness_to_root = Papermill::options[:use_url_key] ? -3 : -2
177 | @root_directory ||= File.dirname(path).split('/')[0..deepness_to_root].join('/')
178 | end
179 |
180 | def set_file_name
181 | return if @real_file_name.blank?
182 | self.title = (basename = @real_file_name.gsub(/#{extension = File.extname(@real_file_name)}$/, ""))
183 | self.file.instance_write(:file_name, "#{basename.to_url}#{extension}")
184 | end
185 |
186 | def destroy_files
187 | FileUtils.rm_r(root_directory) rescue true
188 | end
189 |
190 | def self.compute_style(style)
191 | style = Papermill::options[:aliases][style.to_sym] || Papermill::options[:aliases][style.to_s] || !Papermill::options[:alias_only] && style
192 | [Symbol, String].include?(style.class) ? { :geometry => style.to_s } : style
193 | end
194 | end
--------------------------------------------------------------------------------
/lib/papermill/form_builder.rb:
--------------------------------------------------------------------------------
1 | class ActionView::Helpers::FormBuilder
2 | include ActionView::Helpers::FormTagHelper
3 |
4 | def assets_upload(method, options = {})
5 | papermill_upload_tag method, { :thumbnail => false }.update(options)
6 | end
7 | def asset_upload(method, options = {})
8 | papermill_upload_tag method, { :gallery => false, :thumbnail => false }.update(options)
9 | end
10 | def images_upload(method, options = {})
11 | papermill_upload_tag method, options
12 | end
13 | def image_upload(method, options = {})
14 | papermill_upload_tag method, { :gallery => false }.update(options)
15 | end
16 | end
17 |
18 | module ActionView::Helpers::FormTagHelper
19 |
20 | def assets_upload_tag(assetable, method, options = {})
21 | papermill_upload_tag method, { :thumbnail => false, :assetable => assetable }.update(options)
22 | end
23 |
24 | def asset_upload_tag(assetable, method, options = {})
25 | papermill_upload_tag method, { :gallery => false, :thumbnail => false, :assetable => assetable }.update(options)
26 | end
27 |
28 | def images_upload_tag(assetable, method, options = {})
29 | papermill_upload_tag method, { :assetable => assetable }.update(options)
30 | end
31 |
32 | def image_upload_tag(assetable, method, options = {})
33 | papermill_upload_tag method, { :gallery => false, :assetable => assetable }.update(options)
34 | end
35 |
36 | private
37 |
38 | def papermill_upload_tag(method, options)
39 | assetable = options[:object] || options[:assetable] || @object || @template.instance_variable_get("@#{@object_name}")
40 |
41 | raise PapermillException.new("Form object not found. Please provide it with :object => @assetable with the Papermill helper call") unless assetable
42 | assetable_name = @object_name && "#{@object_name}#{(i = @options[:index]) ? "[#{i}]" : ""}"
43 |
44 | sanitized_method = method.to_s.gsub(/[\?\/\-]$/, '')
45 | sanitized_object_name = @object_name.to_s.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
46 | field_id = @object_name && "#{sanitized_object_name}#{(i = @options[:index]) ? "_#{i}" : ""}_#{sanitized_method}"
47 | assetable.class.papermill(method)
48 | association_options = assetable.class.papermill_options[method.to_sym]
49 |
50 | raise PapermillException.new("Papermill association #{method} failed to be generated dynamically for #{assetable.class.name}") unless association_options
51 | options = association_options.deep_merge(options)
52 | field_name = "#{assetable_name}[papermill_#{method}_ids][]"
53 |
54 | html = {}
55 |
56 | if ot = options[:thumbnail]
57 | w = ot[:width] || ot[:height] && ot[:aspect_ratio] && (ot[:height] * ot[:aspect_ratio]).to_i || nil
58 | h = ot[:height] || ot[:width] && ot[:aspect_ratio] && (ot[:width] / ot[:aspect_ratio]).to_i || nil
59 | computed_style = ot[:style] || (w || h) && "#{w}x#{h}>" || "original"
60 | html[:css] = set_papermill_inline_css(field_id, w, h, options)
61 | end
62 |
63 | html[:js] = set_papermill_inline_js(field_id, compute_papermill_url(:create, computed_style, field_name, field_id, options), options)
64 |
65 |
66 | html[:upload_button] = %{\
67 |
68 |
69 |
}
70 |
71 | # I don't use the full :through association that is not updated if assetable.new_record?.
72 | collection = association_options[:through] ? assetable.send("#{method}_associations").map(&:papermill_asset) : assetable.send(method)
73 | locals = { :thumbnail_style => computed_style, :targetted_size => options[:targetted_size], :field_name => field_name, :field_id => field_id }
74 | html[:container] = @template.content_tag(:div, :id => field_id, :class => "papermill-#{method.to_s} #{(options[:thumbnail] ? "papermill-thumb-container" : "papermill-asset-container")} #{(options[:gallery] ? "papermill-multiple-items" : "papermill-unique-item")}") do
75 | @template.render(:partial => "papermill/asset",
76 | :collection => collection,
77 | :locals => locals)
78 | end
79 |
80 | if options[:gallery] && options[:mass_edit]
81 | html[:mass_edit] = %{\
82 | #{I18n.t("papermill.modify-all")}
83 | #{options[:mass_editable_fields].map do |field|
84 | %{#{I18n.t("papermill.#{field.to_s}", :default => field.to_s)} }
85 | end.join("\n")} }
86 | end
87 |
88 | if options[:through]
89 | browser_url = compute_papermill_url(:browser, computed_style, field_name, field_id, options)
90 | html[:browser] = %{Ajouter... }
91 | end
92 |
93 | # hidden_field needed to empty a list.
94 | %{
95 | #{@template.hidden_field("#{assetable_name}[papermill_#{method}_ids]", nil)}
96 | #{html[:css].to_s + html[:js].to_s + options[:form_helper_elements].map{|element| html[element] || ""}.join("\n")}
97 |
}.html_safe
98 | end
99 |
100 |
101 | def compute_papermill_url(action, computed_style, field_name, field_id, options)
102 | @template.url_for({
103 | :escape => false, :controller => "/papermill", :action => action,
104 | :asset_class => (options[:class_name] || PapermillAsset).to_s,
105 | :gallery => !!options[:gallery], :thumbnail_style => computed_style, :targetted_size => options[:targetted_size],
106 | :field_name => field_name, :field_id => field_id
107 | })
108 | end
109 |
110 | def set_papermill_inline_js(field_id, create_url, options)
111 | outputed_js = (options[:gallery] ? %{ jQuery("##{field_id}").sortable() } : "") +
112 | %{
113 | new SWFUpload({
114 | post_params: {
115 | "#{ ActionController::Base.session_options[:key] }": "#{ @template.cookies[ActionController::Base.session_options[:key]] }"
116 | },
117 | upload_id: "#{ field_id }",
118 | upload_url: "#{ @template.escape_javascript create_url }",
119 | file_types: "#{ options[:swfupload].delete(:file_types) || (options[:images_only] ? '*.jpg;*.jpeg;*.png;*.gif' : '*.*') }",
120 | file_queue_limit: "#{ !options[:gallery] ? '1' : '0' }",
121 | file_queued_handler: Papermill.file_queued,
122 | file_dialog_complete_handler: Papermill.file_dialog_complete,
123 | upload_start_handler: Papermill.upload_start,
124 | upload_progress_handler: Papermill.upload_progress,
125 | file_queue_error_handler: Papermill.file_queue_error,
126 | upload_error_handler: Papermill.upload_error,
127 | upload_success_handler: Papermill.upload_success,
128 | upload_complete_handler: Papermill.upload_complete,
129 | button_placeholder_id: "browse_for_#{field_id}",
130 | #{ options[:swfupload].map { |key, value| "#{key}: #{value}" if value }.compact.join(",\n") }
131 | });
132 | }
133 | if options[:use_content_for]
134 | @template.content_for :papermill_inline_js do
135 | outputed_js
136 | end
137 | ""
138 | else
139 | %{}
144 | end
145 | end
146 |
147 | def set_papermill_inline_css(field_id, width, height, options)
148 | return unless options[:inline_css]
149 | html = ["\n"]
150 | size = [width && "width:#{width}px", height && "height:#{height}px"].compact.join("; ")
151 | if og = options[:gallery]
152 | vp, hp, vm, hm, b = [og[:vpadding], og[:hpadding], og[:vmargin], og[:hmargin], og[:border_thickness]].map &:to_i
153 | gallery_width = (og[:width] || width) && "width:#{og[:width] || og[:columns]*(width.to_i+(hp+hm+b)*2)}px;" || ""
154 | gallery_height = (og[:height] || height) && "min-height:#{og[:height] || og[:lines]*(height.to_i+(vp+vm+b)*2)}px;" || ""
155 | html << %{##{field_id} { #{gallery_width} #{gallery_height} }}
156 | html << %{##{field_id} .asset { margin:#{vm}px #{hm}px; border-width:#{b}px; padding:#{vp}px #{hp}px; #{size}; }}
157 | else
158 | html << %{##{field_id}, ##{field_id} .asset { #{size} }}
159 | end
160 | html << %{##{field_id} .name { width:#{width || "100"}px; }}
161 | if options[:use_content_for]
162 | @template.content_for :papermill_inline_css do
163 | html.join("\n")
164 | end
165 | ""
166 | else
167 | %{}
170 | end
171 | end
172 | end
--------------------------------------------------------------------------------
/public/facebox/facebox.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Facebox (for jQuery)
3 | * version: 1.2 (05/05/2008)
4 | * @requires jQuery v1.2 or later
5 | *
6 | * Examples at http://famspam.com/facebox/
7 | *
8 | * Licensed under the MIT:
9 | * http://www.opensource.org/licenses/mit-license.php
10 | *
11 | * Copyright 2007, 2008 Chris Wanstrath [ chris@ozmm.org ]
12 | *
13 | * Usage:
14 | *
15 | * jQuery(document).ready(function() {
16 | * jQuery('a[rel*=facebox]').facebox()
17 | * })
18 | *
19 | * Terms
20 | * Loads the #terms div in the box
21 | *
22 | * Terms
23 | * Loads the terms.html page in the box
24 | *
25 | * Terms
26 | * Loads the terms.png image in the box
27 | *
28 | *
29 | * You can also use it programmatically:
30 | *
31 | * jQuery.facebox('some html')
32 | *
33 | * The above will open a facebox with "some html" as the content.
34 | *
35 | * jQuery.facebox(function($) {
36 | * $.get('blah.html', function(data) { $.facebox(data) })
37 | * })
38 | *
39 | * The above will show a loading screen before the passed function is called,
40 | * allowing for a better ajaxy experience.
41 | *
42 | * The facebox function can also display an ajax page or image:
43 | *
44 | * jQuery.facebox({ ajax: 'remote.html' })
45 | * jQuery.facebox({ image: 'dude.jpg' })
46 | *
47 | * Want to close the facebox? Trigger the 'close.facebox' document event:
48 | *
49 | * jQuery(document).trigger('close.facebox')
50 | *
51 | * Facebox also has a bunch of other hooks:
52 | *
53 | * loading.facebox
54 | * beforeReveal.facebox
55 | * reveal.facebox (aliased as 'afterReveal.facebox')
56 | * init.facebox
57 | *
58 | * Simply bind a function to any of these hooks:
59 | *
60 | * $(document).bind('reveal.facebox', function() { ...stuff to do after the facebox and contents are revealed... })
61 | *
62 | */
63 | (function($) {
64 | $.facebox = function(data, klass) {
65 | $.facebox.loading()
66 |
67 | if (data.ajax) fillFaceboxFromAjax(data.ajax)
68 | else if (data.image) fillFaceboxFromImage(data.image)
69 | else if (data.div) fillFaceboxFromHref(data.div)
70 | else if ($.isFunction(data)) data.call($)
71 | else $.facebox.reveal(data, klass)
72 | }
73 |
74 | /*
75 | * Public, $.facebox methods
76 | */
77 |
78 | $.extend($.facebox, {
79 | settings: {
80 | opacity : 0,
81 | overlay : true,
82 | loadingImage : '/facebox/loading.gif',
83 | closeImage : '/facebox/closelabel.gif',
84 | imageTypes : [ 'png', 'jpg', 'jpeg', 'gif' ],
85 | faceboxHtml : '\
86 | \
87 | \
112 |
'
113 | },
114 |
115 | loading: function() {
116 | init()
117 | if ($('#facebox .loading').length == 1) return true
118 | showOverlay()
119 |
120 | $('#facebox .content').empty()
121 | $('#facebox .body').children().hide().end().
122 | append('')
123 |
124 | $('#facebox').css({
125 | top: getPageScroll()[1] + (getPageHeight() / 10),
126 | left: 385.5
127 | }).show()
128 |
129 | $(document).bind('keydown.facebox', function(e) {
130 | if (e.keyCode == 27) $.facebox.close()
131 | return true
132 | })
133 | $(document).trigger('loading.facebox')
134 | },
135 |
136 | reveal: function(data, klass) {
137 | $(document).trigger('beforeReveal.facebox')
138 | if (klass) $('#facebox .content').addClass(klass)
139 | $('#facebox .content').append(data)
140 | $('#facebox .loading').remove()
141 | $('#facebox .body').children().fadeIn('normal')
142 | $('#facebox').css('left', $(window).width() / 2 - ($('#facebox table').width() / 2))
143 | $(document).trigger('reveal.facebox').trigger('afterReveal.facebox')
144 | },
145 |
146 | close: function() {
147 | $(document).trigger('close.facebox')
148 | return false
149 | }
150 | })
151 |
152 | /*
153 | * Public, $.fn methods
154 | */
155 |
156 | $.fn.facebox = function(settings) {
157 | init(settings)
158 |
159 | function clickHandler() {
160 | $.facebox.loading(true)
161 |
162 | // support for rel="facebox.inline_popup" syntax, to add a class
163 | // also supports deprecated "facebox[.inline_popup]" syntax
164 | var klass = this.rel.match(/facebox\[?\.(\w+)\]?/)
165 | if (klass) klass = klass[1]
166 |
167 | fillFaceboxFromHref(this.href, klass)
168 | return false
169 | }
170 |
171 | return this.click(clickHandler)
172 | }
173 |
174 | /*
175 | * Private methods
176 | */
177 |
178 | // called one time to setup facebox on this page
179 | function init(settings) {
180 | if ($.facebox.settings.inited) return true
181 | else $.facebox.settings.inited = true
182 |
183 | $(document).trigger('init.facebox')
184 | makeCompatible()
185 |
186 | var imageTypes = $.facebox.settings.imageTypes.join('|')
187 | $.facebox.settings.imageTypesRegexp = new RegExp('\.' + imageTypes + '$', 'i')
188 |
189 | if (settings) $.extend($.facebox.settings, settings)
190 | $('body').append($.facebox.settings.faceboxHtml)
191 |
192 | var preload = [ new Image(), new Image() ]
193 | preload[0].src = $.facebox.settings.closeImage
194 | preload[1].src = $.facebox.settings.loadingImage
195 |
196 | $('#facebox').find('.b:first, .bl, .br, .tl, .tr').each(function() {
197 | preload.push(new Image())
198 | preload.slice(-1).src = $(this).css('background-image').replace(/url\((.+)\)/, '$1')
199 | })
200 |
201 | $('#facebox .close').click($.facebox.close)
202 | $('#facebox .close_image').attr('src', $.facebox.settings.closeImage)
203 | }
204 |
205 | // getPageScroll() by quirksmode.com
206 | function getPageScroll() {
207 | var xScroll, yScroll;
208 | if (self.pageYOffset) {
209 | yScroll = self.pageYOffset;
210 | xScroll = self.pageXOffset;
211 | } else if (document.documentElement && document.documentElement.scrollTop) { // Explorer 6 Strict
212 | yScroll = document.documentElement.scrollTop;
213 | xScroll = document.documentElement.scrollLeft;
214 | } else if (document.body) {// all other Explorers
215 | yScroll = document.body.scrollTop;
216 | xScroll = document.body.scrollLeft;
217 | }
218 | return new Array(xScroll,yScroll)
219 | }
220 |
221 | // Adapted from getPageSize() by quirksmode.com
222 | function getPageHeight() {
223 | var windowHeight
224 | if (self.innerHeight) { // all except Explorer
225 | windowHeight = self.innerHeight;
226 | } else if (document.documentElement && document.documentElement.clientHeight) { // Explorer 6 Strict Mode
227 | windowHeight = document.documentElement.clientHeight;
228 | } else if (document.body) { // other Explorers
229 | windowHeight = document.body.clientHeight;
230 | }
231 | return windowHeight
232 | }
233 |
234 | // Backwards compatibility
235 | function makeCompatible() {
236 | var $s = $.facebox.settings
237 |
238 | $s.loadingImage = $s.loading_image || $s.loadingImage
239 | $s.closeImage = $s.close_image || $s.closeImage
240 | $s.imageTypes = $s.image_types || $s.imageTypes
241 | $s.faceboxHtml = $s.facebox_html || $s.faceboxHtml
242 | }
243 |
244 | // Figures out what you want to display and displays it
245 | // formats are:
246 | // div: #id
247 | // image: blah.extension
248 | // ajax: anything else
249 | function fillFaceboxFromHref(href, klass) {
250 | // div
251 | if (href.match(/#/)) {
252 | var url = window.location.href.split('#')[0]
253 | var target = href.replace(url,'')
254 | $.facebox.reveal($(target).clone().show(), klass)
255 |
256 | // image
257 | } else if (href.match($.facebox.settings.imageTypesRegexp)) {
258 | fillFaceboxFromImage(href, klass)
259 | // ajax
260 | } else {
261 | fillFaceboxFromAjax(href, klass)
262 | }
263 | }
264 |
265 | function fillFaceboxFromImage(href, klass) {
266 | var image = new Image()
267 | image.onload = function() {
268 | $.facebox.reveal('', klass)
269 | }
270 | image.src = href
271 | }
272 |
273 | function fillFaceboxFromAjax(href, klass) {
274 | $.get(href, function(data) { $.facebox.reveal(data, klass) })
275 | }
276 |
277 | function skipOverlay() {
278 | return $.facebox.settings.overlay == false || $.facebox.settings.opacity === null
279 | }
280 |
281 | function showOverlay() {
282 | if (skipOverlay()) return
283 |
284 | if ($('facebox_overlay').length == 0)
285 | $("body").append('
')
286 |
287 | $('#facebox_overlay').hide().addClass("facebox_overlayBG")
288 | .css('opacity', $.facebox.settings.opacity)
289 | .click(function() { $(document).trigger('close.facebox') })
290 | .fadeIn(200)
291 | return false
292 | }
293 |
294 | function hideOverlay() {
295 | if (skipOverlay()) return
296 |
297 | $('#facebox_overlay').fadeOut(200, function(){
298 | $("#facebox_overlay").removeClass("facebox_overlayBG")
299 | $("#facebox_overlay").addClass("facebox_hide")
300 | $("#facebox_overlay").remove()
301 | })
302 |
303 | return false
304 | }
305 |
306 | /*
307 | * Bindings
308 | */
309 |
310 | $(document).bind('close.facebox', function() {
311 | $(document).unbind('keydown.facebox')
312 | $('#facebox').fadeOut(function() {
313 | $('#facebox .content').removeClass().addClass('content')
314 | hideOverlay()
315 | $('#facebox .loading').remove()
316 | })
317 | })
318 |
319 | })(jQuery);
320 |
--------------------------------------------------------------------------------
/generators/papermill_initializer/papermill_initializer.rb:
--------------------------------------------------------------------------------
1 | # DO NOT MOVE OR RENAME THIS FILE
2 | # It must stand in your RAILS_ROOT/config/initializer folder and be named papermill.rb, because it is explicitely early-loaded by Papermill
3 |
4 | module Papermill
5 |
6 | # All options here already are defaults.
7 |
8 | unless defined?(OPTIONS)
9 |
10 | OPTIONS = {
11 |
12 |
13 | #@@@@@@@@@@@@@@ Papermill association parameters @@@@@@@@@@@@@@@@@@
14 |
15 | # You can override these parameters here, or in your papermill associations definition.
16 |
17 | # Associated PapermillAsset subclass (must be an STI subclass of PapermillAsset)
18 | # :class_name => "PapermillAsset",
19 |
20 | # You can use the included join table if you need your assets to be associated to more than one assetable
21 | # :through => false,
22 |
23 |
24 | #@@@@@@@@@@@@@@@@@@@ form-helper parameters @@@@@@@@@@@@@@@@@@@@@@@
25 |
26 | # You can override all these parameters here, or in your papermill associations definition, or in form-helper calls.
27 |
28 | # Helper can generates inline css styling that adapt to your gallery/images placeholder. You can use it to scaffold, then copy the lines you need in your application css and set it to false.
29 |
30 | # :inline_css => true,
31 |
32 | # If you don't want generated CSS and JS to be captured and available through @papermill_inline_css/@papermill_inline_js, and instead directly outputed in the form, set it to false
33 |
34 | # :use_content_for => true,
35 |
36 | # SwfUpload will only let the user upload images.
37 |
38 | # :images_only => false,
39 |
40 | # Dashboard is only for galleries
41 | # You can remove/change order of HTML elements.
42 |
43 | # :form_helper_elements => [:upload_button, :container, :mass_edit],
44 |
45 | # Batch edit a single field of all the assets in a form field
46 |
47 | # :mass_edit => true,
48 |
49 | # Attributes editable at once for all assets in a gallery
50 |
51 | # :mass_editable_fields => ["title", "copyright", "description"],
52 |
53 | # Attributes you can edit in the form. You can use :type (string or text) and :label (any string)
54 | # if you have more complex needs, you should override app/views/papermill/_form.html.erb in your application.
55 |
56 | # :editable_fields => [
57 | # {:title => {:type => "string"}},
58 | # {:alt => {:type => "string"}},
59 | # {:copyright => {:type => "string"}},
60 | # {:description => {:type => "text" }},
61 | # ],
62 |
63 | # FormHelper gallery options
64 | # If :inline_css is true, css will be generated automatically and added through @content_for_papermill_inline_css (papermill_stylesheet_tag includes it)
65 | # Great for quick admin scaffolding.
66 |
67 | :gallery => {
68 |
69 | # override calculated gallery width. Ex: "auto"
70 |
71 | # :width => nil,
72 |
73 | # override calculated gallery height
74 |
75 | # :height => nil,
76 |
77 | # Number of columns and lines in a gallery
78 |
79 | # :columns => 8,
80 | # :lines => 2,
81 |
82 | # vertical/horizontal padding/margin around each thumbnails
83 |
84 | # :vpadding => 0,
85 | # :hpadding => 0,
86 | # :vmargin => 1,
87 | # :hmargin => 1,
88 |
89 | # border around thumbnails
90 |
91 | # :border_thickness => 2
92 | },
93 |
94 | # FormHelper thumbnail's information.
95 | # Set :width OR :height to nil to use aspect_ratio value. Remember that 4/3 == 1 => Use : 4.0/3
96 | # You can override computed ImageMagick transformation strings that defaults to "#{:width}x#{:height}>" by setting a value to :style
97 | # Needed if you set :aliases_only to true
98 |
99 | :thumbnail => {
100 |
101 | # :width => 100,
102 | # :height => 100,
103 | # :aspect_ratio => nil,
104 | # :style => nil
105 | },
106 |
107 | # If you plan to use the original images in one place in one specific size, you can pass targetted_size,
108 | # to incitate the user to crop the image himself (double-click on the image when editing) with the right parameters,
109 | # instead of letting ImageMagick do his magick bluntly at render-time.
110 |
111 | # :targetted_size => nil,
112 |
113 | # Options passed on to SWFUpload.
114 | # To remove an option when overriding, set it to nil.
115 |
116 | :swfupload => {
117 | # :flash_url => "'/swfupload/swfupload.swf'",
118 | # :button_image_url => "'/papermill/images/upload-blank.png'",
119 | # :button_width => 61,
120 | # :button_height => 22,
121 | # :button_text => %{'#{I18n.t("papermill.upload-button-wording")} '},
122 | # :button_text_style => %{'.button-text { font-size: 12pt; font-weight: bold; }'},
123 | # :button_text_top_padding => 4,
124 | # :button_text_left_padding => 4,
125 | # :debug => false,
126 | # :prevent_swf_caching => true,
127 | # :file_size_limit => "'10 MB'"
128 | },
129 |
130 | #@@@@@@@@@@@@@@@@@ thumbnails style parameters @@@@@@@@@@@@@@@@@@@@
131 |
132 | # You can override all these parameters here, or in your papermill associations definition, or in thumbnail styling hashes.
133 |
134 | # 1. COPYRIGHT WATERMARKING
135 |
136 | # Activate with '©' at the end of your geometry string or pass :copyright => true|"my_copyright" in alias definition or geometry hash
137 | # Papermill will use, in that order of priority :
138 | # * copyright found in geometry string AFTER the @
139 | # * alternatively :copyright in alias/definition hash
140 | # * asset's copyright column (if found)
141 | # * associated :copyright definition in your Assetable association definition
142 | # * below's :copyright definition
143 |
144 | # Set this definition to a default copyright name if you want one (You'll still need to postfix your geometry string with © or pass {:watermark => true} in your alias/geometry hash to use it)
145 |
146 | # :copyright => nil,
147 |
148 | # Textilize, truncate, transform... your copyright before its ImageMagick integration
149 |
150 | # :copyright_text_transform => Proc.new {|c| c },
151 |
152 | # Watermark ImageMagick command string.
153 | # * %s gets interpolated with above transformed copyright string
154 | # * DO NOT change the background color!, change the bordercolor instead. (because background color adds to bordercolor I set it to totally transparent)
155 | # * for both fill (=foreground color) and bordercolor (=background color), the last two octals control alpha (transparency). FF is opaque, 00 is transparent.
156 | # * remove -bordercolor if you don't want any background
157 | # * add +antialias to REMOVE antialiasing (after '-font Arial-Bold', for example)
158 | # * font-size is pointsize
159 | # * type 'identify -list font' to get a list of the fonts you can use (ImageMagick will default to Arial/Times if it can't find it)
160 | # * use -gravity and -geometry for positionning, -geometry +x+y is relative to -gravity's corner/zone
161 | # * parenthesis are there to isolate the label creation from the compositing (blending) part.
162 | # * don't touch things you don't understand, you'll save yourself some time
163 |
164 | # :copyright_im_command => %{ \\( -font Arial-Bold -pointsize 9 -fill '#FFFFFFE0' -border 3 -bordercolor '#50550080' -background '#00000000' label:' %s ' \\) -gravity South-West -geometry +0+0 -composite },
165 |
166 | # 2. IMAGE WATERMARKING
167 |
168 | # Activate with "-wm" at the end of your geometry string BEFORE copyright ('©'), or alternatively with :watermark => in your alias/inline hash
169 | # If you pass an image_path to :watermark, it will override below :
170 |
171 | # you can use a relative path from your public directory (see :public_root), a complete path, or an URI
172 |
173 | # :watermark => "/images/rails.png",
174 |
175 | # default :watermarking command for image_magick. %s gets interpolated with above image path.
176 |
177 | # :watermark_im_command => %{- | composite \\( %s -resize 100% \\) - -dissolve 20% -gravity center -geometry +0+0 },
178 |
179 |
180 |
181 | #@@@@@@@@@@@@@@@@@ Application-wide parameters @@@@@@@@@@@@@@@@@@@@
182 |
183 | # Set to true to require aliases in all url/path, disabling the
184 | # Don't forget to give an alias value to options[:thumbnail][:style] if true!
185 |
186 | # :alias_only => false,
187 |
188 | # Needed if :alias_only
189 | # Aliases are available application wide for all your assets. You can pass them instead of a geometry_string or a geometry hash
190 |
191 | :aliases => {
192 | # :mini_crop => "100x100#",
193 | # :cant_touch_this => {
194 | # :geometry => "400x>",
195 | # :copyright => "You sire, can't touch this",
196 | # :watermark => "http://westsidewill.com/newblog/wp-content/uploads/2009/09/MC-Hammer.jpg"
197 | # }
198 | },
199 |
200 | # To prevent generation of thumbnails and guessing of assets location through URL hacking
201 | # e.g. if you want to protect access to non-copyrighted original files,
202 | # or don't want users to browse images by guessing the sequence of ids,
203 | # an encrypted hash can be generated for each geometry string/alias and added to path/url.
204 | # Use your imagination for :url_key_generator, really.
205 | # Please note that all previous assets paths will be lost if you add/remove or change the :url_key generation.
206 |
207 | # :use_url_key => false,
208 | # :url_key_salt => "change-me-to-your-favorite-pet's-name",
209 | # :url_key_generator => Proc.new { |style, asset| Digest::SHA512.hexdigest("#{style}#{asset.id}#{Papermill::options[:url_key_salt]}")[0..10] },
210 |
211 | # added before path/url. Your front webserver will need to be able to find your assets
212 | # :papermill_path_prefix => ":rails_root/public/system/papermill",
213 | # :papermill_url_prefix => "/system/papermill",
214 |
215 | # you can set it to false if you don't plan to have too many assets. (dangerous)
216 | # :use_id_partition => true,
217 |
218 | # If you use those defaults, the first asset will end-up in RAILS_ROOT/public/system/papermill/000/000/001/original/my_first_asset_name.ext
219 | # You'll access it with my_domain.com/system/papermill/000/000/001/original/my_first_asset_name.ext
220 | }
221 | end
222 | end
223 |
--------------------------------------------------------------------------------
/README.rdoc:
--------------------------------------------------------------------------------
1 | = Papermill
2 |
3 | * Asset management made easy, 10 minutes integration.
4 | * All-you-can-eat glue around Polymorphic Paperclip table, SWFUpload & JQuery.
5 | * Associate any image or list of images with any model and any key.
6 |
7 | == Install the gem
8 |
9 | sudo gem install papermill
10 |
11 | == Try the demo
12 |
13 | rails -m http://github.com/bbenezech/papermill/raw/master/demo.txt papermill-example
14 |
15 | === Out-of-the-box compatibility with :
16 |
17 | * Formtastic # use :as => :[image|asset](s)_upload
18 | * JGrowl # for notifications (included)
19 | * FaceBox # for popups (included)
20 | * Stringex # (or any String#to_url) for asset filename/url generation
21 |
22 | === Navigator minimal requirements:
23 |
24 | * IE6+
25 | * Flash 9+
26 | * Javascript ON
27 |
28 | Check your audience.
29 |
30 | === Server requirements:
31 |
32 | * Rails 2.3.[4~>8]
33 | * Paperclip 2.3.1.1 (loaded with gem dependency)
34 | * Front web server serving static assets if present, and forwarding demand to rails if not. Any classic installation will do that by default.
35 | * NOT compatible with Heroku/S3
36 |
37 | == Installation
38 |
39 | === Once gem is installed
40 |
41 | Generate the migration
42 | ./script/generate papermill_table PapermillMigration
43 |
44 | Edit it and migrate
45 | rake db:migrate
46 |
47 | Copy static assets to your public directory
48 | ./script/generate papermill_assets
49 |
50 | Create the option file config/initializers/papermill.rb
51 | ./script/generate papermill_initializer
52 |
53 | Go have a look at config/initializers/papermill.rb
54 |
55 | === In environment.rb
56 |
57 | ...
58 | Rails::Initializer.run do |config|
59 | ...
60 | config.gem papermill
61 | end
62 |
63 | === In your layout
64 |
65 |
66 | ==== Quick version
67 |
68 | Inside
69 | <%= papermill_stylesheet_tag %>
70 |
71 | Before (best practice for javascript loading)
72 | <%= papermill_javascript_tag :with_jquery => "no_conflict" %>
73 |
74 | You don't need :with_jquery if load it by yourself. Pass "no_conflict" if you use the default Prototype library, or some other '$' library (mootools..)
75 |
76 | ==== In a real-world production application, you could use something like this, and adapt it to your own needs
77 |
78 | Inside
79 | <% unless @content_for_papermill_inline_js.blank? %>
80 | <%= javascript_include_tag "/facebox/facebox.js", "/jgrowl/jquery.jgrowl_minimized.js", "/papermill/jquery.Jcrop.min.js", "/swfupload/swfupload.js", "/papermill/papermill.js", :cache => "papermill" %>
81 |
86 | <% end %>
87 |
88 | Before
89 |
90 |
91 | <% unless @content_for_papermill_inline_js.blank? %>
92 | <%= stylesheet_link_tag("/facebox/facebox.css", "/jgrowl/jquery.jgrowl.css", "/Jcrop/jquery.Jcrop.css", "/papermill/papermill.css", :cache => "papermill") %>
93 |
96 | <% end %>
97 |
98 | == Security
99 |
100 | === URL-hacking
101 |
102 | Maybe you don't want users to use your application as a thumbnailing farm for their own uploaded images, or you have protected members areas and you don't want users to 'browse' others members file.
103 |
104 | * Brute solution: pass :use_url_key to true in the options (config/initializers/papermill.rb). A crypted hash unique to your application and to each asset and to the requested style will be added to the URL. No more happy-guessing of anything. Do that first before going live, or you'll have to migrate all assets...
105 | * pass :alias_only to true. This will disable the possibility to generate thumbnails with a papermill string in the url, but won't do anything for the member area thing. Plus you will have to use aliases only, form helpers included (pass :thumbnail => { :style => :some_alias })
106 |
107 | == Usage
108 |
109 | Assetable is the class that has_many papermill_assets (i.e. the class with the papermill declaration)
110 |
111 | === Assetable declaration
112 |
113 | You can have one :default association (his settings will be used for unfound associations) and as many other associations as you want in your model.
114 | You can define a papermill relationship dynamically: just do smtg like Assetable.papermill(:dynamic_key, {}) when you need to. Perfect for CMS where associations are created by users. Then you'll be able to use assetable.dynamic_key to retrieve the associated assets. If you don't send the {}, default options from default association will be used, which may or may not be what you want.
115 |
116 | Actually, the form helper leverages this when you use a :key that doesn't exist: it will create a new Papermill relationship whith :key as the name and options from the :default declaration if any found on the model.
117 |
118 | If you don't need dynamic keys, just declare your associations in the model, like this :
119 |
120 | class Article
121 | papermill :default
122 | papermill :images
123 | papermill :pdf_version
124 | papermill :cover_image
125 | papermill :illustrations
126 | end
127 |
128 | === Form helpers
129 |
130 | Example form:
131 |
132 | form_for @assetable do
133 | # I need a simple asset upload field :
134 | f.asset_upload :pdf_version
135 |
136 | # Now I need to be able to upload as many documents as I need, and sort them at will
137 | # no document should be bigger than 1MB (respect the quoting!)
138 | # and I don't want the mass_edit feature
139 | f.assets_upload :documentation, :swfupload => { :file_size_limit => "'1 MB'" }, :mass_edit => false
140 |
141 | # I need to display *one* cover *image*, format will be 200x200
142 | # targetted_size will give the uploader hints when cropping the image after upload : desired display size and wanted aspect-ratio.
143 | # Better than cropping automatically in the center if the character's head is in the upper-left corner..
144 | # :thumbnail => { :width & :height } set the dimensions of the preview thumbnail
145 | # And finally, I need a 200x200# crop for preview, not the default 200x200> that would be generated by default ("#{:width}x#{:heigth}>")
146 | f.image_upload :cover_image, :targetted_size => "200x200", :thumbnail => { :width => 200, :height => 200, :style => "200x200#" }
147 |
148 | # Now the image gallery, sortable.
149 | # I use :gallery => { :lines & :columns } to give the number of lines/columns,
150 | # and some CSS will be generated to size the gallery perfectly,
151 | # according to the thumb size inside the gallery and their padding/margin/border sizes.
152 | # the number of lines will increase if needed when uploading
153 | f.images_upload :illustrations, {
154 | :thumbnail => {
155 | :width => 100,
156 | :height => 70
157 | },
158 | :gallery => {
159 | :columns => 8, # number of columns
160 | :lines => 2, # number of lines
161 | :vpadding => 2, # vertical padding around each thumb
162 | :hpadding => 2, # horizontal one
163 | :vmargin => 3, # vertical margin
164 | :hmargin => 1, # horizontal one
165 | :border_thickness => 2 # border size around each thumb
166 | }
167 | }
168 | end
169 |
170 | With Formtastic, pass
171 |
172 | :as => (:image_upload | :images_upload | :asset_upload | :assets_upload)
173 | And add your options as you would with the normal helpers.
174 |
175 | With FormTagHelpers, use (image_upload_tag | images_upload_tag | asset_upload_tag | assets_upload_tag) @assetable, :key, options
176 |
177 | image_upload_tag @article, :cover_image, :targetted_size => "200x200"
178 |
179 | === Asset editing
180 |
181 | * double-click on any uploaded asset in any form-helper to access & edit his properties
182 | * then double-click image to crop it if it's an image. You'll then access a Jcrop window. Pass :targetted_size => "widthxheigth" to lock aspect-ratio and default the selection size to widthxheigth.
183 |
184 | === Thumbnails
185 |
186 |
187 | ==== On-the-fly request time processing:
188 |
189 | PapermillAsset#url(papermill string (see 1.)) # path and url behave the same way
190 | PapermillAsset#url(papermill alias (see 2.))
191 |
192 | Pros: fast. Nothing done upon page rendering. If asset isn't found by Apache/NGinx, then request is passed to rails, which will create it, once.
193 |
194 | Cons: need to setup an alias in the options if you want to define use a hash instead of a papermill string (for custom watermark)
195 |
196 | ==== Render time processing:
197 |
198 | PapermillAsset#url!(papermill string (see 1.)) # path! and url! behave the same way
199 | PapermillAsset#url!(papermill alias (see 2.))
200 | PapermillAsset#url!(papermill hash (see 3.))
201 |
202 | Pros: can use a hash directly in the url call.
203 |
204 | Cons: needs a thumbnail presence check at each render.
205 |
206 | ==== 1. Papermill String
207 |
208 | Consist of:
209 |
210 | * an ImageMagick geometry string (ex: "100x100>", "original", "100x#", etc.)
211 | * an optional watermark (-wm) flag # will use option[:watemark] for URI
212 | * an optional copyright (©) flag # will use copyright text after the "©" or options[:copyright]
213 |
214 | Examples:
215 |
216 | image_tag @article.covers.first.url("100x100")
217 | image_tag @article.covers.first.url("original©")
218 | image_tag @article.covers.first.url("100x100#-wm©")
219 | image_tag @article.covers.first.url("100x200#©papermill")
220 |
221 | ==== 2. Papermill Alias
222 |
223 | Those are application-wide, set them in the options
224 |
225 | Consist of:
226 |
227 | :geometry => "ImageMagick-geometry-string"
228 | :copyright => true | "copyright" # If true, the asset copyright field will be used. Edit the asset.
229 | :watermark => true | URI # If true, will use options[:watemark]
230 |
231 | Examples:
232 |
233 | #config/initilializers/papermill.rb
234 |
235 | # snip
236 | :aliases => {
237 | :thumb_copyrighted => {
238 | :geometry => "100x100",
239 | :copyright => "papermill",
240 | },
241 | :thumb_copyrighted_dynamically => {
242 | :geometry => "100x100",
243 | :copyright => true
244 | },
245 | :thumb_watermarked_with_rails => {
246 | :width => "100",
247 | :height => "100",
248 | :watermark => "/images/rails.png"
249 | }
250 | }
251 |
252 | Then in your views, simply do
253 |
254 | image_tag @article.covers.first.url(:thumb_copyrighted)
255 |
256 | ==== 3. Papermill Hash
257 |
258 | Same as aliases, but defined directly in #url!()
259 | Plus you can add a :name that will be used for style-name (defaults to a md5 of the hash)
260 |
261 | Example:
262 |
263 | image_tag @article.covers.first.url(
264 | :geometry => "100x100",
265 | :watermark => "/images/rails.png",
266 | :copyright => "papermill",
267 | :name => "thumbnail_watermarked_and_copyrighted"
268 | )
269 |
270 | === Resource access
271 |
272 | Papermill generates an # association
273 |
274 | @entry.mug_shots.first
275 | @entry.diaporamas.each do |image| ..
276 | # etc.
277 |
278 | === Using PapermillAsset
279 |
280 | @asset = @entry.mug_shots.first
281 | image_tag @asset.url # original
282 | image_tag @asset.url("100x>") # assuming asset is an image
283 | image_tag @asset.url(:big) # assuming you have a :big alias
284 | @asset.name
285 | @asset.content_type
286 | @asset.path
287 | @asset.path("100x>")
288 | # etc.
289 |
290 | === Translations:
291 |
292 | Papermill is fully I18n-able.
293 | Copy config/locales/papermill.yml to your root config/locale folder to modify any wording in a any locale.
294 |
295 | Copyright (c) 2009 Benoit Bénézech, released under the MIT license
296 |
297 | http://rubyonrails.org/images/rails.png
--------------------------------------------------------------------------------
/public/jgrowl/jquery.jgrowl.js:
--------------------------------------------------------------------------------
1 | /**
2 | * jGrowl 1.2.5
3 | *
4 | * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
5 | * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
6 | *
7 | * Written by Stan Lemon
8 | * Last updated: 2009.12.15
9 | *
10 | * jGrowl is a jQuery plugin implementing unobtrusive userland notifications. These
11 | * notifications function similarly to the Growl Framework available for
12 | * Mac OS X (http://growl.info).
13 | *
14 | * To Do:
15 | * - Move library settings to containers and allow them to be changed per container
16 | *
17 | * Changes in 1.2.5
18 | * - Changed wrapper jGrowl's options usage to "o" instead of $.jGrowl.defaults
19 | * - Added themeState option to control 'highlight' or 'error' for jQuery UI
20 | * - Ammended some CSS to provide default positioning for nested usage.
21 | * - Changed some CSS to be prefixed with jGrowl- to prevent namespacing issues
22 | *
23 | * Changes in 1.2.4
24 | * - Fixed IE bug with the close-all button
25 | * - Fixed IE bug with the filter CSS attribute (special thanks to gotwic)
26 | * - Update IE opacity CSS
27 | * - Changed font sizes to use "em", and only set the base style
28 | *
29 | * Changes in 1.2.3
30 | * - The callbacks no longer use the container as context, instead they use the actual notification
31 | * - The callbacks now receive the container as a parameter after the options parameter
32 | * - beforeOpen and beforeClose now check the return value, if it's false - the notification does
33 | * not continue. The open callback will also halt execution if it returns false.
34 | * - Fixed bug where containers would get confused
35 | * - Expanded the pause functionality to pause an entire container.
36 | *
37 | * Changes in 1.2.2
38 | * - Notification can now be theme rolled for jQuery UI, special thanks to Jeff Chan!
39 | *
40 | * Changes in 1.2.1
41 | * - Fixed instance where the interval would fire the close method multiple times.
42 | * - Added CSS to hide from print media
43 | * - Fixed issue with closer button when div { position: relative } is set
44 | * - Fixed leaking issue with multiple containers. Special thanks to Matthew Hanlon!
45 | *
46 | * Changes in 1.2.0
47 | * - Added message pooling to limit the number of messages appearing at a given time.
48 | * - Closing a notification is now bound to the notification object and triggered by the close button.
49 | *
50 | * Changes in 1.1.2
51 | * - Added iPhone styled example
52 | * - Fixed possible IE7 bug when determining if the ie6 class shoudl be applied.
53 | * - Added template for the close button, so that it's content could be customized.
54 | *
55 | * Changes in 1.1.1
56 | * - Fixed CSS styling bug for ie6 caused by a mispelling
57 | * - Changes height restriction on default notifications to min-height
58 | * - Added skinned examples using a variety of images
59 | * - Added the ability to customize the content of the [close all] box
60 | * - Added jTweet, an example of using jGrowl + Twitter
61 | *
62 | * Changes in 1.1.0
63 | * - Multiple container and instances.
64 | * - Standard $.jGrowl() now wraps $.fn.jGrowl() by first establishing a generic jGrowl container.
65 | * - Instance methods of a jGrowl container can be called by $.fn.jGrowl(methodName)
66 | * - Added glue preferenced, which allows notifications to be inserted before or after nodes in the container
67 | * - Added new log callback which is called before anything is done for the notification
68 | * - Corner's attribute are now applied on an individual notification basis.
69 | *
70 | * Changes in 1.0.4
71 | * - Various CSS fixes so that jGrowl renders correctly in IE6.
72 | *
73 | * Changes in 1.0.3
74 | * - Fixed bug with options persisting across notifications
75 | * - Fixed theme application bug
76 | * - Simplified some selectors and manipulations.
77 | * - Added beforeOpen and beforeClose callbacks
78 | * - Reorganized some lines of code to be more readable
79 | * - Removed unnecessary this.defaults context
80 | * - If corners plugin is present, it's now customizable.
81 | * - Customizable open animation.
82 | * - Customizable close animation.
83 | * - Customizable animation easing.
84 | * - Added customizable positioning (top-left, top-right, bottom-left, bottom-right, center)
85 | *
86 | * Changes in 1.0.2
87 | * - All CSS styling is now external.
88 | * - Added a theme parameter which specifies a secondary class for styling, such
89 | * that notifications can be customized in appearance on a per message basis.
90 | * - Notification life span is now customizable on a per message basis.
91 | * - Added the ability to disable the global closer, enabled by default.
92 | * - Added callbacks for when a notification is opened or closed.
93 | * - Added callback for the global closer.
94 | * - Customizable animation speed.
95 | * - jGrowl now set itself up and tears itself down.
96 | *
97 | * Changes in 1.0.1:
98 | * - Removed dependency on metadata plugin in favor of .data()
99 | * - Namespaced all events
100 | */
101 | (function($) {
102 |
103 | /** jGrowl Wrapper - Establish a base jGrowl Container for compatibility with older releases. **/
104 | $.jGrowl = function( m , o ) {
105 | // To maintain compatibility with older version that only supported one instance we'll create the base container.
106 | if ( $('#jGrowl').size() == 0 )
107 | $('
').addClass( (o && o.position) ? o.position : $.jGrowl.defaults.position ).appendTo('body');
108 |
109 | // Create a notification on the container.
110 | $('#jGrowl').jGrowl(m,o);
111 | };
112 |
113 |
114 | /** Raise jGrowl Notification on a jGrowl Container **/
115 | $.fn.jGrowl = function( m , o ) {
116 | if ( $.isFunction(this.each) ) {
117 | var args = arguments;
118 |
119 | return this.each(function() {
120 | var self = this;
121 |
122 | /** Create a jGrowl Instance on the Container if it does not exist **/
123 | if ( $(this).data('jGrowl.instance') == undefined ) {
124 | $(this).data('jGrowl.instance', $.extend( new $.fn.jGrowl(), { notifications: [], element: null, interval: null } ));
125 | $(this).data('jGrowl.instance').startup( this );
126 | }
127 |
128 | /** Optionally call jGrowl instance methods, or just raise a normal notification **/
129 | if ( $.isFunction($(this).data('jGrowl.instance')[m]) ) {
130 | $(this).data('jGrowl.instance')[m].apply( $(this).data('jGrowl.instance') , $.makeArray(args).slice(1) );
131 | } else {
132 | $(this).data('jGrowl.instance').create( m , o );
133 | }
134 | });
135 | };
136 | };
137 |
138 | $.extend( $.fn.jGrowl.prototype , {
139 |
140 | /** Default JGrowl Settings **/
141 | defaults: {
142 | pool: 0,
143 | header: '',
144 | group: '',
145 | sticky: false,
146 | position: 'top-right',
147 | glue: 'after',
148 | theme: 'default',
149 | themeState: 'highlight',
150 | corners: '10px',
151 | check: 250,
152 | life: 3000,
153 | speed: 'normal',
154 | easing: 'swing',
155 | closer: true,
156 | closeTemplate: '×',
157 | closerTemplate: '[ close all ]
',
158 | log: function(e,m,o) {},
159 | beforeOpen: function(e,m,o) {},
160 | open: function(e,m,o) {},
161 | beforeClose: function(e,m,o) {},
162 | close: function(e,m,o) {},
163 | animateOpen: {
164 | opacity: 'show'
165 | },
166 | animateClose: {
167 | opacity: 'hide'
168 | }
169 | },
170 |
171 | notifications: [],
172 |
173 | /** jGrowl Container Node **/
174 | element: null,
175 |
176 | /** Interval Function **/
177 | interval: null,
178 |
179 | /** Create a Notification **/
180 | create: function( message , o ) {
181 | var o = $.extend({}, this.defaults, o);
182 |
183 | this.notifications.push({ message: message , options: o });
184 |
185 | o.log.apply( this.element , [this.element,message,o] );
186 | },
187 |
188 | render: function( notification ) {
189 | var self = this;
190 | var message = notification.message;
191 | var o = notification.options;
192 |
193 | var notification = $(
194 | '' +
196 | '
' + o.closeTemplate + '
' +
197 | '' +
198 | '
' + message + '
'
199 | ).data("jGrowl", o).addClass(o.theme).children('div.jGrowl-close').bind("click.jGrowl", function() {
200 | $(this).parent().trigger('jGrowl.close');
201 | }).parent();
202 |
203 |
204 | /** Notification Actions **/
205 | $(notification).bind("mouseover.jGrowl", function() {
206 | $('div.jGrowl-notification', self.element).data("jGrowl.pause", true);
207 | }).bind("mouseout.jGrowl", function() {
208 | $('div.jGrowl-notification', self.element).data("jGrowl.pause", false);
209 | }).bind('jGrowl.beforeOpen', function() {
210 | if ( o.beforeOpen.apply( notification , [notification,message,o,self.element] ) != false ) {
211 | $(this).trigger('jGrowl.open');
212 | }
213 | }).bind('jGrowl.open', function() {
214 | if ( o.open.apply( notification , [notification,message,o,self.element] ) != false ) {
215 | if ( o.glue == 'after' ) {
216 | $('div.jGrowl-notification:last', self.element).after(notification);
217 | } else {
218 | $('div.jGrowl-notification:first', self.element).before(notification);
219 | }
220 |
221 | $(this).animate(o.animateOpen, o.speed, o.easing, function() {
222 | // Fixes some anti-aliasing issues with IE filters.
223 | if ($.browser.msie && (parseInt($(this).css('opacity'), 10) === 1 || parseInt($(this).css('opacity'), 10) === 0))
224 | this.style.removeAttribute('filter');
225 |
226 | $(this).data("jGrowl").created = new Date();
227 | });
228 | }
229 | }).bind('jGrowl.beforeClose', function() {
230 | if ( o.beforeClose.apply( notification , [notification,message,o,self.element] ) != false )
231 | $(this).trigger('jGrowl.close');
232 | }).bind('jGrowl.close', function() {
233 | // Pause the notification, lest during the course of animation another close event gets called.
234 | $(this).data('jGrowl.pause', true);
235 | $(this).animate(o.animateClose, o.speed, o.easing, function() {
236 | $(this).remove();
237 | var close = o.close.apply( notification , [notification,message,o,self.element] );
238 |
239 | if ( $.isFunction(close) )
240 | close.apply( notification , [notification,message,o,self.element] );
241 | });
242 | }).trigger('jGrowl.beforeOpen');
243 |
244 | /** Optional Corners Plugin **/
245 | if ( $.fn.corner != undefined ) $(notification).corner( o.corners );
246 |
247 | /** Add a Global Closer if more than one notification exists **/
248 | if ( $('div.jGrowl-notification:parent', self.element).size() > 1 &&
249 | $('div.jGrowl-closer', self.element).size() == 0 && this.defaults.closer != false ) {
250 | $(this.defaults.closerTemplate).addClass('jGrowl-closer ui-state-highlight ui-corner-all').addClass(this.defaults.theme)
251 | .appendTo(self.element).animate(this.defaults.animateOpen, this.defaults.speed, this.defaults.easing)
252 | .bind("click.jGrowl", function() {
253 | $(this).siblings().children('div.close').trigger("click.jGrowl");
254 |
255 | if ( $.isFunction( self.defaults.closer ) ) {
256 | self.defaults.closer.apply( $(this).parent()[0] , [$(this).parent()[0]] );
257 | }
258 | });
259 | };
260 | },
261 |
262 | /** Update the jGrowl Container, removing old jGrowl notifications **/
263 | update: function() {
264 | $(this.element).find('div.jGrowl-notification:parent').each( function() {
265 | if ( $(this).data("jGrowl") != undefined && $(this).data("jGrowl").created != undefined &&
266 | ($(this).data("jGrowl").created.getTime() + $(this).data("jGrowl").life) < (new Date()).getTime() &&
267 | $(this).data("jGrowl").sticky != true &&
268 | ($(this).data("jGrowl.pause") == undefined || $(this).data("jGrowl.pause") != true) ) {
269 |
270 | // Pause the notification, lest during the course of animation another close event gets called.
271 | $(this).trigger('jGrowl.beforeClose');
272 | }
273 | });
274 |
275 | if ( this.notifications.length > 0 &&
276 | (this.defaults.pool == 0 || $(this.element).find('div.jGrowl-notification:parent').size() < this.defaults.pool) )
277 | this.render( this.notifications.shift() );
278 |
279 | if ( $(this.element).find('div.jGrowl-notification:parent').size() < 2 ) {
280 | $(this.element).find('div.jGrowl-closer').animate(this.defaults.animateClose, this.defaults.speed, this.defaults.easing, function() {
281 | $(this).remove();
282 | });
283 | }
284 | },
285 |
286 | /** Setup the jGrowl Notification Container **/
287 | startup: function(e) {
288 | this.element = $(e).addClass('jGrowl').append('
');
289 | this.interval = setInterval( function() {
290 | $(e).data('jGrowl.instance').update();
291 | }, this.defaults.check);
292 |
293 | if ($.browser.msie && parseInt($.browser.version) < 7 && !window["XMLHttpRequest"]) {
294 | $(this.element).addClass('ie6');
295 | }
296 | },
297 |
298 | /** Shutdown jGrowl, removing it and clearing the interval **/
299 | shutdown: function() {
300 | $(this.element).removeClass('jGrowl').find('div.jGrowl-notification').remove();
301 | clearInterval( this.interval );
302 | },
303 |
304 | close: function() {
305 | $(this.element).find('div.jGrowl-notification').each(function(){
306 | $(this).trigger('jGrowl.beforeClose');
307 | });
308 | }
309 | });
310 |
311 | /** Reference the Defaults Object for compatibility with older versions of jGrowl **/
312 | $.jGrowl.defaults = $.fn.jGrowl.prototype.defaults;
313 |
314 | })(jQuery);
315 |
--------------------------------------------------------------------------------
/public/Jcrop/jquery.Jcrop.min.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Jcrop v.0.9.8 (minimized)
3 | * (c) 2008 Kelly Hallman and DeepLiquid.com
4 | * More information: http://deepliquid.com/content/Jcrop.html
5 | * Released under MIT License - this header must remain with code
6 | */
7 |
8 |
9 | (function($){$.Jcrop=function(obj,opt)
10 | {var obj=obj,opt=opt;if(typeof(obj)!=='object')obj=$(obj)[0];if(typeof(opt)!=='object')opt={};if(!('trackDocument'in opt))
11 | {opt.trackDocument=$.browser.msie?false:true;if($.browser.msie&&$.browser.version.split('.')[0]=='8')
12 | opt.trackDocument=true;}
13 | if(!('keySupport'in opt))
14 | opt.keySupport=$.browser.msie?false:true;var defaults={trackDocument:false,baseClass:'jcrop',addClass:null,bgColor:'black',bgOpacity:.6,borderOpacity:.4,handleOpacity:.5,handlePad:5,handleSize:9,handleOffset:5,edgeMargin:14,aspectRatio:0,keySupport:true,cornerHandles:true,sideHandles:true,drawBorders:true,dragEdges:true,boxWidth:0,boxHeight:0,boundary:8,animationDelay:20,swingSpeed:3,allowSelect:true,allowMove:true,allowResize:true,minSelect:[0,0],maxSize:[0,0],minSize:[0,0],onChange:function(){},onSelect:function(){}};var options=defaults;setOptions(opt);var $origimg=$(obj);var $img=$origimg.clone().removeAttr('id').css({position:'absolute'});$img.width($origimg.width());$img.height($origimg.height());$origimg.after($img).hide();presize($img,options.boxWidth,options.boxHeight);var boundx=$img.width(),boundy=$img.height(),$div=$('
').width(boundx).height(boundy).addClass(cssClass('holder')).css({position:'relative',backgroundColor:options.bgColor}).insertAfter($origimg).append($img);;if(options.addClass)$div.addClass(options.addClass);var $img2=$(' ').attr('src',$img.attr('src')).css('position','absolute').width(boundx).height(boundy);var $img_holder=$('
').width(pct(100)).height(pct(100)).css({zIndex:310,position:'absolute',overflow:'hidden'}).append($img2);var $hdl_holder=$('
').width(pct(100)).height(pct(100)).css('zIndex',320);var $sel=$('
').css({position:'absolute',zIndex:300}).insertBefore($img).append($img_holder,$hdl_holder);var bound=options.boundary;var $trk=newTracker().width(boundx+(bound*2)).height(boundy+(bound*2)).css({position:'absolute',top:px(-bound),left:px(-bound),zIndex:290}).mousedown(newSelection);var xlimit,ylimit,xmin,ymin;var xscale,yscale,enabled=true;var docOffset=getPos($img),btndown,lastcurs,dimmed,animating,shift_down;var Coords=function()
15 | {var x1=0,y1=0,x2=0,y2=0,ox,oy;function setPressed(pos)
16 | {var pos=rebound(pos);x2=x1=pos[0];y2=y1=pos[1];};function setCurrent(pos)
17 | {var pos=rebound(pos);ox=pos[0]-x2;oy=pos[1]-y2;x2=pos[0];y2=pos[1];};function getOffset()
18 | {return[ox,oy];};function moveOffset(offset)
19 | {var ox=offset[0],oy=offset[1];if(0>x1+ox)ox-=ox+x1;if(0>y1+oy)oy-=oy+y1;if(boundyboundx)
28 | {xx=boundx;h=Math.abs((xx-x1)/aspect);yy=rh<0?y1-h:h+y1;}}
29 | else
30 | {xx=x2;h=rwa/aspect;yy=rh<0?y1-h:y1+h;if(yy<0)
31 | {yy=0;w=Math.abs((yy-y1)*aspect);xx=rw<0?x1-w:w+x1;}
32 | else if(yy>boundy)
33 | {yy=boundy;w=Math.abs(yy-y1)*aspect;xx=rw<0?x1-w:w+x1;}}
34 | if(xx>x1){if(xx-x1max_x){xx=x1+max_x;}
35 | if(yy>y1){yy=y1+(xx-x1)/aspect;}else{yy=y1-(xx-x1)/aspect;}}else if(xxmax_x){xx=x1-max_x;}
36 | if(yy>y1){yy=y1+(x1-xx)/aspect;}else{yy=y1-(x1-xx)/aspect;}}
37 | if(xx<0){x1-=xx;xx=0;}else if(xx>boundx){x1-=xx-boundx;xx=boundx;}
38 | if(yy<0){y1-=yy;yy=0;}else if(yy>boundy){y1-=yy-boundy;yy=boundy;}
39 | return last=makeObj(flipCoords(x1,y1,xx,yy));};function rebound(p)
40 | {if(p[0]<0)p[0]=0;if(p[1]<0)p[1]=0;if(p[0]>boundx)p[0]=boundx;if(p[1]>boundy)p[1]=boundy;return[p[0],p[1]];};function flipCoords(x1,y1,x2,y2)
41 | {var xa=x1,xb=x2,ya=y1,yb=y2;if(x2xlimit))
47 | x2=(xsize>0)?(x1+xlimit):(x1-xlimit);if(ylimit&&(Math.abs(ysize)>ylimit))
48 | y2=(ysize>0)?(y1+ylimit):(y1-ylimit);if(ymin&&(Math.abs(ysize)0)?(y1+ymin):(y1-ymin);if(xmin&&(Math.abs(xsize)0)?(x1+xmin):(x1-xmin);if(x1<0){x2-=x1;x1-=x1;}
51 | if(y1<0){y2-=y1;y1-=y1;}
52 | if(x2<0){x1-=x2;x2-=x2;}
53 | if(y2<0){y1-=y2;y2-=y2;}
54 | if(x2>boundx){var delta=x2-boundx;x1-=delta;x2-=delta;}
55 | if(y2>boundy){var delta=y2-boundy;y1-=delta;y2-=delta;}
56 | if(x1>boundx){var delta=x1-boundy;y2-=delta;y1-=delta;}
57 | if(y1>boundy){var delta=y1-boundy;y2-=delta;y1-=delta;}
58 | return makeObj(flipCoords(x1,y1,x2,y2));};function makeObj(a)
59 | {return{x:a[0],y:a[1],x2:a[2],y2:a[3],w:a[2]-a[0],h:a[3]-a[1]};};return{flipCoords:flipCoords,setPressed:setPressed,setCurrent:setCurrent,getOffset:getOffset,moveOffset:moveOffset,getCorner:getCorner,getFixed:getFixed};}();var Selection=function()
60 | {var start,end,dragmode,awake,hdep=370;var borders={};var handle={};var seehandles=false;var hhs=options.handleOffset;if(options.drawBorders){borders={top:insertBorder('hline').css('top',$.browser.msie?px(-1):px(0)),bottom:insertBorder('hline'),left:insertBorder('vline'),right:insertBorder('vline')};}
61 | if(options.dragEdges){handle.t=insertDragbar('n');handle.b=insertDragbar('s');handle.r=insertDragbar('e');handle.l=insertDragbar('w');}
62 | options.sideHandles&&createHandles(['n','s','e','w']);options.cornerHandles&&createHandles(['sw','nw','ne','se']);function insertBorder(type)
63 | {var jq=$('
').css({position:'absolute',opacity:options.borderOpacity}).addClass(cssClass(type));$img_holder.append(jq);return jq;};function dragDiv(ord,zi)
64 | {var jq=$('
').mousedown(createDragger(ord)).css({cursor:ord+'-resize',position:'absolute',zIndex:zi});$hdl_holder.append(jq);return jq;};function insertHandle(ord)
65 | {return dragDiv(ord,hdep++).css({top:px(-hhs+1),left:px(-hhs+1),opacity:options.handleOpacity}).addClass(cssClass('handle'));};function insertDragbar(ord)
66 | {var s=options.handleSize,o=hhs,h=s,w=s,t=o,l=o;switch(ord)
67 | {case'n':case's':w=pct(100);break;case'e':case'w':h=pct(100);break;}
68 | return dragDiv(ord,hdep++).width(w).height(h).css({top:px(-t+1),left:px(-l+1)});};function createHandles(li)
69 | {for(i in li)handle[li[i]]=insertHandle(li[i]);};function moveHandles(c)
70 | {var midvert=Math.round((c.h/2)-hhs),midhoriz=Math.round((c.w/2)-hhs),north=west=-hhs+1,east=c.w-hhs,south=c.h-hhs,x,y;'e'in handle&&handle.e.css({top:px(midvert),left:px(east)})&&handle.w.css({top:px(midvert)})&&handle.s.css({top:px(south),left:px(midhoriz)})&&handle.n.css({left:px(midhoriz)});'ne'in handle&&handle.ne.css({left:px(east)})&&handle.se.css({top:px(south),left:px(east)})&&handle.sw.css({top:px(south)});'b'in handle&&handle.b.css({top:px(south)})&&handle.r.css({left:px(east)});};function moveto(x,y)
71 | {$img2.css({top:px(-y),left:px(-x)});$sel.css({top:px(y),left:px(x)});};function resize(w,h)
72 | {$sel.width(w).height(h);};function refresh()
73 | {var c=Coords.getFixed();Coords.setPressed([c.x,c.y]);Coords.setCurrent([c.x2,c.y2]);updateVisible();};function updateVisible()
74 | {if(awake)return update();};function update()
75 | {var c=Coords.getFixed();resize(c.w,c.h);moveto(c.x,c.y);options.drawBorders&&borders['right'].css({left:px(c.w-1)})&&borders['bottom'].css({top:px(c.h-1)});seehandles&&moveHandles(c);awake||show();options.onChange(unscale(c));};function show()
76 | {$sel.show();$img.css('opacity',options.bgOpacity);awake=true;};function release()
77 | {disableHandles();$sel.hide();$img.css('opacity',1);awake=false;};function showHandles()
78 | {if(seehandles)
79 | {moveHandles(Coords.getFixed());$hdl_holder.show();}};function enableHandles()
80 | {seehandles=true;if(options.allowResize)
81 | {moveHandles(Coords.getFixed());$hdl_holder.show();return true;}};function disableHandles()
82 | {seehandles=false;$hdl_holder.hide();};function animMode(v)
83 | {(animating=v)?disableHandles():enableHandles();};function done()
84 | {animMode(false);refresh();};var $track=newTracker().mousedown(createDragger('move')).css({cursor:'move',position:'absolute',zIndex:360})
85 | $img_holder.append($track);disableHandles();return{updateVisible:updateVisible,update:update,release:release,refresh:refresh,setCursor:function(cursor){$track.css('cursor',cursor);},enableHandles:enableHandles,enableOnly:function(){seehandles=true;},showHandles:showHandles,disableHandles:disableHandles,animMode:animMode,done:done};}();var Tracker=function()
86 | {var onMove=function(){},onDone=function(){},trackDoc=options.trackDocument;if(!trackDoc)
87 | {$trk.mousemove(trackMove).mouseup(trackUp).mouseout(trackUp);}
88 | function toFront()
89 | {$trk.css({zIndex:450});if(trackDoc)
90 | {$(document).mousemove(trackMove).mouseup(trackUp);}}
91 | function toBack()
92 | {$trk.css({zIndex:290});if(trackDoc)
93 | {$(document).unbind('mousemove',trackMove).unbind('mouseup',trackUp);}}
94 | function trackMove(e)
95 | {onMove(mouseAbs(e));};function trackUp(e)
96 | {e.preventDefault();e.stopPropagation();if(btndown)
97 | {btndown=false;onDone(mouseAbs(e));options.onSelect(unscale(Coords.getFixed()));toBack();onMove=function(){};onDone=function(){};}
98 | return false;};function activateHandlers(move,done)
99 | {btndown=true;onMove=move;onDone=done;toFront();return false;};function setCursor(t){$trk.css('cursor',t);};$img.before($trk);return{activateHandlers:activateHandlers,setCursor:setCursor};}();var KeyManager=function()
100 | {var $keymgr=$(' ').css({position:'absolute',left:'-30px'}).keypress(parseKey).blur(onBlur),$keywrap=$('
').css({position:'absolute',overflow:'hidden'}).append($keymgr);function watchKeys()
101 | {if(options.keySupport)
102 | {$keymgr.show();$keymgr.focus();}};function onBlur(e)
103 | {$keymgr.hide();};function doNudge(e,x,y)
104 | {if(options.allowMove){Coords.moveOffset([x,y]);Selection.updateVisible();};e.preventDefault();e.stopPropagation();};function parseKey(e)
105 | {if(e.ctrlKey)return true;shift_down=e.shiftKey?true:false;var nudge=shift_down?10:1;switch(e.keyCode)
106 | {case 37:doNudge(e,-nudge,0);break;case 39:doNudge(e,nudge,0);break;case 38:doNudge(e,0,-nudge);break;case 40:doNudge(e,0,nudge);break;case 27:Selection.release();break;case 9:return true;}
107 | return nothing(e);};if(options.keySupport)$keywrap.insertBefore($img);return{watchKeys:watchKeys};}();function px(n){return''+parseInt(n)+'px';};function pct(n){return''+parseInt(n)+'%';};function cssClass(cl){return options.baseClass+'-'+cl;};function getPos(obj)
108 | {var pos=$(obj).offset();return[pos.left,pos.top];};function mouseAbs(e)
109 | {return[(e.pageX-docOffset[0]),(e.pageY-docOffset[1])];};function myCursor(type)
110 | {if(type!=lastcurs)
111 | {Tracker.setCursor(type);lastcurs=type;}};function startDragMode(mode,pos)
112 | {docOffset=getPos($img);Tracker.setCursor(mode=='move'?mode:mode+'-resize');if(mode=='move')
113 | return Tracker.activateHandlers(createMover(pos),doneSelect);var fc=Coords.getFixed();var opp=oppLockCorner(mode);var opc=Coords.getCorner(oppLockCorner(opp));Coords.setPressed(Coords.getCorner(opp));Coords.setCurrent(opc);Tracker.activateHandlers(dragmodeHandler(mode,fc),doneSelect);};function dragmodeHandler(mode,f)
114 | {return function(pos){if(!options.aspectRatio)switch(mode)
115 | {case'e':pos[1]=f.y2;break;case'w':pos[1]=f.y2;break;case'n':pos[0]=f.x2;break;case's':pos[0]=f.x2;break;}
116 | else switch(mode)
117 | {case'e':pos[1]=f.y+1;break;case'w':pos[1]=f.y+1;break;case'n':pos[0]=f.x+1;break;case's':pos[0]=f.x+1;break;}
118 | Coords.setCurrent(pos);Selection.update();};};function createMover(pos)
119 | {var lloc=pos;KeyManager.watchKeys();return function(pos)
120 | {Coords.moveOffset([pos[0]-lloc[0],pos[1]-lloc[1]]);lloc=pos;Selection.update();};};function oppLockCorner(ord)
121 | {switch(ord)
122 | {case'n':return'sw';case's':return'nw';case'e':return'nw';case'w':return'ne';case'ne':return'sw';case'nw':return'se';case'se':return'nw';case'sw':return'ne';};};function createDragger(ord)
123 | {return function(e){if(options.disabled)return false;if((ord=='move')&&!options.allowMove)return false;btndown=true;startDragMode(ord,mouseAbs(e));e.stopPropagation();e.preventDefault();return false;};};function presize($obj,w,h)
124 | {var nw=$obj.width(),nh=$obj.height();if((nw>w)&&w>0)
125 | {nw=w;nh=(w/$obj.width())*$obj.height();}
126 | if((nh>h)&&h>0)
127 | {nh=h;nw=(h/$obj.height())*$obj.width();}
128 | xscale=$obj.width()/nw;yscale=$obj.height()/nh;$obj.width(nw).height(nh);};function unscale(c)
129 | {return{x:parseInt(c.x*xscale),y:parseInt(c.y*yscale),x2:parseInt(c.x2*xscale),y2:parseInt(c.y2*yscale),w:parseInt(c.w*xscale),h:parseInt(c.h*yscale)};};function doneSelect(pos)
130 | {var c=Coords.getFixed();if(c.w>options.minSelect[0]&&c.h>options.minSelect[1])
131 | {Selection.enableHandles();Selection.done();}
132 | else
133 | {Selection.release();}
134 | Tracker.setCursor(options.allowSelect?'crosshair':'default');};function newSelection(e)
135 | {if(options.disabled)return false;if(!options.allowSelect)return false;btndown=true;docOffset=getPos($img);Selection.disableHandles();myCursor('crosshair');var pos=mouseAbs(e);Coords.setPressed(pos);Tracker.activateHandlers(selectDrag,doneSelect);KeyManager.watchKeys();Selection.update();e.stopPropagation();e.preventDefault();return false;};function selectDrag(pos)
136 | {Coords.setCurrent(pos);Selection.update();};function newTracker()
137 | {var trk=$('
').addClass(cssClass('tracker'));$.browser.msie&&trk.css({opacity:0,backgroundColor:'white'});return trk;};function animateTo(a)
138 | {var x1=a[0]/xscale,y1=a[1]/yscale,x2=a[2]/xscale,y2=a[3]/yscale;if(animating)return;var animto=Coords.flipCoords(x1,y1,x2,y2);var c=Coords.getFixed();var animat=initcr=[c.x,c.y,c.x2,c.y2];var interv=options.animationDelay;var x=animat[0];var y=animat[1];var x2=animat[2];var y2=animat[3];var ix1=animto[0]-initcr[0];var iy1=animto[1]-initcr[1];var ix2=animto[2]-initcr[2];var iy2=animto[3]-initcr[3];var pcent=0;var velocity=options.swingSpeed;Selection.animMode(true);var animator=function()
139 | {return function()
140 | {pcent+=(100-pcent)/velocity;animat[0]=x+((pcent/100)*ix1);animat[1]=y+((pcent/100)*iy1);animat[2]=x2+((pcent/100)*ix2);animat[3]=y2+((pcent/100)*iy2);if(pcent<100)animateStart();else Selection.done();if(pcent>=99.8)pcent=100;setSelectRaw(animat);};}();function animateStart()
141 | {window.setTimeout(animator,interv);};animateStart();};function setSelect(rect)
142 | {setSelectRaw([rect[0]/xscale,rect[1]/yscale,rect[2]/xscale,rect[3]/yscale]);};function setSelectRaw(l)
143 | {Coords.setPressed([l[0],l[1]]);Coords.setCurrent([l[2],l[3]]);Selection.update();};function setOptions(opt)
144 | {if(typeof(opt)!='object')opt={};options=$.extend(options,opt);if(typeof(options.onChange)!=='function')
145 | options.onChange=function(){};if(typeof(options.onSelect)!=='function')
146 | options.onSelect=function(){};};function tellSelect()
147 | {return unscale(Coords.getFixed());};function tellScaled()
148 | {return Coords.getFixed();};function setOptionsNew(opt)
149 | {setOptions(opt);interfaceUpdate();};function disableCrop()
150 | {options.disabled=true;Selection.disableHandles();Selection.setCursor('default');Tracker.setCursor('default');};function enableCrop()
151 | {options.disabled=false;interfaceUpdate();};function cancelCrop()
152 | {Selection.done();Tracker.activateHandlers(null,null);};function destroy()
153 | {$div.remove();$origimg.show();};function interfaceUpdate(alt)
154 | {options.allowResize?alt?Selection.enableOnly():Selection.enableHandles():Selection.disableHandles();Tracker.setCursor(options.allowSelect?'crosshair':'default');Selection.setCursor(options.allowMove?'move':'default');$div.css('backgroundColor',options.bgColor);if('setSelect'in options){setSelect(opt.setSelect);Selection.done();delete(options.setSelect);}
155 | if('trueSize'in options){xscale=options.trueSize[0]/boundx;yscale=options.trueSize[1]/boundy;}
156 | xlimit=options.maxSize[0]||0;ylimit=options.maxSize[1]||0;xmin=options.minSize[0]||0;ymin=options.minSize[1]||0;if('outerImage'in options)
157 | {$img.attr('src',options.outerImage);delete(options.outerImage);}
158 | Selection.refresh();};$hdl_holder.hide();interfaceUpdate(true);var api={animateTo:animateTo,setSelect:setSelect,setOptions:setOptionsNew,tellSelect:tellSelect,tellScaled:tellScaled,disable:disableCrop,enable:enableCrop,cancel:cancelCrop,focus:KeyManager.watchKeys,getBounds:function(){return[boundx*xscale,boundy*yscale];},getWidgetSize:function(){return[boundx,boundy];},release:Selection.release,destroy:destroy};$origimg.data('Jcrop',api);return api;};$.fn.Jcrop=function(options)
159 | {function attachWhenDone(from)
160 | {var loadsrc=options.useImg||from.src;var img=new Image();img.onload=function(){$.Jcrop(from,options);};img.src=loadsrc;};if(typeof(options)!=='object')options={};this.each(function()
161 | {if($(this).data('Jcrop'))
162 | {if(options=='api')return $(this).data('Jcrop');else $(this).data('Jcrop').setOptions(options);}
163 | else attachWhenDone(this);});return this;};})(jQuery);
--------------------------------------------------------------------------------
/public/Jcrop/jquery.Jcrop.js:
--------------------------------------------------------------------------------
1 | /**
2 | * jquery.Jcrop.js v0.9.8
3 | * jQuery Image Cropping Plugin
4 | * @author Kelly Hallman
5 | * Copyright (c) 2008-2009 Kelly Hallman - released under MIT License {{{
6 | *
7 | * Permission is hereby granted, free of charge, to any person
8 | * obtaining a copy of this software and associated documentation
9 | * files (the "Software"), to deal in the Software without
10 | * restriction, including without limitation the rights to use,
11 | * copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | * copies of the Software, and to permit persons to whom the
13 | * Software is furnished to do so, subject to the following
14 | * conditions:
15 |
16 | * The above copyright notice and this permission notice shall be
17 | * included in all copies or substantial portions of the Software.
18 |
19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
21 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
22 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
23 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
24 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
26 | * OTHER DEALINGS IN THE SOFTWARE.
27 |
28 | * }}}
29 | */
30 |
31 | (function($) {
32 |
33 | $.Jcrop = function(obj,opt)
34 | {
35 | // Initialization {{{
36 |
37 | // Sanitize some options {{{
38 | var obj = obj, opt = opt;
39 |
40 | if (typeof(obj) !== 'object') obj = $(obj)[0];
41 | if (typeof(opt) !== 'object') opt = { };
42 |
43 | // Some on-the-fly fixes for MSIE...sigh
44 | if (!('trackDocument' in opt))
45 | {
46 | opt.trackDocument = $.browser.msie ? false : true;
47 | if ($.browser.msie && $.browser.version.split('.')[0] == '8')
48 | opt.trackDocument = true;
49 | }
50 |
51 | if (!('keySupport' in opt))
52 | opt.keySupport = $.browser.msie ? false : true;
53 |
54 | // }}}
55 | // Extend the default options {{{
56 | var defaults = {
57 |
58 | // Basic Settings
59 | trackDocument: false,
60 | baseClass: 'jcrop',
61 | addClass: null,
62 |
63 | // Styling Options
64 | bgColor: 'black',
65 | bgOpacity: .6,
66 | borderOpacity: .4,
67 | handleOpacity: .5,
68 |
69 | handlePad: 5,
70 | handleSize: 9,
71 | handleOffset: 5,
72 | edgeMargin: 14,
73 |
74 | aspectRatio: 0,
75 | keySupport: true,
76 | cornerHandles: true,
77 | sideHandles: true,
78 | drawBorders: true,
79 | dragEdges: true,
80 |
81 | boxWidth: 0,
82 | boxHeight: 0,
83 |
84 | boundary: 8,
85 | animationDelay: 20,
86 | swingSpeed: 3,
87 |
88 | allowSelect: true,
89 | allowMove: true,
90 | allowResize: true,
91 |
92 | minSelect: [ 0, 0 ],
93 | maxSize: [ 0, 0 ],
94 | minSize: [ 0, 0 ],
95 |
96 | // Callbacks / Event Handlers
97 | onChange: function() { },
98 | onSelect: function() { }
99 |
100 | };
101 | var options = defaults;
102 | setOptions(opt);
103 |
104 | // }}}
105 | // Initialize some jQuery objects {{{
106 |
107 | var $origimg = $(obj);
108 | var $img = $origimg.clone().removeAttr('id').css({ position: 'absolute' });
109 |
110 | $img.width($origimg.width());
111 | $img.height($origimg.height());
112 | $origimg.after($img).hide();
113 |
114 | presize($img,options.boxWidth,options.boxHeight);
115 |
116 | var boundx = $img.width(),
117 | boundy = $img.height(),
118 |
119 | $div = $('
')
120 | .width(boundx).height(boundy)
121 | .addClass(cssClass('holder'))
122 | .css({
123 | position: 'relative',
124 | backgroundColor: options.bgColor
125 | }).insertAfter($origimg).append($img);
126 | ;
127 |
128 | if (options.addClass) $div.addClass(options.addClass);
129 | //$img.wrap($div);
130 |
131 | var $img2 = $(' ')/*{{{*/
132 | .attr('src',$img.attr('src'))
133 | .css('position','absolute')
134 | .width(boundx).height(boundy)
135 | ;/*}}}*/
136 | var $img_holder = $('
')/*{{{*/
137 | .width(pct(100)).height(pct(100))
138 | .css({
139 | zIndex: 310,
140 | position: 'absolute',
141 | overflow: 'hidden'
142 | })
143 | .append($img2)
144 | ;/*}}}*/
145 | var $hdl_holder = $('
')/*{{{*/
146 | .width(pct(100)).height(pct(100))
147 | .css('zIndex',320);
148 | /*}}}*/
149 | var $sel = $('
')/*{{{*/
150 | .css({
151 | position: 'absolute',
152 | zIndex: 300
153 | })
154 | .insertBefore($img)
155 | .append($img_holder,$hdl_holder)
156 | ;/*}}}*/
157 |
158 | var bound = options.boundary;
159 | var $trk = newTracker().width(boundx+(bound*2)).height(boundy+(bound*2))
160 | .css({ position: 'absolute', top: px(-bound), left: px(-bound), zIndex: 290 })
161 | .mousedown(newSelection);
162 |
163 | /* }}} */
164 | // Set more variables {{{
165 |
166 | var xlimit, ylimit, xmin, ymin;
167 | var xscale, yscale, enabled = true;
168 | var docOffset = getPos($img),
169 | // Internal states
170 | btndown, lastcurs, dimmed, animating,
171 | shift_down;
172 |
173 | // }}}
174 |
175 |
176 | // }}}
177 | // Internal Modules {{{
178 |
179 | var Coords = function()/*{{{*/
180 | {
181 | var x1 = 0, y1 = 0, x2 = 0, y2 = 0, ox, oy;
182 |
183 | function setPressed(pos)/*{{{*/
184 | {
185 | var pos = rebound(pos);
186 | x2 = x1 = pos[0];
187 | y2 = y1 = pos[1];
188 | };
189 | /*}}}*/
190 | function setCurrent(pos)/*{{{*/
191 | {
192 | var pos = rebound(pos);
193 | ox = pos[0] - x2;
194 | oy = pos[1] - y2;
195 | x2 = pos[0];
196 | y2 = pos[1];
197 | };
198 | /*}}}*/
199 | function getOffset()/*{{{*/
200 | {
201 | return [ ox, oy ];
202 | };
203 | /*}}}*/
204 | function moveOffset(offset)/*{{{*/
205 | {
206 | var ox = offset[0], oy = offset[1];
207 |
208 | if (0 > x1 + ox) ox -= ox + x1;
209 | if (0 > y1 + oy) oy -= oy + y1;
210 |
211 | if (boundy < y2 + oy) oy += boundy - (y2 + oy);
212 | if (boundx < x2 + ox) ox += boundx - (x2 + ox);
213 |
214 | x1 += ox;
215 | x2 += ox;
216 | y1 += oy;
217 | y2 += oy;
218 | };
219 | /*}}}*/
220 | function getCorner(ord)/*{{{*/
221 | {
222 | var c = getFixed();
223 | switch(ord)
224 | {
225 | case 'ne': return [ c.x2, c.y ];
226 | case 'nw': return [ c.x, c.y ];
227 | case 'se': return [ c.x2, c.y2 ];
228 | case 'sw': return [ c.x, c.y2 ];
229 | }
230 | };
231 | /*}}}*/
232 | function getFixed()/*{{{*/
233 | {
234 | if (!options.aspectRatio) return getRect();
235 | // This function could use some optimization I think...
236 | var aspect = options.aspectRatio,
237 | min_x = options.minSize[0]/xscale,
238 | min_y = options.minSize[1]/yscale,
239 | max_x = options.maxSize[0]/xscale,
240 | max_y = options.maxSize[1]/yscale,
241 | rw = x2 - x1,
242 | rh = y2 - y1,
243 | rwa = Math.abs(rw),
244 | rha = Math.abs(rh),
245 | real_ratio = rwa / rha,
246 | xx, yy
247 | ;
248 | if (max_x == 0) { max_x = boundx * 10 }
249 | if (max_y == 0) { max_y = boundy * 10 }
250 | if (real_ratio < aspect)
251 | {
252 | yy = y2;
253 | w = rha * aspect;
254 | xx = rw < 0 ? x1 - w : w + x1;
255 |
256 | if (xx < 0)
257 | {
258 | xx = 0;
259 | h = Math.abs((xx - x1) / aspect);
260 | yy = rh < 0 ? y1 - h: h + y1;
261 | }
262 | else if (xx > boundx)
263 | {
264 | xx = boundx;
265 | h = Math.abs((xx - x1) / aspect);
266 | yy = rh < 0 ? y1 - h : h + y1;
267 | }
268 | }
269 | else
270 | {
271 | xx = x2;
272 | h = rwa / aspect;
273 | yy = rh < 0 ? y1 - h : y1 + h;
274 | if (yy < 0)
275 | {
276 | yy = 0;
277 | w = Math.abs((yy - y1) * aspect);
278 | xx = rw < 0 ? x1 - w : w + x1;
279 | }
280 | else if (yy > boundy)
281 | {
282 | yy = boundy;
283 | w = Math.abs(yy - y1) * aspect;
284 | xx = rw < 0 ? x1 - w : w + x1;
285 | }
286 | }
287 |
288 | // Magic %-)
289 | if(xx > x1) { // right side
290 | if(xx - x1 < min_x) {
291 | xx = x1 + min_x;
292 | } else if (xx - x1 > max_x) {
293 | xx = x1 + max_x;
294 | }
295 | if(yy > y1) {
296 | yy = y1 + (xx - x1)/aspect;
297 | } else {
298 | yy = y1 - (xx - x1)/aspect;
299 | }
300 | } else if (xx < x1) { // left side
301 | if(x1 - xx < min_x) {
302 | xx = x1 - min_x
303 | } else if (x1 - xx > max_x) {
304 | xx = x1 - max_x;
305 | }
306 | if(yy > y1) {
307 | yy = y1 + (x1 - xx)/aspect;
308 | } else {
309 | yy = y1 - (x1 - xx)/aspect;
310 | }
311 | }
312 |
313 | if(xx < 0) {
314 | x1 -= xx;
315 | xx = 0;
316 | } else if (xx > boundx) {
317 | x1 -= xx - boundx;
318 | xx = boundx;
319 | }
320 |
321 | if(yy < 0) {
322 | y1 -= yy;
323 | yy = 0;
324 | } else if (yy > boundy) {
325 | y1 -= yy - boundy;
326 | yy = boundy;
327 | }
328 |
329 | return last = makeObj(flipCoords(x1,y1,xx,yy));
330 | };
331 | /*}}}*/
332 | function rebound(p)/*{{{*/
333 | {
334 | if (p[0] < 0) p[0] = 0;
335 | if (p[1] < 0) p[1] = 0;
336 |
337 | if (p[0] > boundx) p[0] = boundx;
338 | if (p[1] > boundy) p[1] = boundy;
339 |
340 | return [ p[0], p[1] ];
341 | };
342 | /*}}}*/
343 | function flipCoords(x1,y1,x2,y2)/*{{{*/
344 | {
345 | var xa = x1, xb = x2, ya = y1, yb = y2;
346 | if (x2 < x1)
347 | {
348 | xa = x2;
349 | xb = x1;
350 | }
351 | if (y2 < y1)
352 | {
353 | ya = y2;
354 | yb = y1;
355 | }
356 | return [ Math.round(xa), Math.round(ya), Math.round(xb), Math.round(yb) ];
357 | };
358 | /*}}}*/
359 | function getRect()/*{{{*/
360 | {
361 | var xsize = x2 - x1;
362 | var ysize = y2 - y1;
363 |
364 | if (xlimit && (Math.abs(xsize) > xlimit))
365 | x2 = (xsize > 0) ? (x1 + xlimit) : (x1 - xlimit);
366 | if (ylimit && (Math.abs(ysize) > ylimit))
367 | y2 = (ysize > 0) ? (y1 + ylimit) : (y1 - ylimit);
368 |
369 | if (ymin && (Math.abs(ysize) < ymin))
370 | y2 = (ysize > 0) ? (y1 + ymin) : (y1 - ymin);
371 | if (xmin && (Math.abs(xsize) < xmin))
372 | x2 = (xsize > 0) ? (x1 + xmin) : (x1 - xmin);
373 |
374 | if (x1 < 0) { x2 -= x1; x1 -= x1; }
375 | if (y1 < 0) { y2 -= y1; y1 -= y1; }
376 | if (x2 < 0) { x1 -= x2; x2 -= x2; }
377 | if (y2 < 0) { y1 -= y2; y2 -= y2; }
378 | if (x2 > boundx) { var delta = x2 - boundx; x1 -= delta; x2 -= delta; }
379 | if (y2 > boundy) { var delta = y2 - boundy; y1 -= delta; y2 -= delta; }
380 | if (x1 > boundx) { var delta = x1 - boundy; y2 -= delta; y1 -= delta; }
381 | if (y1 > boundy) { var delta = y1 - boundy; y2 -= delta; y1 -= delta; }
382 |
383 | return makeObj(flipCoords(x1,y1,x2,y2));
384 | };
385 | /*}}}*/
386 | function makeObj(a)/*{{{*/
387 | {
388 | return { x: a[0], y: a[1], x2: a[2], y2: a[3],
389 | w: a[2] - a[0], h: a[3] - a[1] };
390 | };
391 | /*}}}*/
392 |
393 | return {
394 | flipCoords: flipCoords,
395 | setPressed: setPressed,
396 | setCurrent: setCurrent,
397 | getOffset: getOffset,
398 | moveOffset: moveOffset,
399 | getCorner: getCorner,
400 | getFixed: getFixed
401 | };
402 | }();
403 |
404 | /*}}}*/
405 | var Selection = function()/*{{{*/
406 | {
407 | var start, end, dragmode, awake, hdep = 370;
408 | var borders = { };
409 | var handle = { };
410 | var seehandles = false;
411 | var hhs = options.handleOffset;
412 |
413 | /* Insert draggable elements {{{*/
414 |
415 | // Insert border divs for outline
416 | if (options.drawBorders) {
417 | borders = {
418 | top: insertBorder('hline')
419 | .css('top',$.browser.msie?px(-1):px(0)),
420 | bottom: insertBorder('hline'),
421 | left: insertBorder('vline'),
422 | right: insertBorder('vline')
423 | };
424 | }
425 |
426 | // Insert handles on edges
427 | if (options.dragEdges) {
428 | handle.t = insertDragbar('n');
429 | handle.b = insertDragbar('s');
430 | handle.r = insertDragbar('e');
431 | handle.l = insertDragbar('w');
432 | }
433 |
434 | // Insert side handles
435 | options.sideHandles &&
436 | createHandles(['n','s','e','w']);
437 |
438 | // Insert corner handles
439 | options.cornerHandles &&
440 | createHandles(['sw','nw','ne','se']);
441 |
442 | /*}}}*/
443 | // Private Methods
444 | function insertBorder(type)/*{{{*/
445 | {
446 | var jq = $('
')
447 | .css({position: 'absolute', opacity: options.borderOpacity })
448 | .addClass(cssClass(type));
449 | $img_holder.append(jq);
450 | return jq;
451 | };
452 | /*}}}*/
453 | function dragDiv(ord,zi)/*{{{*/
454 | {
455 | var jq = $('
')
456 | .mousedown(createDragger(ord))
457 | .css({
458 | cursor: ord+'-resize',
459 | position: 'absolute',
460 | zIndex: zi
461 | })
462 | ;
463 | $hdl_holder.append(jq);
464 | return jq;
465 | };
466 | /*}}}*/
467 | function insertHandle(ord)/*{{{*/
468 | {
469 | return dragDiv(ord,hdep++)
470 | .css({ top: px(-hhs+1), left: px(-hhs+1), opacity: options.handleOpacity })
471 | .addClass(cssClass('handle'));
472 | };
473 | /*}}}*/
474 | function insertDragbar(ord)/*{{{*/
475 | {
476 | var s = options.handleSize,
477 | o = hhs,
478 | h = s, w = s,
479 | t = o, l = o;
480 |
481 | switch(ord)
482 | {
483 | case 'n': case 's': w = pct(100); break;
484 | case 'e': case 'w': h = pct(100); break;
485 | }
486 |
487 | return dragDiv(ord,hdep++).width(w).height(h)
488 | .css({ top: px(-t+1), left: px(-l+1)});
489 | };
490 | /*}}}*/
491 | function createHandles(li)/*{{{*/
492 | {
493 | for(i in li) handle[li[i]] = insertHandle(li[i]);
494 | };
495 | /*}}}*/
496 | function moveHandles(c)/*{{{*/
497 | {
498 | var midvert = Math.round((c.h / 2) - hhs),
499 | midhoriz = Math.round((c.w / 2) - hhs),
500 | north = west = -hhs+1,
501 | east = c.w - hhs,
502 | south = c.h - hhs,
503 | x, y;
504 |
505 | 'e' in handle &&
506 | handle.e.css({ top: px(midvert), left: px(east) }) &&
507 | handle.w.css({ top: px(midvert) }) &&
508 | handle.s.css({ top: px(south), left: px(midhoriz) }) &&
509 | handle.n.css({ left: px(midhoriz) });
510 |
511 | 'ne' in handle &&
512 | handle.ne.css({ left: px(east) }) &&
513 | handle.se.css({ top: px(south), left: px(east) }) &&
514 | handle.sw.css({ top: px(south) });
515 |
516 | 'b' in handle &&
517 | handle.b.css({ top: px(south) }) &&
518 | handle.r.css({ left: px(east) });
519 | };
520 | /*}}}*/
521 | function moveto(x,y)/*{{{*/
522 | {
523 | $img2.css({ top: px(-y), left: px(-x) });
524 | $sel.css({ top: px(y), left: px(x) });
525 | };
526 | /*}}}*/
527 | function resize(w,h)/*{{{*/
528 | {
529 | $sel.width(w).height(h);
530 | };
531 | /*}}}*/
532 | function refresh()/*{{{*/
533 | {
534 | var c = Coords.getFixed();
535 |
536 | Coords.setPressed([c.x,c.y]);
537 | Coords.setCurrent([c.x2,c.y2]);
538 |
539 | updateVisible();
540 | };
541 | /*}}}*/
542 |
543 | // Internal Methods
544 | function updateVisible()/*{{{*/
545 | { if (awake) return update(); };
546 | /*}}}*/
547 | function update()/*{{{*/
548 | {
549 | var c = Coords.getFixed();
550 |
551 | resize(c.w,c.h);
552 | moveto(c.x,c.y);
553 |
554 | options.drawBorders &&
555 | borders['right'].css({ left: px(c.w-1) }) &&
556 | borders['bottom'].css({ top: px(c.h-1) });
557 |
558 | seehandles && moveHandles(c);
559 | awake || show();
560 |
561 | options.onChange(unscale(c));
562 | };
563 | /*}}}*/
564 | function show()/*{{{*/
565 | {
566 | $sel.show();
567 | $img.css('opacity',options.bgOpacity);
568 | awake = true;
569 | };
570 | /*}}}*/
571 | function release()/*{{{*/
572 | {
573 | disableHandles();
574 | $sel.hide();
575 | $img.css('opacity',1);
576 | awake = false;
577 | };
578 | /*}}}*/
579 | function showHandles()//{{{
580 | {
581 | if (seehandles)
582 | {
583 | moveHandles(Coords.getFixed());
584 | $hdl_holder.show();
585 | }
586 | };
587 | //}}}
588 | function enableHandles()/*{{{*/
589 | {
590 | seehandles = true;
591 | if (options.allowResize)
592 | {
593 | moveHandles(Coords.getFixed());
594 | $hdl_holder.show();
595 | return true;
596 | }
597 | };
598 | /*}}}*/
599 | function disableHandles()/*{{{*/
600 | {
601 | seehandles = false;
602 | $hdl_holder.hide();
603 | };
604 | /*}}}*/
605 | function animMode(v)/*{{{*/
606 | {
607 | (animating = v) ? disableHandles(): enableHandles();
608 | };
609 | /*}}}*/
610 | function done()/*{{{*/
611 | {
612 | animMode(false);
613 | refresh();
614 | };
615 | /*}}}*/
616 |
617 | var $track = newTracker().mousedown(createDragger('move'))
618 | .css({ cursor: 'move', position: 'absolute', zIndex: 360 })
619 |
620 | $img_holder.append($track);
621 | disableHandles();
622 |
623 | return {
624 | updateVisible: updateVisible,
625 | update: update,
626 | release: release,
627 | refresh: refresh,
628 | setCursor: function (cursor) { $track.css('cursor',cursor); },
629 | enableHandles: enableHandles,
630 | enableOnly: function() { seehandles = true; },
631 | showHandles: showHandles,
632 | disableHandles: disableHandles,
633 | animMode: animMode,
634 | done: done
635 | };
636 | }();
637 | /*}}}*/
638 | var Tracker = function()/*{{{*/
639 | {
640 | var onMove = function() { },
641 | onDone = function() { },
642 | trackDoc = options.trackDocument;
643 |
644 | if (!trackDoc)
645 | {
646 | $trk
647 | .mousemove(trackMove)
648 | .mouseup(trackUp)
649 | .mouseout(trackUp)
650 | ;
651 | }
652 |
653 | function toFront()/*{{{*/
654 | {
655 | $trk.css({zIndex:450});
656 | if (trackDoc)
657 | {
658 | $(document)
659 | .mousemove(trackMove)
660 | .mouseup(trackUp)
661 | ;
662 | }
663 | }
664 | /*}}}*/
665 | function toBack()/*{{{*/
666 | {
667 | $trk.css({zIndex:290});
668 | if (trackDoc)
669 | {
670 | $(document)
671 | .unbind('mousemove',trackMove)
672 | .unbind('mouseup',trackUp)
673 | ;
674 | }
675 | }
676 | /*}}}*/
677 | function trackMove(e)/*{{{*/
678 | {
679 | onMove(mouseAbs(e));
680 | };
681 | /*}}}*/
682 | function trackUp(e)/*{{{*/
683 | {
684 | e.preventDefault();
685 | e.stopPropagation();
686 |
687 | if (btndown)
688 | {
689 | btndown = false;
690 |
691 | onDone(mouseAbs(e));
692 | options.onSelect(unscale(Coords.getFixed()));
693 | toBack();
694 | onMove = function() { };
695 | onDone = function() { };
696 | }
697 |
698 | return false;
699 | };
700 | /*}}}*/
701 |
702 | function activateHandlers(move,done)/* {{{ */
703 | {
704 | btndown = true;
705 | onMove = move;
706 | onDone = done;
707 | toFront();
708 | return false;
709 | };
710 | /* }}} */
711 |
712 | function setCursor(t) { $trk.css('cursor',t); };
713 |
714 | $img.before($trk);
715 | return {
716 | activateHandlers: activateHandlers,
717 | setCursor: setCursor
718 | };
719 | }();
720 | /*}}}*/
721 | var KeyManager = function()/*{{{*/
722 | {
723 | var $keymgr = $(' ')
724 | .css({ position: 'absolute', left: '-30px' })
725 | .keypress(parseKey)
726 | .blur(onBlur),
727 |
728 | $keywrap = $('
')
729 | .css({
730 | position: 'absolute',
731 | overflow: 'hidden'
732 | })
733 | .append($keymgr)
734 | ;
735 |
736 | function watchKeys()/*{{{*/
737 | {
738 | if (options.keySupport)
739 | {
740 | $keymgr.show();
741 | $keymgr.focus();
742 | }
743 | };
744 | /*}}}*/
745 | function onBlur(e)/*{{{*/
746 | {
747 | $keymgr.hide();
748 | };
749 | /*}}}*/
750 | function doNudge(e,x,y)/*{{{*/
751 | {
752 | if (options.allowMove) {
753 | Coords.moveOffset([x,y]);
754 | Selection.updateVisible();
755 | };
756 | e.preventDefault();
757 | e.stopPropagation();
758 | };
759 | /*}}}*/
760 | function parseKey(e)/*{{{*/
761 | {
762 | if (e.ctrlKey) return true;
763 | shift_down = e.shiftKey ? true : false;
764 | var nudge = shift_down ? 10 : 1;
765 | switch(e.keyCode)
766 | {
767 | case 37: doNudge(e,-nudge,0); break;
768 | case 39: doNudge(e,nudge,0); break;
769 | case 38: doNudge(e,0,-nudge); break;
770 | case 40: doNudge(e,0,nudge); break;
771 |
772 | case 27: Selection.release(); break;
773 |
774 | case 9: return true;
775 | }
776 |
777 | return nothing(e);
778 | };
779 | /*}}}*/
780 |
781 | if (options.keySupport) $keywrap.insertBefore($img);
782 | return {
783 | watchKeys: watchKeys
784 | };
785 | }();
786 | /*}}}*/
787 |
788 | // }}}
789 | // Internal Methods {{{
790 |
791 | function px(n) { return '' + parseInt(n) + 'px'; };
792 | function pct(n) { return '' + parseInt(n) + '%'; };
793 | function cssClass(cl) { return options.baseClass + '-' + cl; };
794 | function getPos(obj)/*{{{*/
795 | {
796 | // Updated in v0.9.4 to use built-in dimensions plugin
797 | var pos = $(obj).offset();
798 | return [ pos.left, pos.top ];
799 | };
800 | /*}}}*/
801 | function mouseAbs(e)/*{{{*/
802 | {
803 | return [ (e.pageX - docOffset[0]), (e.pageY - docOffset[1]) ];
804 | };
805 | /*}}}*/
806 | function myCursor(type)/*{{{*/
807 | {
808 | if (type != lastcurs)
809 | {
810 | Tracker.setCursor(type);
811 | //Handles.xsetCursor(type);
812 | lastcurs = type;
813 | }
814 | };
815 | /*}}}*/
816 | function startDragMode(mode,pos)/*{{{*/
817 | {
818 | docOffset = getPos($img);
819 | Tracker.setCursor(mode=='move'?mode:mode+'-resize');
820 |
821 | if (mode == 'move')
822 | return Tracker.activateHandlers(createMover(pos), doneSelect);
823 |
824 | var fc = Coords.getFixed();
825 | var opp = oppLockCorner(mode);
826 | var opc = Coords.getCorner(oppLockCorner(opp));
827 |
828 | Coords.setPressed(Coords.getCorner(opp));
829 | Coords.setCurrent(opc);
830 |
831 | Tracker.activateHandlers(dragmodeHandler(mode,fc),doneSelect);
832 | };
833 | /*}}}*/
834 | function dragmodeHandler(mode,f)/*{{{*/
835 | {
836 | return function(pos) {
837 | if (!options.aspectRatio) switch(mode)
838 | {
839 | case 'e': pos[1] = f.y2; break;
840 | case 'w': pos[1] = f.y2; break;
841 | case 'n': pos[0] = f.x2; break;
842 | case 's': pos[0] = f.x2; break;
843 | }
844 | else switch(mode)
845 | {
846 | case 'e': pos[1] = f.y+1; break;
847 | case 'w': pos[1] = f.y+1; break;
848 | case 'n': pos[0] = f.x+1; break;
849 | case 's': pos[0] = f.x+1; break;
850 | }
851 | Coords.setCurrent(pos);
852 | Selection.update();
853 | };
854 | };
855 | /*}}}*/
856 | function createMover(pos)/*{{{*/
857 | {
858 | var lloc = pos;
859 | KeyManager.watchKeys();
860 |
861 | return function(pos)
862 | {
863 | Coords.moveOffset([pos[0] - lloc[0], pos[1] - lloc[1]]);
864 | lloc = pos;
865 |
866 | Selection.update();
867 | };
868 | };
869 | /*}}}*/
870 | function oppLockCorner(ord)/*{{{*/
871 | {
872 | switch(ord)
873 | {
874 | case 'n': return 'sw';
875 | case 's': return 'nw';
876 | case 'e': return 'nw';
877 | case 'w': return 'ne';
878 | case 'ne': return 'sw';
879 | case 'nw': return 'se';
880 | case 'se': return 'nw';
881 | case 'sw': return 'ne';
882 | };
883 | };
884 | /*}}}*/
885 | function createDragger(ord)/*{{{*/
886 | {
887 | return function(e) {
888 | if (options.disabled) return false;
889 | if ((ord == 'move') && !options.allowMove) return false;
890 | btndown = true;
891 | startDragMode(ord,mouseAbs(e));
892 | e.stopPropagation();
893 | e.preventDefault();
894 | return false;
895 | };
896 | };
897 | /*}}}*/
898 | function presize($obj,w,h)/*{{{*/
899 | {
900 | var nw = $obj.width(), nh = $obj.height();
901 | if ((nw > w) && w > 0)
902 | {
903 | nw = w;
904 | nh = (w/$obj.width()) * $obj.height();
905 | }
906 | if ((nh > h) && h > 0)
907 | {
908 | nh = h;
909 | nw = (h/$obj.height()) * $obj.width();
910 | }
911 | xscale = $obj.width() / nw;
912 | yscale = $obj.height() / nh;
913 | $obj.width(nw).height(nh);
914 | };
915 | /*}}}*/
916 | function unscale(c)/*{{{*/
917 | {
918 | return {
919 | x: parseInt(c.x * xscale), y: parseInt(c.y * yscale),
920 | x2: parseInt(c.x2 * xscale), y2: parseInt(c.y2 * yscale),
921 | w: parseInt(c.w * xscale), h: parseInt(c.h * yscale)
922 | };
923 | };
924 | /*}}}*/
925 | function doneSelect(pos)/*{{{*/
926 | {
927 | var c = Coords.getFixed();
928 | if (c.w > options.minSelect[0] && c.h > options.minSelect[1])
929 | {
930 | Selection.enableHandles();
931 | Selection.done();
932 | }
933 | else
934 | {
935 | Selection.release();
936 | }
937 | Tracker.setCursor( options.allowSelect?'crosshair':'default' );
938 | };
939 | /*}}}*/
940 | function newSelection(e)/*{{{*/
941 | {
942 | if (options.disabled) return false;
943 | if (!options.allowSelect) return false;
944 | btndown = true;
945 | docOffset = getPos($img);
946 | Selection.disableHandles();
947 | myCursor('crosshair');
948 | var pos = mouseAbs(e);
949 | Coords.setPressed(pos);
950 | Tracker.activateHandlers(selectDrag,doneSelect);
951 | KeyManager.watchKeys();
952 | Selection.update();
953 |
954 | e.stopPropagation();
955 | e.preventDefault();
956 | return false;
957 | };
958 | /*}}}*/
959 | function selectDrag(pos)/*{{{*/
960 | {
961 | Coords.setCurrent(pos);
962 | Selection.update();
963 | };
964 | /*}}}*/
965 | function newTracker()
966 | {
967 | var trk = $('
').addClass(cssClass('tracker'));
968 | $.browser.msie && trk.css({ opacity: 0, backgroundColor: 'white' });
969 | return trk;
970 | };
971 |
972 | // }}}
973 | // API methods {{{
974 |
975 | function animateTo(a)/*{{{*/
976 | {
977 | var x1 = a[0] / xscale,
978 | y1 = a[1] / yscale,
979 | x2 = a[2] / xscale,
980 | y2 = a[3] / yscale;
981 |
982 | if (animating) return;
983 |
984 | var animto = Coords.flipCoords(x1,y1,x2,y2);
985 | var c = Coords.getFixed();
986 | var animat = initcr = [ c.x, c.y, c.x2, c.y2 ];
987 | var interv = options.animationDelay;
988 |
989 | var x = animat[0];
990 | var y = animat[1];
991 | var x2 = animat[2];
992 | var y2 = animat[3];
993 | var ix1 = animto[0] - initcr[0];
994 | var iy1 = animto[1] - initcr[1];
995 | var ix2 = animto[2] - initcr[2];
996 | var iy2 = animto[3] - initcr[3];
997 | var pcent = 0;
998 | var velocity = options.swingSpeed;
999 |
1000 | Selection.animMode(true);
1001 |
1002 | var animator = function()
1003 | {
1004 | return function()
1005 | {
1006 | pcent += (100 - pcent) / velocity;
1007 |
1008 | animat[0] = x + ((pcent / 100) * ix1);
1009 | animat[1] = y + ((pcent / 100) * iy1);
1010 | animat[2] = x2 + ((pcent / 100) * ix2);
1011 | animat[3] = y2 + ((pcent / 100) * iy2);
1012 |
1013 | if (pcent < 100) animateStart();
1014 | else Selection.done();
1015 |
1016 | if (pcent >= 99.8) pcent = 100;
1017 |
1018 | setSelectRaw(animat);
1019 | };
1020 | }();
1021 |
1022 | function animateStart()
1023 | { window.setTimeout(animator,interv); };
1024 |
1025 | animateStart();
1026 | };
1027 | /*}}}*/
1028 | function setSelect(rect)//{{{
1029 | {
1030 | setSelectRaw([rect[0]/xscale,rect[1]/yscale,rect[2]/xscale,rect[3]/yscale]);
1031 | };
1032 | //}}}
1033 | function setSelectRaw(l) /*{{{*/
1034 | {
1035 | Coords.setPressed([l[0],l[1]]);
1036 | Coords.setCurrent([l[2],l[3]]);
1037 | Selection.update();
1038 | };
1039 | /*}}}*/
1040 | function setOptions(opt)/*{{{*/
1041 | {
1042 | if (typeof(opt) != 'object') opt = { };
1043 | options = $.extend(options,opt);
1044 |
1045 | if (typeof(options.onChange)!=='function')
1046 | options.onChange = function() { };
1047 |
1048 | if (typeof(options.onSelect)!=='function')
1049 | options.onSelect = function() { };
1050 |
1051 | };
1052 | /*}}}*/
1053 | function tellSelect()/*{{{*/
1054 | {
1055 | return unscale(Coords.getFixed());
1056 | };
1057 | /*}}}*/
1058 | function tellScaled()/*{{{*/
1059 | {
1060 | return Coords.getFixed();
1061 | };
1062 | /*}}}*/
1063 | function setOptionsNew(opt)/*{{{*/
1064 | {
1065 | setOptions(opt);
1066 | interfaceUpdate();
1067 | };
1068 | /*}}}*/
1069 | function disableCrop()//{{{
1070 | {
1071 | options.disabled = true;
1072 | Selection.disableHandles();
1073 | Selection.setCursor('default');
1074 | Tracker.setCursor('default');
1075 | };
1076 | //}}}
1077 | function enableCrop()//{{{
1078 | {
1079 | options.disabled = false;
1080 | interfaceUpdate();
1081 | };
1082 | //}}}
1083 | function cancelCrop()//{{{
1084 | {
1085 | Selection.done();
1086 | Tracker.activateHandlers(null,null);
1087 | };
1088 | //}}}
1089 | function destroy()//{{{
1090 | {
1091 | $div.remove();
1092 | $origimg.show();
1093 | };
1094 | //}}}
1095 |
1096 | function interfaceUpdate(alt)//{{{
1097 | // This method tweaks the interface based on options object.
1098 | // Called when options are changed and at end of initialization.
1099 | {
1100 | options.allowResize ?
1101 | alt?Selection.enableOnly():Selection.enableHandles():
1102 | Selection.disableHandles();
1103 |
1104 | Tracker.setCursor( options.allowSelect? 'crosshair': 'default' );
1105 | Selection.setCursor( options.allowMove? 'move': 'default' );
1106 |
1107 | $div.css('backgroundColor',options.bgColor);
1108 |
1109 | if ('setSelect' in options) {
1110 | setSelect(opt.setSelect);
1111 | Selection.done();
1112 | delete(options.setSelect);
1113 | }
1114 |
1115 | if ('trueSize' in options) {
1116 | xscale = options.trueSize[0] / boundx;
1117 | yscale = options.trueSize[1] / boundy;
1118 | }
1119 |
1120 | xlimit = options.maxSize[0] || 0;
1121 | ylimit = options.maxSize[1] || 0;
1122 | xmin = options.minSize[0] || 0;
1123 | ymin = options.minSize[1] || 0;
1124 |
1125 | if ('outerImage' in options)
1126 | {
1127 | $img.attr('src',options.outerImage);
1128 | delete(options.outerImage);
1129 | }
1130 |
1131 | Selection.refresh();
1132 | };
1133 | //}}}
1134 |
1135 | // }}}
1136 |
1137 | $hdl_holder.hide();
1138 | interfaceUpdate(true);
1139 |
1140 | var api = {
1141 | animateTo: animateTo,
1142 | setSelect: setSelect,
1143 | setOptions: setOptionsNew,
1144 | tellSelect: tellSelect,
1145 | tellScaled: tellScaled,
1146 |
1147 | disable: disableCrop,
1148 | enable: enableCrop,
1149 | cancel: cancelCrop,
1150 |
1151 | focus: KeyManager.watchKeys,
1152 |
1153 | getBounds: function() { return [ boundx * xscale, boundy * yscale ]; },
1154 | getWidgetSize: function() { return [ boundx, boundy ]; },
1155 |
1156 | release: Selection.release,
1157 | destroy: destroy
1158 |
1159 | };
1160 |
1161 | $origimg.data('Jcrop',api);
1162 | return api;
1163 | };
1164 |
1165 | $.fn.Jcrop = function(options)/*{{{*/
1166 | {
1167 | function attachWhenDone(from)/*{{{*/
1168 | {
1169 | var loadsrc = options.useImg || from.src;
1170 | var img = new Image();
1171 | img.onload = function() { $.Jcrop(from,options); };
1172 | img.src = loadsrc;
1173 | };
1174 | /*}}}*/
1175 | if (typeof(options) !== 'object') options = { };
1176 |
1177 | // Iterate over each object, attach Jcrop
1178 | this.each(function()
1179 | {
1180 | // If we've already attached to this object
1181 | if ($(this).data('Jcrop'))
1182 | {
1183 | // The API can be requested this way (undocumented)
1184 | if (options == 'api') return $(this).data('Jcrop');
1185 | // Otherwise, we just reset the options...
1186 | else $(this).data('Jcrop').setOptions(options);
1187 | }
1188 | // If we haven't been attached, preload and attach
1189 | else attachWhenDone(this);
1190 | });
1191 |
1192 | // Return "this" so we're chainable a la jQuery plugin-style!
1193 | return this;
1194 | };
1195 | /*}}}*/
1196 |
1197 | })(jQuery);
1198 |
--------------------------------------------------------------------------------