├── .gitignore
├── spec
├── spec.opts
├── fixtures
│ └── assets
│ │ ├── wrong.txt
│ │ ├── big.jpg
│ │ └── picture.png
├── unit
│ ├── unit_spec_helper.rb
│ ├── input_spec.rb
│ ├── text_spec.rb
│ ├── base_controller_spec.rb
│ ├── base_view_helper_spec.rb
│ ├── image_spec.rb
│ ├── page_spec.rb
│ ├── collection_spec.rb
│ └── collection_item_spec.rb
├── support
│ └── paperclip.rb
├── functional
│ ├── page_spec.rb
│ ├── functional_spec_helper.rb
│ ├── custom_tag_helper_spec.rb
│ ├── image_tag_helper_spec.rb
│ └── content_tag_helper_spec.rb
└── spec_helper.rb
├── install.rb
├── uninstall.rb
├── generators
└── plongo_admin
│ ├── templates
│ ├── helper.rb
│ ├── public
│ │ ├── javascripts
│ │ │ └── admin
│ │ │ │ ├── .DS_Store
│ │ │ │ └── plugins
│ │ │ │ ├── ocupload.js
│ │ │ │ ├── rte.js
│ │ │ │ └── rte.tb.js
│ │ ├── images
│ │ │ └── admin
│ │ │ │ └── plugins
│ │ │ │ ├── rte_icons.gif
│ │ │ │ ├── rte_colorpicker_rgb.jpg
│ │ │ │ └── rte_colorpicker_gray.jpg
│ │ └── stylesheets
│ │ │ └── admin
│ │ │ └── plugins
│ │ │ └── rte.css
│ ├── index.html.erb
│ ├── controller.rb
│ ├── edit.html.erb
│ ├── plongo.js
│ ├── plongo.css
│ └── delegate.js
│ ├── plongo_admin_generator.rb
│ └── insert_commands.rb
├── lib
├── plongo
│ ├── elements
│ │ ├── input.rb
│ │ ├── text.rb
│ │ ├── base.rb
│ │ ├── collection_item.rb
│ │ ├── image.rb
│ │ └── collection.rb
│ ├── page.rb
│ ├── plugins
│ │ ├── callbacks.rb
│ │ ├── collection.rb
│ │ └── paperclip.rb
│ └── rails
│ │ ├── custom_tag_helper.rb
│ │ ├── base_controller.rb
│ │ ├── image_tag_helper.rb
│ │ ├── base_view_helper.rb
│ │ ├── content_tag_helper.rb
│ │ └── admin_helper.rb
└── plongo.rb
├── init.rb
├── MIT-LICENSE
├── TODO
├── Rakefile
└── README.textile
/.gitignore:
--------------------------------------------------------------------------------
1 | tmp/**
2 |
--------------------------------------------------------------------------------
/spec/spec.opts:
--------------------------------------------------------------------------------
1 | --color
--------------------------------------------------------------------------------
/install.rb:
--------------------------------------------------------------------------------
1 | # Install hook code here
2 |
--------------------------------------------------------------------------------
/spec/fixtures/assets/wrong.txt:
--------------------------------------------------------------------------------
1 | hello world
--------------------------------------------------------------------------------
/uninstall.rb:
--------------------------------------------------------------------------------
1 | # Uninstall hook code here
2 |
--------------------------------------------------------------------------------
/spec/fixtures/assets/big.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/did/Plongo/master/spec/fixtures/assets/big.jpg
--------------------------------------------------------------------------------
/spec/fixtures/assets/picture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/did/Plongo/master/spec/fixtures/assets/picture.png
--------------------------------------------------------------------------------
/generators/plongo_admin/templates/helper.rb:
--------------------------------------------------------------------------------
1 | module Admin::PagesHelper
2 |
3 | include Plongo::Rails::AdminHelper
4 |
5 | end
--------------------------------------------------------------------------------
/generators/plongo_admin/templates/public/javascripts/admin/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/did/Plongo/master/generators/plongo_admin/templates/public/javascripts/admin/.DS_Store
--------------------------------------------------------------------------------
/generators/plongo_admin/templates/public/images/admin/plugins/rte_icons.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/did/Plongo/master/generators/plongo_admin/templates/public/images/admin/plugins/rte_icons.gif
--------------------------------------------------------------------------------
/generators/plongo_admin/templates/public/images/admin/plugins/rte_colorpicker_rgb.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/did/Plongo/master/generators/plongo_admin/templates/public/images/admin/plugins/rte_colorpicker_rgb.jpg
--------------------------------------------------------------------------------
/generators/plongo_admin/templates/public/images/admin/plugins/rte_colorpicker_gray.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/did/Plongo/master/generators/plongo_admin/templates/public/images/admin/plugins/rte_colorpicker_gray.jpg
--------------------------------------------------------------------------------
/lib/plongo/elements/input.rb:
--------------------------------------------------------------------------------
1 | module Plongo
2 | module Elements
3 |
4 | class Input < Base
5 |
6 | ## attributes ##
7 | key :value, String, :required => true
8 |
9 | end
10 |
11 | end
12 | end
--------------------------------------------------------------------------------
/spec/unit/unit_spec_helper.rb:
--------------------------------------------------------------------------------
1 | require File.join(File.dirname(__FILE__), '..', 'spec_helper')
2 |
3 | MongoMapper.connection = nil
4 |
5 | class MyController < ActionController::Base
6 |
7 | include Plongo::Rails::BaseController
8 |
9 | end
--------------------------------------------------------------------------------
/spec/unit/input_spec.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path(File.dirname(__FILE__) + '/unit_spec_helper')
2 |
3 | describe 'Input' do
4 |
5 | it 'should be valid' do
6 | Plongo::Elements::Input.new(:key => 'title', :name => 'Header', :value => 'Lorem ipsum', :priority => 1).should be_valid
7 | end
8 |
9 | end
--------------------------------------------------------------------------------
/spec/unit/text_spec.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path(File.dirname(__FILE__) + '/unit_spec_helper')
2 |
3 | describe 'Text' do
4 |
5 | it 'should be valid' do
6 | Plongo::Elements::Text.new(:key => 'headline', :name => 'first headline', :value => 'Lorem ipsum', :priority => 1).should be_valid
7 | end
8 |
9 | end
--------------------------------------------------------------------------------
/init.rb:
--------------------------------------------------------------------------------
1 | $:.unshift "#{File.dirname(__FILE__)}/lib"
2 |
3 | require 'plongo'
4 |
5 | ActionController::Base.send :include, Plongo::Rails::BaseController
6 | ActionView::Base.send :include, Plongo::Rails::BaseViewHelper
7 | ActionView::Base.send :include, Plongo::Rails::ContentTagHelper
8 | ActionView::Base.send :include, Plongo::Rails::ImageTagHelper
9 | ActionView::Base.send :include, Plongo::Rails::CustomTagHelper
--------------------------------------------------------------------------------
/lib/plongo/page.rb:
--------------------------------------------------------------------------------
1 | module Plongo
2 |
3 | class Page
4 |
5 | include MongoMapper::Document
6 |
7 | ## attributes ##
8 | key :name, String, :required => true
9 | key :uri, String
10 | key :path, String, :required => true
11 | key :shared, Boolean, :default => false
12 | key :locale, String
13 |
14 | plugin Plongo::Plugins::Collection
15 |
16 | end
17 |
18 | end
--------------------------------------------------------------------------------
/lib/plongo/plugins/callbacks.rb:
--------------------------------------------------------------------------------
1 | module Plongo
2 |
3 | module Plugins
4 |
5 | module Callbacks
6 |
7 | def self.configure(model)
8 | model.class_eval do
9 | include ActiveSupport::Callbacks
10 |
11 | define_callbacks(
12 | :after_validation,
13 | :before_save, :after_save,
14 | :before_destroy, :after_destroy
15 | )
16 | end
17 | end
18 |
19 | end
20 |
21 | end
22 |
23 | end
--------------------------------------------------------------------------------
/lib/plongo/elements/text.rb:
--------------------------------------------------------------------------------
1 | module Plongo
2 | module Elements
3 |
4 | class Text < Base
5 |
6 | ## attributes ##
7 | key :value, String, :required => true
8 |
9 | def value=(text)
10 | if text.present?
11 | text.gsub! /
<\/div>/, '
'
12 | text.gsub! /
<\/p/, ' '
13 | end
14 | send(:write_key, :value, text)
15 | end
16 |
17 | end
18 |
19 | end
20 | end
--------------------------------------------------------------------------------
/lib/plongo/elements/base.rb:
--------------------------------------------------------------------------------
1 | module Plongo
2 | module Elements
3 |
4 | class Base
5 |
6 | include MongoMapper::EmbeddedDocument
7 |
8 | plugin Plongo::Plugins::Callbacks
9 | plugin Plongo::Plugins::Paperclip
10 |
11 | ## attributes ##
12 | key :key, String, :required => true
13 | key :priority, Integer
14 | key :_type, String
15 |
16 | ## methods ##
17 |
18 | protected
19 |
20 | def logger
21 | RAILS_DEFAULT_LOGGER
22 | end
23 |
24 | end
25 |
26 | end
27 | end
--------------------------------------------------------------------------------
/lib/plongo.rb:
--------------------------------------------------------------------------------
1 | $:.unshift File.expand_path(File.dirname(__FILE__))
2 |
3 | require 'plongo/plugins/paperclip'
4 | require 'plongo/plugins/collection'
5 | require 'plongo/plugins/callbacks'
6 | require 'plongo/page'
7 | require 'plongo/elements/base'
8 | require 'plongo/elements/input'
9 | require 'plongo/elements/text'
10 | require 'plongo/elements/image'
11 | require 'plongo/elements/collection'
12 | require 'plongo/elements/collection_item'
13 | require 'plongo/rails/base_controller'
14 | require 'plongo/rails/base_view_helper'
15 | require 'plongo/rails/content_tag_helper'
16 | require 'plongo/rails/image_tag_helper'
17 | require 'plongo/rails/custom_tag_helper'
--------------------------------------------------------------------------------
/lib/plongo/rails/custom_tag_helper.rb:
--------------------------------------------------------------------------------
1 | module Plongo
2 | module Rails
3 |
4 | module CustomTagHelper
5 |
6 | def plongo_content(key, type, options = {}, &block)
7 | default_value = options[:value]
8 |
9 | options[:name] = key if options[:name].blank?
10 |
11 | element = add_plongo_element(type, key, options, &block)
12 |
13 | if element.respond_to?(:source)
14 | element.source? ? element.url : (element.value || default_value)
15 | elsif element.respond_to?(:items)
16 | element.items
17 | else
18 | element.value || default_value
19 | end
20 | end
21 |
22 | end
23 |
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/spec/support/paperclip.rb:
--------------------------------------------------------------------------------
1 | # Temporary path to put downloaded files
2 | Paperclip::Attachment.default_options.merge!({
3 | :path => ":test/:attachment/:id/:style/:basename.:extension"
4 | })
5 |
6 | Paperclip.options[:log] = false
7 |
8 | module FixturedAsset
9 | def self.open(filename)
10 | File.new(self.path(filename))
11 | end
12 |
13 | def self.path(filename)
14 | File.join(File.dirname(__FILE__), '..', 'fixtures', 'assets', filename)
15 | end
16 |
17 | def self.duplicate(filename)
18 | dst = File.join(File.dirname(__FILE__), '..', 'tmp', filename)
19 | FileUtils.cp self.path(filename), dst
20 | dst
21 | end
22 | end
23 |
24 | Paperclip.interpolates :test do |attachment, style|
25 | TEST_DIR
26 | end
--------------------------------------------------------------------------------
/lib/plongo/elements/collection_item.rb:
--------------------------------------------------------------------------------
1 | module Plongo
2 | module Elements
3 |
4 | class CollectionItem
5 | include MongoMapper::EmbeddedDocument
6 |
7 | plugin Plongo::Plugins::Callbacks
8 | plugin Plongo::Plugins::Collection
9 |
10 | ## attributes ##
11 | key :_delete, Integer
12 | key :_position, Integer, :default => 0
13 |
14 | def build_element_from_metadata(attributes)
15 | metadata_key = self._parent_document.find_metadata_key_by_key(attributes[:key])
16 |
17 | element = metadata_key._type.constantize.new(metadata_key.attributes.delete_if { |k, v| %w{_id keys}.include?(k) })
18 |
19 | element.attributes = attributes
20 |
21 | element
22 | end
23 |
24 | end
25 |
26 | end
27 | end
--------------------------------------------------------------------------------
/spec/unit/base_controller_spec.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path(File.dirname(__FILE__) + '/unit_spec_helper')
2 |
3 | describe 'Base controller' do
4 |
5 | before(:all) do
6 | @controller = MyController.new
7 | end
8 |
9 | it 'should keep the last page' do
10 | page = Plongo::Page.new(:name => 'About', :path => 'pages/about')
11 | @controller.send(:append_plongo_page, page)
12 | page = Plongo::Page.new(:name => 'Home page', :path => 'pages/home')
13 | @controller.send(:append_plongo_page, page)
14 | page = Plongo::Page.new(:name => 'Home page *', :path => 'pages/home')
15 | @controller.send(:append_plongo_page, page)
16 |
17 | pages = @controller.instance_variable_get(:@plongo_pages)
18 | pages.size.should == 2
19 | pages.last.name.should == 'Home page *'
20 | end
21 |
22 |
23 | end
--------------------------------------------------------------------------------
/spec/functional/page_spec.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path(File.dirname(__FILE__) + '/functional_spec_helper')
2 |
3 | describe 'Page' do
4 |
5 | before(:all) do
6 | Plongo::Page.destroy_all
7 | end
8 |
9 | it 'should find page from a path and a locale' do
10 | lambda {
11 | Plongo::Page.create!(:name => 'Home page', :path => 'pages/home', :locale => I18n.locale)
12 | Plongo::Page.create!(:name => 'Page accueil', :path => 'pages/home', :locale => 'fr')
13 | }.should change(Plongo::Page, :count).by(2)
14 |
15 | page = Plongo::Page.find_by_path_and_locale('pages/home', 'en')
16 | page.should_not be_nil
17 | page.name.should == 'Home page'
18 |
19 | page = Plongo::Page.find_by_path_and_locale('pages/home', 'fr')
20 | page.should_not be_nil
21 | page.name.should == 'Page accueil'
22 | end
23 |
24 | end
--------------------------------------------------------------------------------
/generators/plongo_admin/templates/index.html.erb:
--------------------------------------------------------------------------------
1 | <%% title 'Listing pages' %>
2 |
3 | <%% if @pages.empty? %>
4 |
No pages found
5 | <%% else %>
6 |
7 |
8 | Name
9 | Uri
10 | View
11 |
12 |
13 | <%% @pages.each do |page| %>
14 |
15 | <%%= page.name %>
16 | <%%= page.uri %>
17 | <%%= page.path %>
18 |
19 | <%%= link_to 'Edit', edit_admin_page_url(page) %>
20 |
21 | <%%= link_to 'Delete', admin_page_url(page), :method => 'delete', :confirm => 'Are you sure ?' %>
22 |
23 |
24 | <%% end %>
25 |
26 | <%% end %>
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | $LOAD_PATH.unshift(File.dirname(__FILE__))
2 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3 |
4 | require 'rubygems'
5 | require 'mocha'
6 | require 'mongo_mapper'
7 |
8 | # require 'carrierwave'
9 | require 'paperclip'
10 | require 'support/paperclip'
11 | require 'plongo'
12 | require 'spec'
13 | require 'spec/autorun'
14 | require 'action_controller'
15 |
16 | TEST_DIR = File.expand_path(File.dirname(__FILE__) + '/../tmp')
17 |
18 | FileUtils.rm_rf(TEST_DIR)
19 | FileUtils.mkdir_p(TEST_DIR)
20 |
21 | RAILS_DEFAULT_LOGGER = Logger.new(File.join(TEST_DIR, 'paperclip.log'))
22 |
23 | # I18n.load_path << Dir[File.join(File.dirname(__FILE__), '..', 'config', 'locales', '*.{rb,yml}') ]
24 | # I18n.default_locale = :en
25 |
26 | Spec::Runner.configure do |config|
27 | config.mock_with :mocha
28 | end
29 |
30 | I18n.locale = 'en'
--------------------------------------------------------------------------------
/generators/plongo_admin/templates/controller.rb:
--------------------------------------------------------------------------------
1 | class Admin::PagesController < Admin::BaseController
2 |
3 | def index
4 | @pages = Plongo::Page.all(:order => 'name ASC')
5 | end
6 |
7 | def edit
8 | @page = Plongo::Page.find(params[:id])
9 | end
10 |
11 | def update
12 | @page = Plongo::Page.find(params[:id])
13 |
14 | respond_to do |format|
15 | if @page.update_attributes(params[:page])
16 | flash[:notice] = 'Page updated with success'
17 | format.html { redirect_to edit_admin_page_url(@page) }
18 | else
19 | flash.now[:error] = "Page not updated"
20 | format.html { render :action => 'edit' }
21 | end
22 | end
23 | end
24 |
25 | def destroy
26 | @page = Plongo::Page.find(params[:id])
27 |
28 | @page.destroy
29 |
30 | flash[:notice] = 'Page deleted with success'
31 |
32 | redirect_to admin_pages_url
33 | end
34 |
35 | end
--------------------------------------------------------------------------------
/lib/plongo/rails/base_controller.rb:
--------------------------------------------------------------------------------
1 | module Plongo
2 | module Rails
3 |
4 | module BaseController
5 |
6 | def self.included(base)
7 | base.send :alias_method_chain, :render, :plongo
8 | end
9 |
10 | protected
11 |
12 | def render_with_plongo(options = nil, extra_options = {}, &block)
13 | output = render_without_plongo(options, extra_options, &block)
14 |
15 | self.save_plongo_pages
16 |
17 | output
18 | end
19 |
20 | def save_plongo_pages
21 | if defined?(@plongo_pages)
22 | @plongo_pages.collect(&:save)
23 | end
24 | end
25 |
26 | def append_plongo_page(page)
27 | @plongo_pages ||= []
28 | if existing_page = @plongo_pages.find { |p| p.path == page.path }
29 | existing_page.attributes = page.attributes
30 | else
31 | @plongo_pages << page
32 | end
33 | end
34 |
35 | end
36 |
37 | end
38 |
39 | end
--------------------------------------------------------------------------------
/spec/unit/base_view_helper_spec.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path(File.dirname(__FILE__) + '/unit_spec_helper')
2 |
3 | describe 'BaseViewHelper' do
4 |
5 | def content_tag(*args); true; end
6 |
7 | include Plongo::Rails::BaseViewHelper
8 |
9 | it 'should return Input class' do
10 | %w{h1 h2 h3 h4 h5 a em small em b u i strong}.each do |tag|
11 | name_to_plongo_element_klass(tag).should == Plongo::Elements::Input
12 | end
13 | end
14 |
15 | it 'should return Text class' do
16 | %w{div p quote pre code}.each do |tag|
17 | name_to_plongo_element_klass(tag).should == Plongo::Elements::Text
18 | end
19 | end
20 |
21 | it 'should return Image class' do
22 | name_to_plongo_element_klass(:img).should == Plongo::Elements::Image
23 | end
24 |
25 | it 'should return Collection class' do
26 | %w{div ul ol dl table tbody}.each do |tag|
27 | name_to_plongo_element_klass(tag) do
28 | true
29 | end.should == Plongo::Elements::Collection
30 | end
31 | end
32 |
33 | end
--------------------------------------------------------------------------------
/generators/plongo_admin/templates/edit.html.erb:
--------------------------------------------------------------------------------
1 | <%% title "Update '#{@page.name}'" %>
2 |
3 | <%% unless @page.errors.empty? %>
4 |
5 |
<%%= form_error_message(@page) %>
6 |
7 | <%% end %>
8 |
9 | <%% form_tag admin_page_url(@page), :class => 'plongo-form', :method => :put, :multipart => true do %>
10 |
11 |
12 | Page title
13 |
14 |
15 | <%%= text_field_tag 'page[name]', @page.name %>
16 |
17 |
18 |
19 |
20 | <%%= plongo_fields(@page) %>
21 |
22 |
23 |
24 |
25 | <%%= submit_tag 'Update' %>
26 | or
27 | <%%= link_to 'back', admin_pages_url %>
28 |
29 | <%% end %>
30 |
31 | <%% content_for :head do %>
32 | <%%= javascript_include_tag 'admin/plugins/delegate', 'admin/plugins/ocupload', 'admin/plugins/rte', 'admin/plugins/rte.tb', 'admin/plongo' %>
33 | <%%= stylesheet_link_tag 'admin/plugins/rte', 'admin/plongo' %>
34 | <%% end %>
--------------------------------------------------------------------------------
/lib/plongo/elements/image.rb:
--------------------------------------------------------------------------------
1 | module Plongo
2 | module Elements
3 |
4 | class Image < Base
5 |
6 | ## behaviours ##
7 | has_attached_file :source, :styles => {
8 | :thumbnail => ["50x50#", :png],
9 | :cropped => ["100%x100%", :png]
10 | },
11 | :processors => [:cropper]
12 |
13 | ## attributes ##
14 | key :width, Integer
15 | key :height, Integer
16 | key :source_file_name, String
17 | key :source_content_type, String
18 | key :source_file_size, Integer
19 | key :source_updated_at, Time
20 |
21 | ## validations ##
22 | validates_attachment_content_type :source, :content_type => %r{image/}
23 |
24 | ## methods ##
25 |
26 | def cropped?
27 | !(self.width.blank? || self.height.blank?)
28 | end
29 |
30 | def size
31 | "#{self.width}x#{self.height}" if self.width && self.height
32 | end
33 |
34 | def url
35 | self.cropped? ? self.source.url(:cropped) : self.source.url
36 | end
37 | end
38 |
39 | end
40 | end
--------------------------------------------------------------------------------
/MIT-LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2010 [name of plugin creator]
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 |
--------------------------------------------------------------------------------
/spec/unit/image_spec.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path(File.dirname(__FILE__) + '/unit_spec_helper')
2 |
3 | describe 'Image' do
4 |
5 | it 'should be valid' do
6 | Plongo::Elements::Image.new(image_attributes).should be_valid
7 | end
8 |
9 | it 'should have an attachment' do
10 | image = Plongo::Elements::Image.new(image_attributes(:source => FixturedAsset.open('picture.png')))
11 | image.should be_valid
12 | image.source.should_not be_nil
13 | end
14 |
15 | it 'should not be valid if attachment is not an image' do
16 | image = Plongo::Elements::Image.new(image_attributes(:source => FixturedAsset.open('wrong.txt')))
17 | image.should_not be_valid
18 | end
19 |
20 | it 'should be cropped' do
21 | image = Plongo::Elements::Image.new
22 | image.cropped?.should be_false
23 |
24 | image.width = 200
25 | image.height = 100
26 |
27 | image.cropped?.should be_true
28 | image.size.should == '200x100'
29 | end
30 |
31 | protected
32 |
33 | def image_attributes(options = {})
34 | {
35 | :key => 'banner',
36 | :name => 'A banner',
37 | :priority => 1
38 | }.merge(options)
39 | end
40 |
41 | end
--------------------------------------------------------------------------------
/lib/plongo/rails/image_tag_helper.rb:
--------------------------------------------------------------------------------
1 | module Plongo
2 | module Rails
3 |
4 | module ImageTagHelper
5 |
6 | def self.included(base)
7 | base.send :alias_method_chain, :image_tag, :plongo
8 | end
9 |
10 | def image_tag_with_plongo(source, options = {})
11 | if !options[:plongo_key].blank? || !options[:plongo].blank?
12 | plongo_options = options.delete(:plongo) || {}
13 |
14 | key = options.delete(:plongo_key) || plongo_options[:key]
15 |
16 | if size = options[:size]
17 | plongo_options[:width], plongo_options[:height] = size.split("x") if size =~ %r{^\d+x\d+$}
18 | end
19 |
20 | # puts "key = #{key}"
21 |
22 | element = add_plongo_element(:img, key, {
23 | :value => source,
24 | :name => options[:alt] || 'Image',
25 |
26 | }.merge(plongo_options))
27 |
28 | # puts "*** source = #{element.source.url.inspect} / #{element.source?} / #{element.value}"
29 |
30 | source = element.source? ? element.url : source
31 | end
32 |
33 | image_tag_without_plongo(source, options)
34 | end
35 |
36 | end
37 | end
38 | end
--------------------------------------------------------------------------------
/TODO:
--------------------------------------------------------------------------------
1 | Installation
2 | ======
3 |
4 | config.gem 'mongo', :source => 'http://gemcutter.org'
5 | config.gem 'mongo_ext', :source => 'http://gemcutter.org', :lib => false
6 | config.gem 'mongo_mapper', :source => 'http://gemcutter.org', :version => '0.7.0'
7 |
8 | TODO
9 | =====
10 |
11 | x modifier element_attributes et item_attributes (id => hash)
12 | x carousel type => collection
13 | - page:
14 | - tag pour decrire la page (/path ?) => set_plongo_page 'Home page', 'pages/home'r
15 | x url
16 | ! rails templates ?
17 | x save page once
18 | x snippets
19 | - property shared
20 | -> Page est un snippet ou l'inverse ?
21 | x liens
22 | -> <%= plongo_content :key => 'email', :name => 'Admin email address', :type => :input %>
23 | -> <%= plongo_content :key => 'body', :name => 'Body of the article', :type => :text %>
24 | -> <%= plongo_content :key => 'banner', :name => 'Banner image', :type => :image, :with => 300, :height => 200 %>
25 | - plongo_helpers
26 | - tests
27 | x position des items
28 | - changer le path d'un plongo content
29 | - ajout des pages au pool de pages
30 | - plongo_helpers ?
31 |
32 | NEW FEATURES:
33 | - set_plongo_page 'pages/home'
34 |
35 | Plongo
36 | ======
37 |
38 | Introduction goes here.
39 |
40 |
41 |
42 | Example
43 | =======
44 |
45 | Example goes here.
46 |
47 |
48 | Copyright (c) 2010 [Did], released under the MIT license
49 |
--------------------------------------------------------------------------------
/spec/functional/functional_spec_helper.rb:
--------------------------------------------------------------------------------
1 | require File.join(File.dirname(__FILE__), '..', 'spec_helper')
2 |
3 | require 'spec'
4 | require 'rubygems'
5 |
6 | def smart_require(lib_name, gem_name, gem_version = '>= 0.0.0')
7 | begin
8 | require lib_name if lib_name
9 | rescue LoadError
10 | if gem_name
11 | gem gem_name, gem_version
12 | require lib_name if lib_name
13 | end
14 | end
15 | end
16 |
17 | smart_require 'active_support', 'activesupport', '>= 2.3.4'
18 | smart_require 'action_controller', 'actionpack', '>= 2.3.4'
19 | smart_require 'action_view', 'actionpack', '>= 2.3.4'
20 |
21 | MongoMapper.connection = Mongo::Connection.new('127.0.0.1', 27017, {
22 | :logger => Logger.new(TEST_DIR + '/test.log')
23 | })
24 | MongoMapper.database = 'plongotest'
25 |
26 | MongoMapper.database.collection_names.each do |collection|
27 | next if collection == 'system.indexes'
28 | MongoMapper.database.collection(collection).drop
29 | end
30 |
31 |
32 | class FakeController
33 |
34 | def render; ''; end
35 |
36 | include Plongo::Rails::BaseController
37 |
38 | def initialize(request)
39 | @request = request
40 | end
41 |
42 | def controller_path
43 | 'pages'
44 | end
45 |
46 | def action_name
47 | 'home'
48 | end
49 |
50 | def request
51 | @request
52 | end
53 |
54 | end
55 |
56 | module ApplicationHelper
57 |
58 | end
--------------------------------------------------------------------------------
/generators/plongo_admin/plongo_admin_generator.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path(File.dirname(__FILE__) + "/insert_commands.rb")
2 |
3 | class PlongoAdminGenerator < Rails::Generator::Base
4 |
5 | def manifest
6 | record do |m|
7 | m.directory "app/controllers/admin"
8 | m.template "controller.rb", "app/controllers/admin/pages_controller.rb"
9 |
10 | m.directory "app/helpers/admin"
11 | m.template "helper.rb", "app/helpers/admin/pages_helper.rb"
12 |
13 | m.directory "app/views/admin/pages"
14 | m.template "index.html.erb", "app/views/admin/pages/index.html.erb"
15 | m.template "edit.html.erb", "app/views/admin/pages/edit.html.erb"
16 |
17 | m.directory "public/stylesheets/admin"
18 | m.template "plongo.css", "public/stylesheets/admin/plongo.css"
19 | m.directory "public/javascripts/admin"
20 | m.template "plongo.js", "public/javascripts/admin/plongo.js"
21 | m.directory "public/javascripts/admin/plugins"
22 | m.template "delegate.js", "public/javascripts/admin/plugins/delegate.js"
23 |
24 | m.insert_close_to 'config/routes.rb', 'admin.resource :session', <<-END
25 | admin.resources :pages
26 | END
27 |
28 | end
29 | end
30 |
31 | def assets(manifest)
32 | Dir[File.join(source_path('public'), '**/**')].each do |file|
33 | if File.directory?(file)
34 | manifest.directory relative_public_path(file)
35 | else
36 | manifest.file relative_public_path(file), relative_public_path(file)
37 | end
38 | end
39 | end
40 |
41 | def relative_public_path(file)
42 | File.join('public', file.gsub(source_path('public'), ''))
43 | end
44 |
45 | end
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 | require 'rake'
3 |
4 | begin
5 | require 'jeweler'
6 | Jeweler::Tasks.new do |gem|
7 | gem.name = "plongo"
8 | gem.summary = %Q{TODO: one-line summary of your gem}
9 | gem.description = %Q{TODO: longer description of your gem}
10 | gem.email = "didier@nocoffee.fr"
11 | gem.homepage = "http://github.com/Did/plongo"
12 | gem.authors = ["Didier Lafforgue"]
13 | gem.add_development_dependency "rspec", ">= 1.2.9"
14 | gem.add_development_dependency "mongo_mapper", ">= 0.7.0"
15 | # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
16 | end
17 | Jeweler::GemcutterTasks.new
18 | rescue LoadError
19 | puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
20 | end
21 |
22 | require 'spec/rake/spectask'
23 |
24 | Spec::Rake::SpecTask.new(:rcov) do |spec|
25 | spec.libs << 'lib' << 'spec'
26 | spec.pattern = 'spec/**/*_spec.rb'
27 | spec.rcov = true
28 | end
29 |
30 | Spec::Rake::SpecTask.new('spec:unit') do |spec|
31 | spec.libs << 'lib' << 'spec'
32 | spec.spec_files = FileList['spec/unit/**/*_spec.rb']
33 | end
34 |
35 | Spec::Rake::SpecTask.new('spec:functionals') do |spec|
36 | spec.libs << 'lib' << 'spec'
37 | spec.spec_files = FileList['spec/functional/**/*_spec.rb']
38 | end
39 |
40 | task :spec => [:check_dependencies, 'spec:unit', 'spec:functionals']
41 |
42 | task :default => :spec
43 |
44 | require 'rake/rdoctask'
45 | desc 'Generate documentation for the plongo plugin.'
46 | Rake::RDocTask.new(:rdoc) do |rdoc|
47 | rdoc.rdoc_dir = 'rdoc'
48 | rdoc.title = 'Plongo'
49 | rdoc.options << '--line-numbers' << '--inline-source'
50 | rdoc.rdoc_files.include('README')
51 | rdoc.rdoc_files.include('lib/**/*.rb')
52 | end
53 |
--------------------------------------------------------------------------------
/spec/functional/custom_tag_helper_spec.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path(File.dirname(__FILE__) + '/functional_spec_helper')
2 |
3 | include ActionView::Helpers
4 |
5 | describe 'CustomTagHelper' do
6 |
7 | include Plongo::Rails::BaseViewHelper
8 | include Plongo::Rails::CustomTagHelper
9 |
10 | attr_accessor :output_buffer
11 |
12 | before(:each) do
13 | @output_buffer = ''
14 | @request = mock('FakeRequest')
15 | @request.stubs(:request_uri).returns('/')
16 | @controller = FakeController.new(@request)
17 | Plongo::Page.destroy_all
18 | end
19 |
20 | it 'should store a new element for the current page if passing the right option' do
21 | lambda {
22 | plongo_content('title', :input, { :value => 'Hello world' }).should == 'Hello world'
23 | @controller.send(:save_plongo_pages) # uber important
24 | }.should change(Plongo::Page, :count).by(1)
25 |
26 | page = Plongo::Page.all.last
27 |
28 | page.elements.should_not be_empty
29 | page.elements[0].class.should == Plongo::Elements::Input
30 | page.elements[0].key.should == 'title'
31 | page.elements[0].name.should == 'title'
32 | end
33 |
34 | it 'should handle image type' do
35 | plongo_content('banner', :image, { :value => 'banner.jpg', :width => 200, :height => 100 }).should == 'banner.jpg'
36 |
37 | @controller.send(:save_plongo_pages) # uber important
38 |
39 | page = Plongo::Page.all.last
40 | element = page.elements[0]
41 | element.width.should == 200
42 | element.height.should == 100
43 | element.source = FixturedAsset.open('picture.png')
44 | page.save!
45 |
46 | @plongo_page = nil # reload
47 |
48 | output = plongo_content('banner', :image, { :value => 'banner.jpg' })
49 | output.should match(/\/system\/sources\/[a-zA-F0-9]+\/cropped\/picture.png/)
50 | end
51 |
52 | end
--------------------------------------------------------------------------------
/spec/unit/page_spec.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path(File.dirname(__FILE__) + '/unit_spec_helper')
2 |
3 | describe 'Page' do
4 |
5 | it 'should be valid' do
6 | (page = Plongo::Page.new(:name => 'Home page', :path => 'pages/home')).should be_valid
7 | page.shared.should be_false
8 | end
9 |
10 | it 'should add elements' do
11 | page = Plongo::Page.new(:name => 'Home page')
12 | page.elements << Plongo::Elements::Input.new(:key => 'title')
13 | page.elements << Plongo::Elements::Text.new(:key => 'tagline')
14 |
15 | page.elements.size.should == 2
16 | end
17 |
18 | it 'should have sorted elements by priority' do
19 | page = Plongo::Page.new(:name => 'Home page')
20 | page.elements << Plongo::Elements::Input.new(:key => 'title', :priority => 6)
21 | page.elements << Plongo::Elements::Input.new(:key => 'email')
22 | page.elements << Plongo::Elements::Text.new(:key => 'footer', :priority => 8)
23 | page.elements << Plongo::Elements::Text.new(:key => 'tagline', :priority => 4)
24 |
25 | page.sorted_elements.collect(&:key).should == %w{tagline title footer email}
26 | end
27 |
28 | it 'should retrieve an element by its key' do
29 | page = Plongo::Page.new(:name => 'Home page')
30 | page.elements << Plongo::Elements::Input.new(:key => 'title')
31 | page.elements << Plongo::Elements::Text.new(:key => 'tagline')
32 |
33 | page.find_element_by_key('title').should_not be_nil
34 | page.find_element_by_key('footer').should be_nil
35 | end
36 |
37 | it 'should update elements from a hash' do
38 | page = Plongo::Page.new(:name => 'Home page')
39 | page.elements << (element1 = Plongo::Elements::Input.new(:key => 'title', :value => 'a title'))
40 | page.elements << (element2 = Plongo::Elements::Text.new(:key => 'tagline', :value => 'a tagline', :priority => 10))
41 |
42 | page.element_attributes = {
43 | element1.id => { :value => 'new title' },
44 | element2.id => { :value => 'new tagline' }
45 | }
46 |
47 | page.elements.first.value.should == 'new title'
48 | page.elements.first._type.should == 'Plongo::Elements::Input'
49 |
50 | page.elements.last.value.should == 'new tagline'
51 | page.elements.last._type.should == 'Plongo::Elements::Text'
52 | page.elements.last.priority.should == 10
53 | end
54 |
55 | end
--------------------------------------------------------------------------------
/lib/plongo/plugins/collection.rb:
--------------------------------------------------------------------------------
1 | module Plongo
2 |
3 | module Plugins
4 |
5 | module Collection
6 |
7 | def self.configure(model)
8 | model.class_eval do
9 | ## associations ##
10 | many :elements, :class_name => 'Plongo::Elements::Base', :polymorphic => true
11 |
12 | ## callbacks ##
13 | before_save :before_save_elements
14 | after_save :after_save_elements
15 | before_destroy :before_destroy_elements
16 | after_destroy :after_destroy_elements
17 |
18 | ## validations ##
19 | validates_associated :elements
20 | end
21 | end
22 |
23 | module InstanceMethods
24 |
25 | def sorted_elements
26 | self.elements.sort { |a, b| (a.priority || 99) <=> (b.priority || 99) }
27 | end
28 |
29 | def find_element_by_key(key)
30 | self.elements.detect { |el| el.key == key }
31 | end
32 |
33 | def element_attributes=(hash = {})
34 | # based on accepts_nested_attributes_for but in our case just handle updates
35 | hash.each_pair do |id, attributes|
36 | attributes.symbolize_keys!
37 |
38 | if (element = self.elements.detect { |el| el._id.to_s == id.to_s })
39 | element.attributes = attributes.symbolize_keys
40 | else
41 | if attributes.key?(:type)
42 | element = attributes[:type].constantize.new(attributes)
43 | else
44 | element = build_element_from_metadata(attributes) # type not present, should be a collection item
45 | end
46 | self.elements << element
47 | end
48 | end
49 | end
50 |
51 | protected
52 |
53 | def before_save_elements
54 | self.elements.each { |el| el.send(:run_callbacks, :before_save) }
55 | end
56 |
57 | def after_save_elements
58 | self.elements.each { |el| el.send(:run_callbacks, :after_save) }
59 | end
60 |
61 | def before_destroy_elements
62 | self.elements.each { |el| el.send(:run_callbacks, :before_destroy) }
63 | end
64 |
65 | def after_destroy_elements
66 | self.elements.each { |el| el.send(:run_callbacks, :after_destroy) }
67 | end
68 |
69 | end
70 |
71 | end
72 |
73 | end
74 |
75 | end
--------------------------------------------------------------------------------
/lib/plongo/elements/collection.rb:
--------------------------------------------------------------------------------
1 | module Plongo
2 | module Elements
3 |
4 | class Collection < Base
5 |
6 | ## associations ##
7 | many :metadata_keys, :class_name => 'Plongo::Elements::Base', :polymorphic => true
8 | many :items, :class_name => 'Plongo::Elements::CollectionItem'
9 |
10 | ## attributes
11 | key :highlight
12 |
13 | ## callbacks ##
14 | before_save :sort_items
15 | before_save :delete_items
16 |
17 | before_save :before_save_items
18 | after_save :after_save_items
19 | before_destroy :before_destroy_items
20 | after_destroy :after_destroy_items
21 |
22 | ## validations ##
23 | validates_associated :items
24 |
25 | def item_attributes=(hash = {})
26 | # based on accepts_nested_attributes_for but in our case just handle updates
27 | hash.each_pair do |id, attributes|
28 | attributes.symbolize_keys!
29 |
30 | if (item = self.items.detect { |item| item._id.to_s == id.to_s })
31 | item.attributes = attributes.symbolize_keys
32 | else
33 | item = self.items.build
34 | item.attributes = attributes
35 | end
36 | end
37 | end
38 |
39 | def sorted_metadata_keys
40 | self.metadata_keys.sort { |a, b| (a.priority || 99) <=> (b.priority || 99) }
41 | end
42 |
43 | def find_metadata_key_by_key(key)
44 | self.metadata_keys.detect { |el| el.key == key }
45 | end
46 |
47 | protected
48 |
49 | def sort_items
50 | self.items.sort! { |a, b| a._position <=> b._position }
51 | end
52 |
53 | def delete_items
54 | self.items.delete_if { |item| item._delete == true || item._delete == '1' || item._delete == 1 }
55 | end
56 |
57 | def before_save_items
58 | self.items.each { |el| el.send(:run_callbacks, :before_save) }
59 | end
60 |
61 | def after_save_items
62 | self.items.each { |el| el.send(:run_callbacks, :after_save) }
63 | end
64 |
65 | def before_destroy_items
66 | self.items.each { |el| el.send(:run_callbacks, :before_destroy) }
67 | end
68 |
69 | def after_destroy_items
70 | self.items.each { |el| el.send(:run_callbacks, :after_destroy) }
71 | end
72 |
73 | end
74 |
75 | end
76 | end
--------------------------------------------------------------------------------
/lib/plongo/plugins/paperclip.rb:
--------------------------------------------------------------------------------
1 | module Plongo
2 |
3 | module Plugins
4 |
5 | module Paperclip
6 |
7 | def self.configure(model)
8 | model.class_eval do
9 | include ::Paperclip
10 |
11 | class << self
12 | def has_attached_file(name, options = {})
13 | has_attached_file_with_mongomapper(name, options)
14 | end
15 | end
16 | end
17 | end
18 |
19 | module ClassMethods
20 |
21 | def has_attached_file_with_mongomapper(name, options = {})
22 | include ::Paperclip::InstanceMethods
23 |
24 | write_inheritable_attribute(:attachment_definitions, {}) if attachment_definitions.nil?
25 | attachment_definitions[name] = {:validations => []}.merge(options)
26 |
27 | after_save :save_attached_files
28 | before_destroy :destroy_attached_files
29 |
30 | define_callbacks :before_post_process, :after_post_process
31 | define_callbacks :"before_#{name}_post_process", :"after_#{name}_post_process"
32 |
33 | define_method name do |*args|
34 | a = attachment_for(name)
35 | (args.length > 0) ? a.to_s(args.first) : a
36 | end
37 |
38 | define_method "#{name}=" do |file|
39 | attachment_for(name).assign(file)
40 | end
41 |
42 | define_method "#{name}?" do
43 | attachment_for(name).file?
44 | end
45 |
46 | validates_each name, :logic => lambda {
47 | attachment = attachment_for(name)
48 | attachment.send(:flush_errors) unless attachment.valid?
49 | }
50 | end
51 |
52 | end
53 |
54 | module InstanceMethods
55 |
56 | end
57 |
58 | end
59 |
60 | end
61 |
62 | end
63 |
64 | module Paperclip
65 | class Cropper < Thumbnail
66 |
67 | def initialize file, options = {}, attachment = nil
68 | if options[:geometry] == "100%x100%" && attachment.instance.cropped?
69 | options[:geometry] = "#{attachment.instance.width}x#{attachment.instance.height}#"
70 | end
71 | super
72 | end
73 |
74 | end
75 | end
76 |
77 | Paperclip.interpolates :id_partition do |attachment, style|
78 | if (id = attachment.instance.id).is_a?(Integer)
79 | ("%09d" % id).scan(/\d{3}/).join("/")
80 | else
81 | id.scan(/.{3}/).first(3).join("/")
82 | end
83 | end
--------------------------------------------------------------------------------
/spec/functional/image_tag_helper_spec.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path(File.dirname(__FILE__) + '/functional_spec_helper')
2 |
3 | include ActionView::Helpers
4 |
5 | describe 'ImageTagHelper' do
6 |
7 | include Plongo::Rails::BaseViewHelper
8 | include Plongo::Rails::ImageTagHelper
9 |
10 | attr_accessor :output_buffer
11 |
12 | before(:each) do
13 | @output_buffer = ''
14 | @request = mock('FakeRequest')
15 | @request.stubs(:request_uri).returns('/')
16 | @controller = FakeController.new(@request)
17 | Plongo::Page.destroy_all
18 | end
19 |
20 | it 'should render image tag without plongo' do
21 | image_tag('http://www.somewhere.net', :alt => 'nice').should == '
'
22 | end
23 |
24 | it 'should store a new element for the current page if passing the right option' do
25 | lambda {
26 | image_tag('banner.png', :alt => 'Banner', :plongo_key => 'banner').should == '
'
27 | @controller.send(:save_plongo_pages) # uber important
28 | }.should change(Plongo::Page, :count).by(1)
29 |
30 | page = Plongo::Page.all.last
31 |
32 | page.elements.should_not be_empty
33 | page.elements[0].class.should == Plongo::Elements::Image
34 | page.elements[0].name.should == 'Banner'
35 | end
36 |
37 | it 'should accept options' do
38 | image_tag('banner.png', :plongo => { :key => 'banner', :priority => 42, :name => 'Test' }).should == '
'
39 |
40 | @controller.send(:save_plongo_pages) # uber important
41 |
42 | page = Plongo::Page.all.last
43 | page.elements.first.name.should == 'Test'
44 | page.elements.first.priority.should == 42
45 | end
46 |
47 | it 'should use an uploaded image' do
48 | image_tag_with_an_uploaded_file('banner.png', 'picture.png', { :alt => 'Banner', :plongo_key => 'banner' })
49 |
50 | output = image_tag('banner.png', :alt => 'Banner', :plongo_key => 'banner')
51 | output.should match(/\/system\/sources\/[a-zA-F0-9]+\/original\/picture.png/)
52 | end
53 |
54 | it 'should crop an uploaded image' do
55 | options = { :alt => 'Banner', :plongo_key => 'banner', :size => '48x32' }
56 | image = image_tag_with_an_uploaded_file('banner.png', 'big.jpg', options.dup)
57 |
58 | output = image_tag('banner.png', options)
59 | output.should match(/\/system\/sources\/[a-zA-F0-9]+\/cropped\/big.png/)
60 | output.should match(/width=\"48\"/)
61 | output.should match(/height=\"32\"/)
62 | end
63 |
64 | protected
65 |
66 | def image_tag_with_an_uploaded_file(name, filename, options = {})
67 | image_tag('banner.png', options)
68 |
69 | @controller.send(:save_plongo_pages) # uber important
70 |
71 | page = Plongo::Page.find_by_path('pages/home')
72 | element = page.elements[0]
73 | element.source = FixturedAsset.open(filename)
74 | page.save!
75 |
76 | @plongo_page = nil # reload
77 | end
78 |
79 | end
--------------------------------------------------------------------------------
/generators/plongo_admin/templates/plongo.js:
--------------------------------------------------------------------------------
1 | $(document).ready(function() {
2 |
3 | $('.plongo-text textarea').filter(function() {
4 | return $(this).parents('.plongo-new-item').length == 0
5 | }).rte({
6 | controls_rte: rte_toolbar,
7 | controls_html: html_toolbar,
8 | height: 200
9 | });
10 |
11 | // form
12 |
13 | var updatePlongoCollectionItemPositions = function(collection) {
14 | $.each(collection.find('.plongo-item:visible'), function(index) {
15 | $(this).find('input[type=hidden].position').val(index);
16 | });
17 | }
18 |
19 | var setupPlongoCollectionEffects = function(collection, index) {
20 | collection.accordion('destroy');
21 | collection.accordion({
22 | header: 'div.plongo-item h3',
23 | collapsible: true,
24 | alwaysOpen: false,
25 | active: false,
26 | autoHeight: false
27 | }).sortable({
28 | items: '>div.plongo-item',
29 | update: function(event, ui) {
30 | updatePlongoCollectionItemPositions(collection);
31 | }
32 | });
33 |
34 | if (typeof(index) != 'undefined')
35 | collection.accordion('activate', index);
36 | }
37 |
38 | $('.plongo-collection').each(function() {
39 | setupPlongoCollectionEffects($(this));
40 | });
41 |
42 | $('form').delegate('keypress', {
43 | 'input.highlight': function(e) {
44 | var input = $(e.target);
45 | var title = input.parents('.plongo-item').find('h3');
46 |
47 | setTimeout(function() {
48 | title.html(input.val());
49 | }, 50);
50 | }
51 | });
52 |
53 | $('form').delegate('click', {
54 | '.add-button': function(e) {
55 | var link = $(e.target);
56 | var pattern = link.parent().prev('.plongo-new-item');
57 | var newItem = pattern.clone();
58 |
59 | newItem.addClass('plongo-item');
60 | newItem.removeClass('plongo-new-item');
61 | newItem.html(pattern.html().replace(/NEW_RECORD(_[0-9])+/g, new Date().getTime()));
62 |
63 | var index = link.parents('ol').find('.plongo-item').size();
64 |
65 | newItem.insertBefore(pattern);
66 |
67 | newItem.show();
68 |
69 | newItem.find('textarea').rte({
70 | controls_rte: rte_toolbar,
71 | controls_html: html_toolbar,
72 | height: 200
73 | });
74 |
75 | var collection = link.parents('li.plongo-collection');
76 |
77 | setupPlongoCollectionEffects(collection, index);
78 |
79 | updatePlongoCollectionItemPositions(collection);
80 |
81 | e.preventDefault();
82 | },
83 | '.remove-button': function(e) {
84 | var link = $(e.target);
85 | var field = link.next('input[type=hidden]');
86 | var collection = link.parents('li.plongo-collection');
87 |
88 | var wrapper = link.closest('div.plongo-item');
89 | if (wrapper.attr('id') == '') {
90 | wrapper.remove();
91 | } else {
92 | field.val('1');
93 | wrapper.hide();
94 | }
95 |
96 | updatePlongoCollectionItemPositions(collection);
97 |
98 | setupPlongoCollectionEffects();
99 |
100 | e.preventDefault();
101 | }
102 | });
103 |
104 | $('form').submit(function(e) {
105 | $(e.target).find('.plongo-new-item').remove();
106 | });
107 |
108 | });
--------------------------------------------------------------------------------
/spec/unit/collection_spec.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path(File.dirname(__FILE__) + '/unit_spec_helper')
2 |
3 | describe 'Collection' do
4 |
5 | it 'should be valid' do
6 | new_collection.should be_valid
7 | end
8 |
9 | it 'should add metadata keys' do
10 | collection = new_collection
11 | collection.metadata_keys << Plongo::Elements::Input.new(:key => 'title')
12 | collection.metadata_keys << Plongo::Elements::Text.new(:key => 'tagline', :priority => 2)
13 |
14 | collection.metadata_keys.size.should == 2
15 | end
16 |
17 | it 'should have sorted elements by priority' do
18 | collection = new_collection
19 | collection.metadata_keys << Plongo::Elements::Input.new(:key => 'title', :priority => 8)
20 | collection.metadata_keys << Plongo::Elements::Text.new(:key => 'body', :priority => 6)
21 | collection.metadata_keys << Plongo::Elements::Text.new(:key => 'tagline')
22 |
23 | collection.sorted_metadata_keys.collect(&:key).should == %w{body title tagline}
24 | end
25 |
26 | it 'should update items from a hash' do
27 | item_1, item_2 = new_collection_item, new_collection_item
28 |
29 | collection = new_collection(:items => [item_1, item_2])
30 |
31 | collection.items[0].elements.first._type.should == 'Plongo::Elements::Input'
32 |
33 | collection.item_attributes = {
34 | item_1.id => {
35 | :element_attributes => {
36 | item_1.elements[0].id => { :value => 'new title', :priority => 5 },
37 | item_1.elements[1].id => { :value => 'new tagline' }
38 | },
39 | :_position => 2
40 | },
41 | item_2.id => {
42 | :element_attributes => {
43 | item_2.elements[1].id => { :value => 'very new tagline' }
44 | },
45 | :_position => 1
46 | },
47 | 'NEW_RECORD' => {
48 | :name => 'New item',
49 | :_position => 3
50 | }
51 | }
52 |
53 | collection.send(:sort_items)
54 |
55 | collection.items.size.should == 3
56 |
57 | collection.items[0].elements.first.value.should == 'a title'
58 | collection.items[0].elements[1].value.should == 'very new tagline'
59 | collection.items[0].elements[1]._type.should == 'Plongo::Elements::Text'
60 |
61 | collection.items[1].elements.first.value.should == 'new title'
62 | collection.items[1].elements.first.priority.should == 5
63 | collection.items[1].elements.first._type.should == 'Plongo::Elements::Input'
64 |
65 | collection.items[2]._id.should_not == 'NEW_RECORD'
66 | collection.items[2].name.should == 'New item'
67 | end
68 |
69 | protected
70 |
71 | def new_collection(options = {})
72 | Plongo::Elements::Collection.new({ :key => 'carousel', :name => 'Main carousel' }.merge(options))
73 | end
74 |
75 | def new_collection_item(options = {})
76 | Plongo::Elements::CollectionItem.new({
77 | :elements => [
78 | Plongo::Elements::Input.new(:key => 'title', :value => 'a title'),
79 | Plongo::Elements::Text.new(:key => 'tagline', :value => 'a tagline', :priority => 10),
80 | Plongo::Elements::Image.new(:key => 'picture', :value => 'a picture')
81 | ]}.merge(options))
82 | end
83 |
84 | end
--------------------------------------------------------------------------------
/lib/plongo/rails/base_view_helper.rb:
--------------------------------------------------------------------------------
1 | module Plongo
2 | module Rails
3 |
4 | module BaseViewHelper
5 |
6 | protected
7 |
8 | def add_plongo_element(tag_or_type, key, options = {}, &block)
9 | if defined?(@plongo_collection) && !@plongo_collection.nil?
10 | if (element = @plongo_collection.find_metadata_key_by_key(key)).nil?
11 | element = name_to_plongo_element_klass(tag_or_type, &block).new(options.merge(:key => key))
12 | @plongo_collection.metadata_keys << element
13 | else
14 | element.attributes = options
15 | end
16 | element
17 | else
18 | container = plongo_container(options[:page])
19 |
20 | if (element = container.find_element_by_key(key)).nil?
21 | element = name_to_plongo_element_klass(tag_or_type, &block).new(options.merge(:key => key))
22 | container.elements << element
23 | else
24 | options.delete(:value)
25 | element.attributes = options
26 | end
27 |
28 | element
29 | end
30 | end
31 |
32 | def plongo_container(options = nil)
33 | return @plongo_item if defined?(@plongo_item) && @plongo_item
34 | plongo_page(options)
35 | end
36 |
37 | def plongo_page(options = nil)
38 | return @plongo_page if (options.nil? || options.empty?) && defined?(@plongo_page) && @plongo_page
39 |
40 | current_path = File.join(@controller.controller_path, @controller.action_name)
41 |
42 | page_options = {
43 | :name => @controller.action_name,
44 | :uri => @controller.request.request_uri.gsub(/^([a-z0-9A-Z\-_\/]*)(\?.*)?/, '\1'),
45 | :path => current_path,
46 | :shared => false,
47 | :locale => I18n.locale.to_s
48 | }.merge(options || {})
49 |
50 | page_options[:shared] = true if options && !options[:path].blank? && options[:path] != current_path
51 |
52 | if (page = Plongo::Page.find_by_path_and_locale(page_options[:path], page_options[:locale])).nil?
53 | page = Plongo::Page.new(page_options)
54 | else
55 | page.attributes = page_options unless options.nil?
56 | end
57 |
58 | @controller.send(:append_plongo_page, page)
59 |
60 | @plongo_page = page #if (options.nil? || options.empty? || options.key?(:path))
61 |
62 | page
63 | end
64 |
65 | def name_to_plongo_element_klass(name, &block)
66 | case name.to_s
67 | when /h[1-9]/, 'b', 'i', 'u', 'span', 'a', 'em', 'small', 'strong' then Plongo::Elements::Input
68 | when 'img' then Plongo::Elements::Image
69 | when 'div', 'ol', 'ul', 'dl', 'table', 'tbody'
70 | if block_given? #&& block.arity == 1
71 | Plongo::Elements::Collection
72 | else
73 | Plongo::Elements::Text
74 | end
75 | else
76 | begin
77 | "Plongo::Elements::#{name.to_s.capitalize}".constantize
78 | rescue NameError
79 | Plongo::Elements::Text
80 | end
81 | end
82 | end
83 |
84 | end
85 |
86 | end
87 | end
--------------------------------------------------------------------------------
/generators/plongo_admin/templates/plongo.css:
--------------------------------------------------------------------------------
1 | .plongo-form fieldset {
2 | /* background: white;*/
3 | border: 0px;
4 | margin: 0px;
5 | padding: 0px;
6 | }
7 |
8 | .plongo-form fieldset legend {
9 | color: #333;
10 | font-size: 0.9em;
11 | padding: 0.2em;
12 | text-transform: uppercase;
13 | }
14 |
15 | .plongo-form fieldset ol {
16 | list-style: none;
17 | padding: 0px 10px;
18 | margin: 0 0 20px 0;
19 | background-color: #F8F8F8;
20 | }
21 |
22 | .plongo-form fieldset ol li.plongo-input input[type=text],
23 | .plongo-form fieldset ol li.plongo-text textarea {
24 | padding: 4px;
25 | border-color: #bbb;
26 | width: 99%;
27 | }
28 |
29 | .plongo-form fieldset ol li.plongo-input input[type=text] { font-size: 1.2em; }
30 | .plongo-form fieldset ol li.plongo-text textarea { height: 70px; }
31 |
32 | .plongo-form fieldset ol li.plongo-image {
33 | padding-bottom: 0px;
34 | }
35 |
36 | .plongo-form fieldset ol li.plongo-image div.preview {
37 | width: 100px;
38 | height: 85px;
39 | float: left;
40 | margin: 5px 0 0 0;
41 | }
42 |
43 | .plongo-form fieldset ol li.plongo-image div.preview img {
44 | border: 4px solid white;
45 | }
46 |
47 | .plongo-form fieldset ol li.plongo-image div.preview p {
48 | margin: 0px;
49 | }
50 |
51 | .plongo-form fieldset ol li.plongo-image input {
52 | margin: 10px 0 0 110px;
53 | clear: both;
54 | }
55 |
56 | .plongo-form fieldset.buttons {
57 | background: white;
58 | text-align: center;
59 | }
60 |
61 | .plongo-form fieldset.buttons a {
62 | text-decoration: none;
63 | color: #008aef;
64 | border-bottom: 1px dotted #008aef;
65 | }
66 |
67 |
68 | /* ___ collection ___ */
69 | .plongo-form fieldset ol li.plongo-collection {
70 | padding: 10px 0;
71 | }
72 |
73 | .plongo-form fieldset ol li.plongo-collection .plongo-item {
74 | position: relative;
75 | }
76 |
77 | .plongo-form fieldset ol li.plongo-collection h3 {
78 | background: #222;
79 | color: #ddd;
80 | font-size: 1.2em;
81 | padding: 6px 8px;
82 | -moz-border-radius: 6px;
83 | -webkit-border-radius: 6px;
84 | cursor: pointer;
85 | }
86 |
87 | .plongo-form fieldset ol li.plongo-collection p.action {
88 | border-top: 1px dotted #aaa;
89 | padding-top: 15px;
90 | }
91 |
92 | .plongo-form fieldset ol li.plongo-collection p.action a.add-button {
93 | margin-left: 10px;
94 | background: #222;
95 | color: #ddd;
96 | padding: 3px 4px;
97 | text-transform: uppercase;
98 | font-size: 0.9em;
99 | text-decoration: none;
100 | -moz-border-radius: 3px;
101 | -webkit-border-radius: 3px;
102 | }
103 |
104 | .plongo-form fieldset ol li.plongo-collection a.remove-button {
105 | position: absolute;
106 | top: 7px;
107 | right: 5px;
108 | color: white;
109 | font-size: 0.7em;
110 | text-decoration: none;
111 | background: #c00;
112 | padding: 1px 2px;
113 | -moz-border-radius: 3px;
114 | -webkit-border-radius: 3px;
115 | }
116 |
117 | .plongo-form fieldset ol li.plongo-collection li {
118 | margin-bottom: 10px;
119 | }
120 |
121 | .plongo-form fieldset ol li.plongo-collection li label {
122 | display: block;
123 | color: #777777;
124 | }
125 |
126 | .plongo-form fieldset ol li.plongo-collection li input,
127 | .plongo-form fieldset ol li.plongo-collection li textarea { margin: 0px; }
128 |
129 | .plongo-form .plongo-new-item {
130 | display: none;
131 | }
132 |
133 | .plongo-form fieldset p.inline-errors {
134 | margin-top: 10px;
135 | position: relative;
136 | padding: 4px 5px;
137 | background: #FFE5E5;
138 | color: #CE2525;
139 | }
--------------------------------------------------------------------------------
/spec/unit/collection_item_spec.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path(File.dirname(__FILE__) + '/unit_spec_helper')
2 |
3 | describe 'CollectionItem' do
4 |
5 | it 'should be valid' do
6 | Plongo::Elements::CollectionItem.new.should be_valid
7 | end
8 |
9 | it 'should add elements' do
10 | item = Plongo::Elements::CollectionItem.new
11 | item.elements << Plongo::Elements::Input.new(:key => 'title')
12 | item.elements << Plongo::Elements::Text.new(:key => 'tagline')
13 |
14 | item.elements.size.should == 2
15 | end
16 |
17 | it 'should have sorted elements by priority' do
18 | item = Plongo::Elements::CollectionItem.new
19 | item.elements << Plongo::Elements::Input.new(:key => 'title', :priority => 6)
20 | item.elements << Plongo::Elements::Input.new(:key => 'email')
21 | item.elements << Plongo::Elements::Text.new(:key => 'footer', :priority => 8)
22 | item.elements << Plongo::Elements::Text.new(:key => 'tagline', :priority => 4)
23 |
24 | item.sorted_elements.collect(&:key).should == %w{tagline title footer email}
25 | end
26 |
27 | it 'should retrieve an element by its key' do
28 | item = Plongo::Elements::CollectionItem.new
29 | item.elements << Plongo::Elements::Input.new(:key => 'title')
30 | item.elements << Plongo::Elements::Text.new(:key => 'tagline')
31 |
32 | item.find_element_by_key('title').should_not be_nil
33 | item.find_element_by_key('footer').should be_nil
34 | end
35 |
36 | it 'should update elements from a hash' do
37 | item = Plongo::Elements::CollectionItem.new
38 | item.elements << (element1 = Plongo::Elements::Input.new(:key => 'title', :value => 'a title'))
39 | item.elements << (element2 = Plongo::Elements::Text.new(:key => 'tagline', :value => 'a tagline', :priority => 10))
40 |
41 | item.element_attributes = {
42 | element1.id => { :value => 'new title' },
43 | element2.id => { :value => 'new tagline' },
44 | 'NEW_RECORD' => {
45 | :type => 'Plongo::Elements::Input',
46 | :name => 'Url',
47 | :key => 'url',
48 | :value => 'an url',
49 | :priority => 42
50 | }
51 | }
52 |
53 | item.elements.first.value.should == 'new title'
54 | item.elements.first._type.should == 'Plongo::Elements::Input'
55 |
56 | item.elements[1].value.should == 'new tagline'
57 | item.elements[1]._type.should == 'Plongo::Elements::Text'
58 | item.elements[1].priority.should == 10
59 |
60 | item.elements.last._type.should == 'Plongo::Elements::Input'
61 | item.elements.last.name.should == 'Url'
62 | item.elements.last.key.should == 'url'
63 | item.elements.last.value.should == 'an url'
64 | item.elements.last.priority.should == 42
65 | end
66 |
67 | it 'should validate an item' do
68 | item = Plongo::Elements::CollectionItem.new
69 | item.elements << Plongo::Elements::Input.new(:key => 'title', :value => nil)
70 | item.valid?.should be_false
71 | end
72 |
73 | it 'should build an element from metadata key' do
74 | collection = Plongo::Elements::Collection.new
75 | collection.metadata_keys << Plongo::Elements::Input.new(:key => 'title')
76 |
77 | collection.item_attributes = {
78 | 'NEW_ITEM' => {
79 | :element_attributes => {
80 | 'A_FIELD' => { :value => 'Hello world', :key => 'title' }
81 | }
82 | }
83 | }
84 |
85 | collection.items.first.elements.first.value.should == 'Hello world'
86 | collection.items.first.elements.first._type.should == 'Plongo::Elements::Input'
87 | end
88 |
89 | end
--------------------------------------------------------------------------------
/spec/functional/content_tag_helper_spec.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path(File.dirname(__FILE__) + '/functional_spec_helper')
2 |
3 | include ActionView::Helpers
4 |
5 | describe 'ContentTagHelper' do
6 |
7 | include Plongo::Rails::BaseViewHelper
8 | include Plongo::Rails::ContentTagHelper
9 |
10 | attr_accessor :output_buffer
11 |
12 | before(:each) do
13 | @output_buffer = ''
14 | @request = mock('FakeRequest')
15 | @request.stubs(:request_uri).returns('/')
16 | @controller = FakeController.new(@request)
17 | Plongo::Page.destroy_all
18 | end
19 |
20 | # it 'should render tags without plongo' do
21 | # content_tag(:h1, 'Hello world').should == '
Hello world '
22 | # content_tag(:p) do
23 | # concat('Lorem ipsum')
24 | # end.should == '
Lorem ipsum
'
25 | # end
26 | #
27 | # it 'should store a new element for the current page if passing the right option' do
28 | # lambda {
29 | # content_tag(:h1, 'Title goes here', :plongo_key => 'title').should == '
Title goes here '
30 | # @controller.send(:save_plongo_pages)
31 | # }.should change(Plongo::Page, :count).by(1)
32 | #
33 | # lambda {
34 | # content_tag(:h1, 'It does not matter', :plongo_key => 'title').should == '
Title goes here '
35 | # @controller.send(:save_plongo_pages)
36 | # }.should_not change(Plongo::Page, :count).by(1)
37 | #
38 | # page = Plongo::Page.all.last
39 | #
40 | # page.name.should == 'home'
41 | # page.path.should == 'pages/home'
42 | # end
43 | #
44 | # it 'should accept options' do
45 | # content_tag(:h1, 'Title goes here', :plongo => { :key => 'title', :priority => 42 }).should == '
Title goes here '
46 | #
47 | # @controller.send(:save_plongo_pages) # uber important
48 | #
49 | # page = Plongo::Page.all.last
50 | # page.elements.size.should == 1
51 | # page.elements.first.name.should == 'Title'
52 | # page.elements.first.priority.should == 42
53 | # end
54 | #
55 | # it 'should setup a collection' do
56 | # render_collection.should == '
Item title Item description
'
57 | #
58 | # (collection = Plongo::Page.find_by_path('pages/home').elements.first).should_not be_nil
59 | # collection.metadata_keys.should_not be_empty
60 | # collection.metadata_keys.collect(&:key).should == %w{title body}
61 | # collection.items.should be_empty
62 | #
63 | # # does not change anything
64 | # render_collection.should == '
Item title Item description
'
65 | # end
66 |
67 | it 'should render multiple items of a collection' do
68 | render_collection
69 |
70 | page = Plongo::Page.find_by_path('pages/home')
71 | collection = page.elements.first
72 |
73 | collection.items << new_collection_item('Hello world', 'Lorem ipsum...')
74 | collection.items << new_collection_item('Foo bar', 'Lorem ipsum...etc')
75 |
76 | page.save
77 |
78 | @plongo_page = nil # reset
79 |
80 | render_collection.should == '
Hello world Lorem ipsum...
Foo bar Lorem ipsum...etc
'
81 | end
82 |
83 | protected
84 |
85 | def render_collection
86 | output = content_tag(:ul, :plongo => { :key => 'simple_collection', :name => 'Simple collection' }) do
87 | nested_output = (content_tag(:li) do
88 | content_tag(:h2, 'Item title', :plongo_key => 'title') +
89 | content_tag(:div, 'Item description', :plongo_key => 'body')
90 | end)
91 | nested_output << '
' if @plongo_last_item
92 | nested_output
93 | end
94 |
95 | @controller.send(:save_plongo_pages) # uber important
96 |
97 | output
98 | end
99 |
100 | def new_collection_item(title, body)
101 | item = Plongo::Elements::CollectionItem.new
102 | item.elements << Plongo::Elements::Input.new(:key => 'title', :value => title)
103 | item.elements << Plongo::Elements::Text.new(:key => 'body', :value => body)
104 | item
105 | end
106 |
107 | end
--------------------------------------------------------------------------------
/generators/plongo_admin/insert_commands.rb:
--------------------------------------------------------------------------------
1 | Rails::Generator::Commands::Create.class_eval do
2 | def route_resource(*resources)
3 | resource_list = resources.map { |r| r.to_sym.inspect }.join(', ')
4 | sentinel = 'ActionController::Routing::Routes.draw do |map|'
5 |
6 | logger.route "map.resource #{resource_list}"
7 | unless options[:pretend]
8 | gsub_file 'config/routes.rb', /(#{Regexp.escape(sentinel)})/mi do |match|
9 | "#{match}\n map.resource #{resource_list}\n"
10 | end
11 | end
12 | end
13 |
14 | def route_name(name, path, route_options = {})
15 | sentinel = 'ActionController::Routing::Routes.draw do |map|'
16 |
17 | logger.route "map.#{name} '#{path}', :controller => '#{route_options[:controller]}', :action => '#{route_options[:action]}'"
18 | unless options[:pretend]
19 | gsub_file 'config/routes.rb', /(#{Regexp.escape(sentinel)})/mi do |match|
20 | "#{match}\n map.#{name} '#{path}', :controller => '#{route_options[:controller]}', :action => '#{route_options[:action]}'"
21 | end
22 | end
23 | end
24 |
25 | def insert_into(file, line)
26 | logger.insert "#{line} into #{file}"
27 | unless options[:pretend]
28 | gsub_file file, /^(class .+|module .+|ActionController::Routing::Routes.draw do \|map\|)$/ do |match|
29 | "#{match}\n #{line}"
30 | end
31 | end
32 | end
33 |
34 | def insert_before(file, line)
35 | logger.insert "#{line} into #{file}"
36 | unless options[:pretend]
37 | gsub_file file, /^(Spec::Runner.configure do \|config\|)$/ do |match|
38 | "#{line}\n #{match}"
39 | end
40 | end
41 | end
42 |
43 | def append_into(file, line)
44 | logger.insert "#{line} into #{file}"
45 | unless options[:pretend]
46 | append_file(file, line)
47 | end
48 | end
49 |
50 | def insert_close_to(file, regexp, line)
51 | logger.insert "#{line} into #{file}"
52 | unless options[:pretend]
53 | gsub_file file, Regexp.new(regexp) do |match|
54 | "#{match}\n #{line}"
55 | end
56 | end
57 | end
58 |
59 | protected
60 |
61 | def append_file(relative_destination, line)
62 | path = destination_path(relative_destination)
63 | content = File.read(path) + "\n #{line}"
64 | File.open(path, 'wb') { |file| file.write(content) }
65 | end
66 | end
67 |
68 | Rails::Generator::Commands::Destroy.class_eval do
69 | def route_resource(*resources)
70 | resource_list = resources.map { |r| r.to_sym.inspect }.join(', ')
71 | look_for = "\n map.resource #{resource_list}\n"
72 | logger.route "map.resource #{resource_list}"
73 | unless options[:pretend]
74 | gsub_file 'config/routes.rb', /(#{look_for})/mi, ''
75 | end
76 | end
77 |
78 | def route_name(name, path, route_options = {})
79 | look_for = "\n map.#{name} '#{path}', :controller => '#{route_options[:controller]}', :action => '#{route_options[:action]}'"
80 | logger.route "map.#{name} '#{path}', :controller => '#{route_options[:controller]}', :action => '#{route_options[:action]}'"
81 | unless options[:pretend]
82 | gsub_file 'config/routes.rb', /(#{look_for})/mi, ''
83 | end
84 | end
85 |
86 | def insert_into(file, line)
87 | logger.remove "#{line} from #{file}"
88 | unless options[:pretend]
89 | gsub_file file, "\n #{line}", ''
90 | end
91 | end
92 |
93 | def insert_before(file, line)
94 | insert_into(file, line)
95 | end
96 |
97 | def insert_close_to(file, regexp, line)
98 | insert_into(file, line)
99 | end
100 | end
101 |
102 | Rails::Generator::Commands::List.class_eval do
103 | def route_resource(*resources)
104 | resource_list = resources.map { |r| r.to_sym.inspect }.join(', ')
105 | logger.route "map.resource #{resource_list}"
106 | end
107 |
108 | def route_name(name, path, options = {})
109 | logger.route "map.#{name} '#{path}', :controller => '{options[:controller]}', :action => '#{options[:action]}'"
110 | end
111 |
112 | def insert_into(file, line)
113 | insert_into(file, line)
114 | end
115 |
116 | def insert_before(file, line)
117 | insert_into(file, line)
118 | end
119 |
120 | def insert_close_to(file, line)
121 | insert_into(file, line)
122 | end
123 | end
--------------------------------------------------------------------------------
/README.textile:
--------------------------------------------------------------------------------
1 | h1. Plongo
2 |
3 | Super simple CMS brick powered by Mongodb.
4 | The aim is to define in the view the way the administration interface will be in order to edit the page content.
5 | For now, that's more or less a proof of concept.
6 |
7 | h2. Pre-requisities
8 |
9 | Mongodb
10 |
11 | h2. Installation
12 |
13 | For now, only installation as a plugin is available.
14 |
15 |
16 | script/plugin install git://github.com/did/plongo.git
17 |
18 |
19 | Then, edit your database.yml and modify all your environments like this
20 |
21 |
22 | development:
23 | adapter: sqlite3
24 | database: db/development.sqlite3
25 | pool: 5
26 | timeout: 5000
27 | mongodb:
28 | host: localhost
29 | database: _development
30 |
31 |
32 | Create a file mongodb.rb and put it into the config/initializers folder
33 |
34 |
35 | cfg = Rails.configuration.database_configuration[RAILS_ENV]['mongodb']
36 | MongoMapper.connection = Mongo::Connection.new(cfg['host'])
37 | MongoMapper.database = cfg['database']
38 |
39 |
40 | Do not forget to update your config.rb file by adding reference to new gems
41 |
42 |
43 | config.gem 'mongo', :source => 'http://gemcutter.org'
44 | config.gem 'mongo_ext', :source => 'http://gemcutter.org', :lib => false
45 | config.gem 'mongo_mapper', :source => 'http://gemcutter.org', :version => '0.7.0'
46 |
47 |
48 | Finally, generate back-office interface (for now, it is installed in admin/)
49 |
50 |
51 | ./script/generate plongo_admin
52 |
53 |
54 | h2. How it works ?
55 |
56 | A view template is considered as a page in plongo. So once you define a component inside such as a text, a new plongo page is created with default parameters based on the controller / action. Parameters may be overidden.
57 | Each page is unique thanks to its path (<controller>/<action>).
58 |
59 | h2. Usage
60 |
61 | Plongo comes with some useful view helpers.
62 |
63 | h3. Simple text
64 |
65 |
66 | <%= content_tag :h2, 'A title by default', :plongo => { :key => 'title', :name => 'Page title', :priority => 1 } %>
67 |
68 |
69 | will display a h2 tag and the content will be editable thru a back-office interface (in progress)
70 |
71 | _Note: priority gives the position of the element in the back-office interface.
72 |
73 |
74 | <%= content_tag :div, 'Lorem ipsum....', :plongo => { :key => 'a_paragraph', :name => 'Main paragraph' } %>
75 |
76 |
77 | h3. Image
78 |
79 |
80 | <%= image_tag 'default url', :plongo_key => 'image', :alt => 'Article image', :size => '400x200' %>
81 |
82 |
83 | will display an image uploaded thru a back-office interface. The uploaded image will also be cropped based on the size attribute.
84 |
85 | h3. List of items
86 |
87 | This is problably the most powerful in Plongo. It makes the edition of HTML list super easy. HTML list can be carousel, simple UL list, ...etc.
88 |
89 |
90 | <% content_tag :ul, :plongo => { :key => 'features', :name => 'List of features', :highlight => 'title' } do %>
91 |
92 | <%= content_tag :h3, :plongo => { :key => 'title', :name => 'Title of the feature' } %>
93 |
94 | <%= image_tag 'default url', :plongo_key => 'screenshot', :alt => 'Screenshot of the feature', :size => '100x100' %>
95 |
96 |
97 | <%= plongo_content 'description', :text, :name => 'A description', :value => 'Lorem ipsum....' %>
98 |
99 |
100 | <% end %>
101 |
102 |
103 | The HTML content inside UL is the template for the list. Each item will have the same "layout" (title + image +text)
104 |
105 | _note:The ':highlight' attribute defines the property displayed to represent the element in the back-office interface.
106 |
107 | h3. Changing page attributes
108 |
109 | In some cases, you may want to change page attributes.
110 |
111 |
112 | <% plongo_page :name => 'Welcome page', :path => '/home' %>
113 |
114 |
115 | h2. Tests / Bugs / Evolutions
116 |
117 | The plugin is fully tests with rspec (unit / functional tests). Into the plugin folder, type
118 |
119 |
120 | rake
121 |
122 |
123 | You may find bugs, sure you will actually. If you have time to investigate and solve them, just apply the classic procedure (fork, fix, test and submit).
124 |
125 | For evolutions, you're welcome to suggest your ideas. Contact me at didier at nocoffee dot fr.
126 |
127 |
128 | Copyright (c) 2010 NoCoffee, released under the MIT license
--------------------------------------------------------------------------------
/generators/plongo_admin/templates/delegate.js:
--------------------------------------------------------------------------------
1 | (function($) {
2 |
3 | $.fn.delegate = function(eventType, rules) {
4 |
5 | var makeArgs = function(args, el) {
6 | var args = $.makeArray(args);
7 | args.length < 2 ? args.push(el) : args.splice(1, 0, el);
8 | return args;
9 | };
10 |
11 | // In IE reset/submit do not bubble.
12 | // Safari 2 submit does not bubble.
13 | if (($.browser.msie && /reset|submit/.test(eventType))
14 | || ($.browser.safari && parseInt($.browser.version) < 500 && eventType == 'submit')) {
15 |
16 | if (eventType == 'reset') {
17 | // reset:
18 | // click [type=reset]
19 | // press escape [type=text], [type=password], textarea
20 | this.bind('click', function(e) {
21 | for (var selector in rules) {
22 | var $target = $(e.target);
23 | if ($target.is('[type=reset]')
24 | && $(e.target.form).is(selector)) {
25 |
26 | arguments[0] = $.event.fix(e);
27 | arguments[0].target = e.target.form;
28 | arguments[0].type = eventType;
29 | var ret = rules[selector].apply(this, makeArgs(arguments, e.target.form)); // required if confirm() is involved
30 | return ret;
31 | }
32 | }
33 | });
34 | this.bind('keypress', function(e) {
35 | for (var selector in rules) {
36 | var $target = $(e.target);
37 | if ($target.is('[type=text], [type=password], textarea')
38 | && $(e.target.form).is(selector)
39 | && e.keyCode == 27) {
40 |
41 | arguments[0] = $.event.fix(e);
42 | arguments[0].target = e.target.form;
43 | arguments[0].type = eventType;
44 | var ret = rules[selector].apply(this, makeArgs(arguments, e.target.form)); // required if confirm() is involved
45 | return ret;
46 | }
47 | }
48 | });
49 | }
50 |
51 | if (eventType == 'submit') {
52 | // submit:
53 | // click [type=submit], [type=image]
54 | // press enter [type=text], [type=password], textarea
55 | this.bind('click', function(e) {
56 | for (var selector in rules) {
57 | var $target = $(e.target), form;
58 | if (($target.is('[type=submit], [type=image]')
59 | || ($target = $(e.target).parents('[type=submit], [type=image]')) && $target.length)
60 | && $((form = $target[0].form)).is(selector)) {
61 |
62 | arguments[0] = $.event.fix(e);
63 | arguments[0].target = form;
64 | arguments[0].type = eventType;
65 | var ret = rules[selector].apply(this, makeArgs(arguments, form)); // required if confirm() is involved
66 | return ret;
67 | }
68 | }
69 | });
70 | this.bind('keypress', function(e) {
71 | for (var selector in rules) {
72 | var $target = $(e.target);
73 | if ($target.is('[type=text], [type=password], textarea')
74 | && $(e.target.form).is(selector)
75 | && e.keyCode == 13) {
76 |
77 | arguments[0] = $.event.fix(e);
78 | arguments[0].target = e.target.form;
79 | arguments[0].type = eventType;
80 | var ret = rules[selector].apply(this, makeArgs(arguments, e.target.form)); // required if confirm() is involved
81 | return ret;
82 | }
83 | }
84 | });
85 | }
86 |
87 | return this;
88 | }
89 |
90 | return this.bind(eventType, function(e) {
91 | for (var selector in rules) {
92 | var $target = $(e.target);
93 | if ($target.is(selector) || ($target = $target.parents(selector)) && $target.length)
94 | return rules[selector].apply(this, makeArgs(arguments, $target[0]));
95 | }
96 | });
97 | };
98 |
99 | })(jQuery);
--------------------------------------------------------------------------------
/lib/plongo/rails/content_tag_helper.rb:
--------------------------------------------------------------------------------
1 | module Plongo
2 | module Rails
3 |
4 | module ContentTagHelper
5 |
6 | def self.included(base)
7 | base.send :alias_method_chain, :content_tag, :plongo
8 | end
9 |
10 | def content_tag_with_plongo(name, content_or_options_with_block = nil, options = nil, escape = true, &block)
11 | if block_given?
12 | options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash)
13 |
14 | if options.is_a?(Hash) && (!options.symbolize_keys[:plongo_key].blank? || !options.symbolize_keys[:plongo].blank?)
15 | plongo_options = options.delete(:plongo) || {}
16 | key = options.delete(:plongo_key) || plongo_options[:key]
17 |
18 | element = add_plongo_element(name, key, {
19 | :value => content_or_options_with_block,
20 | :name => key.humanize
21 | }.merge(plongo_options), &block)
22 |
23 | if element.respond_to?(:metadata_keys)
24 | # puts "collection found !!!"
25 | # dealing with collection type
26 |
27 | if element.items.empty?
28 | # puts "initializing collection.....#{element.inspect}"
29 | # element.metadata_keys = []
30 | @plongo_collection = element
31 | @plongo_last_item = true
32 |
33 | output = content_tag_without_plongo(name, options, nil, escape, &block)
34 |
35 | @plongo_collection = nil
36 |
37 | plongo_page.save! # saving page at this time is required if the collection is used further
38 |
39 | # puts "collection saved !"
40 |
41 | output
42 | else
43 | # puts "================================================================="
44 | i = 0
45 | output = element.items.inject('') do |output, item|
46 | # puts "render item #{item.inspect}"
47 | @plongo_item = item
48 | @plongo_last_item = (i == (element.items.size - 1))
49 |
50 | # puts "i = #{i} / last item ? #{@plongo_last_item.inspect}"
51 |
52 | i += 1
53 |
54 | # puts "---->" + (foo = capture(&block))
55 | # puts "-------------------------------"
56 | output + capture(&block)
57 |
58 | end
59 |
60 | output = content_tag_string(name, output, options, escape)
61 |
62 | @plongo_item = nil # reset it
63 |
64 | # if block_called_from_erb?(block)
65 | # concat(output)
66 | # else
67 | # output
68 | # end
69 | # puts "[OUTPUT] #{name} / #{output} / #{options}"
70 | # content_tag_without_plongo(name, output, options, escape)
71 |
72 | if block_called_from_erb?(block)
73 | concat(output)
74 | else
75 | output
76 | end
77 | end
78 | else
79 | content_tag_without_plongo(name, element.value, options, escape)
80 | # content_tag_string(name, element.value, options, escape)
81 | # DONE
82 | end
83 | else
84 | # DONE
85 | content_tag_without_plongo(name, options, nil, escape, &block)
86 | end
87 | else
88 | # DONE
89 | if options.is_a?(Hash) && (!options.symbolize_keys[:plongo_key].blank? || !options.symbolize_keys[:plongo].blank?)
90 | plongo_options = options.delete(:plongo) || {}
91 | key = options.delete(:plongo_key) || plongo_options[:key]
92 |
93 | element = add_plongo_element(name, key, {
94 | :value => content_or_options_with_block,
95 | :name => key.humanize
96 | }.merge(plongo_options), &block)
97 |
98 | # puts "!!! writing #{element.value} !!!"
99 |
100 | # content_tag_without_plongo(name, element.value, options, escape)
101 | content_or_options_with_block = element.value
102 | end
103 |
104 | content_tag_without_plongo(name, content_or_options_with_block, options, escape)
105 | end
106 | end
107 |
108 | end
109 |
110 | # class CollectionItemProxy
111 | #
112 | # def initialize(collection)
113 | # puts "creating collection !"
114 | # @collection = collection
115 | # end
116 | #
117 | # # def method_missing(name, *args)
118 | # # puts "calling #{name}"
119 | # # end
120 | #
121 | # def content_tag(*args)
122 | # puts "calling #{@collection.name}"
123 | # end
124 | #
125 | # def foo
126 | # puts "hello world !!!"
127 | # end
128 | #
129 | # def to_s
130 | # "CollectionItemProxy ready (#{@collection.name}) !"
131 | # end
132 | #
133 | # end
134 |
135 | end
136 | end
137 |
138 |
139 | # if block_given? && block.arity == 1
140 | # logger.debug "===> Collection found !!!!"
141 | # end
142 |
143 |
144 |
145 |
146 | # content_tag_without_plongo(name, content_or_options_with_block, options, escape, &block)
147 |
148 |
149 | # if element.elements.empty?
150 | # content_tag_string(name, "Collection empty", options, escape)
151 | # else
152 | # output = element.items('').inject do |output, item|
153 | # output + content_tag_string(name, capture(Plongo::Rails::CollectionItemProxy.new(item), &block), options, escape)
154 | # end
155 | #
156 | # if block_called_from_erb?(block)
157 | # concat(output)
158 | # else
159 | # output
160 | # end
161 | # end
--------------------------------------------------------------------------------
/generators/plongo_admin/templates/public/javascripts/admin/plugins/ocupload.js:
--------------------------------------------------------------------------------
1 | /*
2 | * One Click Upload - jQuery Plugin
3 | * Copyright (c) 2008 Michael Mitchell - http://www.michaelmitchell.co.nz
4 | * Patched/fixed by Andrey Gayvoronsky - http://www.gayvoronsky.com
5 | */
6 | (function($){
7 | $.fn.upload = function(options) {
8 | // Merge the users options with our defaults
9 | options = $.extend({
10 | name: 'file',
11 | enctype: 'multipart/form-data',
12 | action: '',
13 | autoSubmit: true,
14 | onSubmit: function() {},
15 | onComplete: function() {},
16 | onSelect: function() {},
17 | params: {}
18 | }, options);
19 |
20 | return new $.ocupload(this, options);
21 | },
22 |
23 | $.ocupload = function(element, options) {
24 | // Fix scope problems
25 | var self = this;
26 |
27 | // A unique id so we can find our elements later
28 | var id = new Date().getTime().toString().substr(8);
29 |
30 | // Upload Iframe
31 | var iframe = $(
32 | '
'
37 | ).css({
38 | display: 'none'
39 | });
40 |
41 | // Form
42 | var form = $(
43 | '
'
49 | ).css({
50 | margin: 0,
51 | padding: 0
52 | });
53 |
54 | // File Input
55 | var input = $(
56 | '
'
60 | ).css({
61 | 'width': 'auto',
62 | 'position': 'absolute',
63 | 'right': 0,
64 | 'top': 0,
65 | 'opacity': 0,
66 | 'zoom': 1,
67 | 'filter': 'alpha(opacity=0)',
68 | 'border': 0,
69 | 'font-size': '10em'
70 | });
71 |
72 | // Put everything together
73 | element.wrap('
'); //container
74 |
75 | element.wrap(form);
76 | element.wrap('
');
77 |
78 | // Find the container and make it nice and snug
79 | element.parent().css({
80 | 'float': 'left',
81 | 'white-space': 'nowrap',
82 | 'position': 'relative',
83 | 'z-index': 1,
84 | 'left': 0,
85 | 'top': 0,
86 | 'overflow': 'hidden',
87 | 'display': 'inline',
88 | 'border': 0
89 | });
90 |
91 | element.after(input);
92 | element.parent().parent().after(iframe);
93 | form = input.parent().parent(); //FIX for correct submiting
94 |
95 | // Watch for file selection
96 | input.change(function() {
97 | // Do something when a file is selected.
98 | self.onSelect();
99 |
100 | // Submit the form automaticly after selecting the file
101 | if(self.autoSubmit) {
102 | self.submit();
103 | }
104 | });
105 |
106 | // Methods
107 | $.extend(this, {
108 | autoSubmit: options.autoSubmit,
109 | onSubmit: options.onSubmit,
110 | onComplete: options.onComplete,
111 | onSelect: options.onSelect,
112 |
113 | // get filename
114 | filename: function() {
115 | return input.attr('value');
116 | },
117 |
118 | // get/set params
119 | params: function(params) {
120 | var params = params ? params : false;
121 |
122 | if(params) {
123 | options.params = $.extend(options.params, params);
124 | }
125 | else {
126 | return options.params;
127 | }
128 | },
129 |
130 | // get/set name
131 | name: function(name) {
132 | var name = name ? name : false;
133 |
134 | if(name) {
135 | input.attr('name', value);
136 | }
137 | else {
138 | return input.attr('name');
139 | }
140 | },
141 |
142 | // get/set action
143 | action: function(action) {
144 | var action = action ? action : false;
145 |
146 | if(action) {
147 | form.attr('action', action);
148 | }
149 | else {
150 | return form.attr('action');
151 | }
152 | },
153 |
154 | // get/set enctype
155 | enctype: function(enctype) {
156 | var enctype = enctype ? enctype : false;
157 |
158 | if(enctype) {
159 | form.attr('enctype', enctype);
160 | }
161 | else {
162 | return form.attr('enctype');
163 | }
164 | },
165 |
166 | // set options
167 | set: function(obj, value) {
168 | var value = value ? value : false;
169 |
170 | function option(action, value) {
171 | switch(action) {
172 | default:
173 | throw new Error('[jQuery.ocupload.set] \''+action+'\' is an invalid option.');
174 | break;
175 | case 'name':
176 | self.name(value);
177 | break;
178 | case 'action':
179 | self.action(value);
180 | break;
181 | case 'enctype':
182 | self.enctype(value);
183 | break;
184 | case 'params':
185 | self.params(value);
186 | break;
187 | case 'autoSubmit':
188 | self.autoSubmit = value;
189 | break;
190 | case 'onSubmit':
191 | self.onSubmit = value;
192 | break;
193 | case 'onComplete':
194 | self.onComplete = value;
195 | break;
196 | case 'onSelect':
197 | self.onSelect = value;
198 | break;
199 | }
200 | }
201 |
202 | if(value) {
203 | option(obj, value);
204 | }
205 | else {
206 | $.each(obj, function(key, value) {
207 | option(key, value);
208 | });
209 | }
210 | },
211 |
212 | // Submit the form
213 | submit: function() {
214 | // Do something before we upload
215 | this.onSubmit();
216 | // add additional paramters before sending
217 | $.each(options.params, function(key, value) {
218 | form.append($(
219 | '
'
224 | ));
225 | });
226 |
227 | // Submit the actual form.
228 | // In that way, because we don't want jquery events (it fires submit event for other parent form)
229 | form.get(0).submit();
230 |
231 | // Do something after we are finished uploading
232 | iframe.unbind().load(function() {
233 | // Get a response from the server in plain text
234 | var myFrame = document.getElementById(iframe.attr('name'));
235 | var response = $(myFrame.contentWindow.document.body).text();
236 |
237 | // Do something on complete
238 | self.onComplete(response); //done :D
239 | });
240 | }
241 | });
242 | }
243 | })(jQuery);
--------------------------------------------------------------------------------
/lib/plongo/rails/admin_helper.rb:
--------------------------------------------------------------------------------
1 | module Plongo
2 | module Rails
3 | module AdminHelper
4 |
5 | def plongo_form(page, url, options = {}, &block)
6 | form_options = { :method => :put, :multipart => true }.merge(options)
7 |
8 | if block_given?
9 | form_tag(url, form_options, &block)
10 | else
11 | form_tag(url, form_options) do
12 | concat(plongo_fields(page))
13 | concat(content_tag(:div, submit_tag('Update'), :class => 'actions'))
14 | end
15 | end
16 | end
17 |
18 | def plongo_fields(page)
19 | content_tag(:fieldset) do
20 | page.sorted_elements.inject('') do |html, element|
21 | html << content_tag(:legend, element.name)
22 | html << (content_tag(:ol) do
23 | concat(plongo_field(element))
24 | end)
25 | end
26 | end
27 | end
28 |
29 | def plongo_field(element, options = {}, &block)
30 | type = plongo_type_name((options[:metadata_key] || element)._type)
31 |
32 | template = "plongo_#{type}_field".to_sym
33 |
34 | if self.respond_to?(template)
35 | content_tag :li, :class => "plongo-#{type} #{'error' if not element.valid?}" do
36 | html = block_given? ? block.call : ''
37 |
38 | html << content_tag(:label, element.name) if options[:with_label] == true
39 |
40 | if options[:parent_element]
41 | options[:class] = 'highlight' if element.key == options[:parent_element].highlight
42 |
43 | html << hidden_field_tag(plongo_tag_name('key', element, options), element.key)
44 | end
45 |
46 | html << self.send(template, element, options)
47 |
48 | if not element.valid?
49 | html << content_tag(:p, element.errors.full_messages.to_sentence, :class => 'inline-errors')
50 | end
51 |
52 | html
53 | end
54 | else
55 | raise "Unknown plongo field tag (#{type})"
56 | end
57 | end
58 |
59 | def plongo_collection_field(collection, options = {})
60 | html = ''
61 |
62 | # items
63 | collection.items.each_with_index do |item, index|
64 | html << content_tag(:div, :id => "plongo-item-#{item.id}", :class => 'plongo-item') do
65 | content_tag(:h3, plongo_item_name(collection, item, index)) +
66 | (content_tag(:ol) do
67 | item.sorted_elements.inject('') do |memo, nested_element|
68 | memo << plongo_field(nested_element, { :parent_element => collection, :item => item, :with_label => true })
69 | end
70 | end) +
71 | link_to('Remove', '#', :class => 'remove-button') +
72 | hidden_field_tag(plongo_tag_name('_delete', item, { :parent_element => collection }), '') +
73 | hidden_field_tag(plongo_tag_name('_position', item, { :parent_element => collection }), index, :class => 'position')
74 | end
75 | end
76 |
77 | # new item
78 | html << (content_tag(:div, :class => 'plongo-new-item') do
79 | content_tag(:h3, plongo_item_name(collection)) + (content_tag(:ol) do
80 | nested_html = ''
81 | collection.sorted_metadata_keys.each_with_index do |metadata_key, index|
82 | nested_html << (plongo_field(metadata_key, { :parent_element => collection, :index => index, :with_label => true }) do
83 | plongo_metadata_key_hidden_fields collection, metadata_key, index
84 | end)
85 | end
86 | nested_html
87 | end) +
88 | link_to('Remove', '#', :class => 'remove-button') +
89 | hidden_field_tag(plongo_tag_name('_position', nil, { :parent_element => collection }), 0, :class => 'position')
90 | end)
91 |
92 | html << content_tag(:p, link_to('Add', '#', :class => 'add-button'), :class => 'action')
93 | end
94 |
95 | def plongo_input_field(input, options = {}, &block)
96 | text_field_tag(plongo_tag_name('value', input, options), input.value, :class => options[:class])
97 | end
98 |
99 | def plongo_text_field(text, options = {}, &block)
100 | text_area_tag(plongo_tag_name('value', text, options), text.value)
101 | end
102 |
103 | def plongo_image_field(image, options = {}, &block)
104 | html = ''
105 |
106 | html << (content_tag(:div, :class => 'preview') do
107 | nested_html = link_to(image_tag(image.source.url(:thumbnail)), image.source.url)
108 | nested_html << content_tag(:p, image.source_file_name)
109 | end) if image.source?
110 |
111 | html << file_field_tag(plongo_tag_name('source', image, options))
112 |
113 | html << '
'
114 | end
115 |
116 | def plongo_type_name(klass)
117 | (klass.is_a?(String) ? klass : klass.name).demodulize.downcase
118 | end
119 |
120 | def plongo_metadata_key_hidden_fields(element, metadata_key, index)
121 | %w{_type key name priority}.inject('') do |html, name|
122 | tag_name = plongo_tag_name(name.gsub(/^_/, ''), element, { :index => index })
123 | html << hidden_field_tag(tag_name, metadata_key.send(name.to_sym))
124 | end
125 | end
126 |
127 | def plongo_tag_name(name, element, options = {})
128 | parent_element = options[:parent_element] || element
129 |
130 | with_metadata_key = !options[:index].nil?
131 | top_level_element = options[:item].nil? && !with_metadata_key && parent_element == element
132 |
133 | common_part = "page[element_attributes][#{parent_element.id}]"
134 |
135 | if top_level_element
136 | "#{common_part}[#{name}]"
137 | elsif with_metadata_key
138 | "#{common_part}[item_attributes][NEW_RECORD_0][element_attributes][#{options[:index]}][#{name}]"
139 | elsif options[:item].nil?
140 | "#{common_part}[item_attributes][#{element ? element.id : 'NEW_RECORD_0'}][#{name}]"
141 | else
142 | "#{common_part}[item_attributes][#{options[:item].id}][element_attributes][#{element.id}][#{name}]"
143 | end
144 | end
145 |
146 | def plongo_item_name(collection, item = nil, index = nil)
147 | if collection.highlight.nil?
148 | index ? "Item ##{index + 1}" : "New Item"
149 | else
150 | if item
151 | item.find_element_by_key(collection.highlight).value
152 | else
153 | collection.find_metadata_key_by_key(collection.highlight).value
154 | end
155 | end
156 | end
157 | end
158 | end
159 | end
--------------------------------------------------------------------------------
/generators/plongo_admin/templates/public/stylesheets/admin/plugins/rte.css:
--------------------------------------------------------------------------------
1 | .rte-zone {
2 | margin: 0;
3 | padding: 0;
4 | border: 1px #999 solid;
5 | clear: both;
6 | font: 10px Tahoma, Verdana, Arial, Helvetica, sans-serif;
7 | }
8 |
9 | .rte-zone textarea {
10 | padding: 0;
11 | margin: 0;
12 | border: 0;
13 | position: relative;
14 | left:0;
15 | clear: both;
16 | }
17 |
18 | .rte-resizer {
19 | width: 100%;
20 | height: 20px;
21 | margin:0;
22 | padding: 0;
23 | display: block;
24 | border-top: 1px solid #999;
25 | background-color: #fdfdfd;
26 | }
27 |
28 | .rte-resizer a {
29 | background: url('/images/admin/plugins/rte_icons.gif') no-repeat 0 0;
30 | background-position: 0 -688px;
31 | width: 16px;
32 | height: 16px;
33 | display: block;
34 | float: right;
35 | cursor: se-resize;
36 | margin-top: 4px;
37 | }
38 | .rte-toolbar {
39 | width: 100%;
40 | margin:0;
41 | padding: 0;
42 | display: block;
43 | border-bottom: 1px dashed #999;
44 | background-color: #fdfdfd;
45 | font: 10px Tahoma, Verdana, Arial, Helvetica, sans-serif;
46 | }
47 |
48 | .rte-toolbar p {
49 | margin: 0;
50 | padding: 0;
51 | clear: both;
52 | }
53 |
54 | .rte-toolbar select {
55 | font: 10px Tahoma, Verdana, Arial, Helvetica, sans-serif;
56 | height: 16px;
57 | padding: 0;
58 | margin: 0;
59 | }
60 |
61 | .rte-panel {
62 | position: absolute;
63 | left: 0;
64 | top: 0;
65 | border: 1px solid #999;
66 | display: block;
67 | clear: both;
68 | margin: 0px;
69 | padding: 5px 5px 0 5px;
70 | background: #f0f0f0;
71 | font: 10px Tahoma, Verdana, Arial, Helvetica, sans-serif;
72 | }
73 |
74 | .rte-panel div.rte-panel-title {
75 | font-weight: bold;
76 | margin: -5px -5px 5px -5px;
77 | padding: 5px;
78 | height: 16px;
79 | line-height: 16px;
80 | background: #e0e0e0;
81 | border-bottom: 1px solid #ccc;
82 | display: block;
83 | clear: both;
84 | cursor: move;
85 | }
86 |
87 | .rte-panel div.rte-panel-title .close {
88 | position: absolute;
89 | top: 0;
90 | right: 0;
91 | display: block;
92 | float: right;
93 | text-decoration: none;
94 | font-size: 14px;
95 | font-weight: bold;
96 | color: #f00;
97 | }
98 |
99 | .rte-panel label {
100 | display: block;
101 | float: left;
102 | width: 50px;
103 | margin: 0 5px 0 2px;
104 | font-weight: bold;
105 | font-size: 10px;
106 | text-align: right;
107 | line-height: 20px;
108 | font-size: 100%;
109 | }
110 |
111 | .rte-panel input, .rte-panel select {
112 | margin: 0 5px 0 2px;
113 | padding: 0;
114 | height: 20px;
115 | font-size: 10px;
116 | border: 1px solid #ccc;
117 | float: left;
118 | vertical-align: middle;
119 | line-height: 20px;
120 | }
121 |
122 | .rte-panel button {
123 | margin: 0 5px 0 2px;
124 | padding: 2px 5px;
125 | font-size: 10px;
126 | border: 1px solid #ccc;
127 | float: left;
128 | vertical-align: middle;
129 | }
130 |
131 | .rte-panel p.submit {
132 | margin: 5px -5px 0 -5px;
133 | padding: 5px;
134 | height: 20px;
135 | line-height: 20px;
136 | background: #e0e0e0;
137 | border-top: 1px solid #ccc;
138 | display: block;
139 | clear: both;
140 | }
141 | .rte-panel p.submit button {
142 | width: 60px;
143 | padding: 2px 5px;
144 | margin-left: 10px;
145 | font-weight: bold;
146 | }
147 |
148 | .boxy-wrapper .colorpicker1, .boxy-wrapper .colorpicker2 {
149 | margin: 0 5px 0 0;
150 | padding: 0;
151 | float: left;
152 | border: 1px solid #000;
153 | }
154 |
155 | .boxy-wrapper .colorpicker2 {
156 | margin: 0;
157 | border: 0;
158 | }
159 |
160 | .boxy-wrapper .colorpicker1 .rgb {
161 | background: url('/images/admin/plugins/rte_colorpicker_rgb.jpg') no-repeat 0 0;
162 | width: 300px;
163 | height: 150px;
164 | cursor: crosshair;
165 | }
166 |
167 | .boxy-wrapper .colorpicker1 .gray{
168 | background: url('/images/admin/plugins/rte_colorpicker_gray.jpg') no-repeat 0 0;
169 | width: 15px;
170 | height: 150px;
171 | cursor: crosshair;
172 | }
173 |
174 | .boxy-wrapper .colorpicker2 .preview {
175 | margin: 3px 0;
176 | padding: 0;
177 | width: 50px;
178 | height: 50px;
179 | border: 1px solid #000;
180 | clear: both;
181 | background: #000;
182 | }
183 |
184 | .boxy-wrapper .colorpicker2 .color {
185 | margin: 3px 0;
186 | padding: 0;
187 | clear: both;
188 | }
189 |
190 | .boxy-wrapper .colorpicker2 .palette {
191 | margin: 0;
192 | padding: 0;
193 | width: 50px;
194 | height: 50px;
195 | border: 1px solid #000;
196 | cursor: crosshair;
197 | clear: both;
198 | font-size: 1px;
199 | }
200 |
201 | .rte-panel .symbols {
202 | margin: 0;
203 | padding: 0;
204 | clear: both;
205 | /* border-top: 1px solid #000;
206 | border-left: 1px solid #000;*/
207 | }
208 |
209 | .rte-panel .symbols a {
210 | font-size: 14px;
211 | line-height: 14px;
212 | vertical-align: middle;
213 | text-align: center;
214 | width: 18px;
215 | height:18px;
216 | float: left;
217 | color: #000;
218 | text-decoration: none;
219 | }
220 |
221 | .rte-panel .symbols a:hover {
222 | background: #ccc;
223 | }
224 |
225 | .boxy-wrapper .colorpicker2 .palette .item {
226 | width: 10px;
227 | height: 10px;
228 | margin: 0;
229 | padding: 0;
230 | float: left;
231 | cursor: crosshair;
232 | border: 0;
233 | }
234 |
235 |
236 | .rte-panel img {
237 | padding:0;
238 | margin:0;
239 | border:0;
240 | }
241 |
242 | .rte-toolbar div.clear {
243 | display: block;
244 | clear: both;
245 | border: 0;
246 | padding: 0;
247 | padding: 2px 0 0 0;
248 | margin: 0;
249 | }
250 |
251 | .rte-toolbar ul {
252 | display: block;
253 | margin: 0px;
254 | padding: 0;
255 | width: 100%;
256 | }
257 |
258 | .rte-toolbar ul li {
259 | list-style-type: none;
260 | float: left;
261 | padding: 0;
262 | margin: 5px 2px;
263 | height: 16px;
264 | }
265 |
266 | .rte-toolbar ul li.separator {
267 | height: 16px;
268 | margin: 5px;
269 | border-left: 1px solid #ccc;
270 | }
271 |
272 | .rte-toolbar ul li a {
273 | border: 1px solid #fdfdfd;
274 | display: block;
275 | width: 16px;
276 | height: 16px;
277 | background: url('/images/admin/plugins/rte_icons.gif') no-repeat 0 0;
278 | cursor: pointer;
279 | margin: 0;
280 | padding: 0;
281 | opacity: 0.5;
282 | -moz-opacity: 0.5;
283 | filter: alpha(opacity = 50);
284 | }
285 |
286 | .rte-toolbar ul li a:hover, .rte-toolbar ul li a.active {
287 | opacity: 1.0;
288 | -moz-opacity: 1.0;
289 | filter: alpha(opacity = 100);
290 | }
291 |
292 | .rte-toolbar ul li a.active {
293 | background-color: #f9f9f9;
294 | border: 1px solid #ccc;
295 | }
296 |
297 | .rte-toolbar ul li a.empty { background-position: 0px 0px; }
298 | .rte-toolbar ul li a.bold { background-position: 0 -112px; }
299 | .rte-toolbar ul li a.italic { background-position: 0 -128px; }
300 | .rte-toolbar ul li a.strikeThrough { background-position: 0 -144px; }
301 | .rte-toolbar ul li a.underline { background-position: 0 -160px; }
302 | .rte-toolbar ul li a.subscript { background-position: 0 -176px; }
303 | .rte-toolbar ul li a.superscript { background-position: 0 -192px; }
304 | .rte-toolbar ul li a.disable { background-position: 0 -480px; }
305 | .rte-toolbar ul li a.enable { background-position: 0 -592px; }
306 | .rte-toolbar ul li a.unorderedList { background-position: 0 -320px; }
307 | .rte-toolbar ul li a.orderedList{ background-position: 0 -336px; }
308 | .rte-toolbar ul li a.justifyLeft { background-position: 0 -16px; }
309 | .rte-toolbar ul li a.justifyCenter { background-position: 0 -32px; }
310 | .rte-toolbar ul li a.justifyRight { background-position: 0 -48px; }
311 | .rte-toolbar ul li a.justifyFull { background-position: 0 -64px; }
312 | .rte-toolbar ul li a.indent { background-position: 0 -80px; }
313 | .rte-toolbar ul li a.outdent { background-position: 0 -96px; }
314 | .rte-toolbar ul li a.removeFormat { background-position: 0 -352px; }
315 | .rte-toolbar ul li a.h1 { background-position: 0 -208px; }
316 | .rte-toolbar ul li a.h2 { background-position: 0 -224px; }
317 | .rte-toolbar ul li a.h3 { background-position: 0 -240px; }
318 | .rte-toolbar ul li a.h4 { background-position: 0 -256px; }
319 | .rte-toolbar ul li a.h5 { background-position: 0 -272px; }
320 | .rte-toolbar ul li a.h6 { background-position: 0 -288px; }
321 | .rte-toolbar ul li a.increaseFontSize { background-position: 0 -512px; }
322 | .rte-toolbar ul li a.decreaseFontSize { background-position: 0 -528px; }
323 | .rte-toolbar ul li a.image { background-position: 0 -560px; }
324 | .rte-toolbar ul li a.word { background-position: 0 -576px; }
325 | .rte-toolbar ul li a.clear { background-position: 0 -608px; }
326 | .rte-toolbar ul li a.link { background-position: 0 -384px; }
327 | .rte-toolbar ul li a.color { background-position: 0 -624px; }
328 | .rte-toolbar ul li a.unlink { background-position: 0 -640px; }
329 |
330 | /* image / link picker */
331 |
332 | .boxy-wrapper div.clear {
333 | height: 1px;
334 | padding: 0px;
335 | margin: 0px;
336 | }
337 |
338 | .boxy-wrapper p.inputs {
339 | padding: 5px 15px 0 15px;
340 | }
341 |
342 | .boxy-wrapper #pick-assets {
343 | list-style: none;
344 | margin: 0px;
345 | padding-bottom: 0px;
346 | }
347 |
348 | .boxy-wrapper #pick-assets li {
349 | width: 400px;
350 | }
351 |
352 | .boxy-wrapper #pick-assets li a {
353 | padding: 5px;
354 | color: #333;
355 | position: relative;
356 | display: block;
357 | text-decoration: none;
358 | }
359 |
360 | .boxy-wrapper #pick-assets li a:hover {
361 | background-color: #333;
362 | color: #bbb;
363 | }
364 |
365 | .boxy-wrapper #pick-assets li a em {
366 | position: absolute;
367 | right: 10px;
368 | font-size: 0.8em;
369 | }
370 |
371 | .boxy-wrapper p.help {
372 | margin: 0px;
373 | font-size: 1em;
374 | }
375 |
376 | .boxy-wrapper p.submit {
377 | text-align: center;
378 | margin: 0px;
379 | padding: 0px 0 10px 0;
380 | }
381 |
382 | .boxy-wrapper p.loader { text-align: center; }
383 |
--------------------------------------------------------------------------------
/generators/plongo_admin/templates/public/javascripts/admin/plugins/rte.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Lightweight RTE - jQuery Plugin, version 1.2
3 | * Copyright (c) 2009 Andrey Gayvoronsky - http://www.gayvoronsky.com
4 | */
5 | jQuery.fn.rte = function(options, editors) {
6 | if(!editors || editors.constructor != Array)
7 | editors = new Array();
8 |
9 | $(this).each(function(i) {
10 | var id = (this.id) ? this.id : editors.length;
11 | editors[id] = new lwRTE (this, options || {});
12 | });
13 |
14 | return editors;
15 | }
16 |
17 | var lwRTE_resizer = function(textarea) {
18 | this.drag = false;
19 | this.rte_zone = $(textarea).parents('.rte-zone');
20 | }
21 |
22 | lwRTE_resizer.mousedown = function(resizer, e) {
23 | resizer.drag = true;
24 | resizer.event = (typeof(e) == "undefined") ? window.event : e;
25 | resizer.rte_obj = $(".rte-resizer", resizer.rte_zone).prev().eq(0);
26 | $('body', document).css('cursor', 'se-resize');
27 | return false;
28 | }
29 |
30 | lwRTE_resizer.mouseup = function(resizer, e) {
31 | resizer.drag = false;
32 | $('body', document).css('cursor', 'auto');
33 | return false;
34 | }
35 |
36 | lwRTE_resizer.mousemove = function(resizer, e) {
37 | if(resizer.drag) {
38 | e = (typeof(e) == "undefined") ? window.event : e;
39 | var w = Math.max(1, resizer.rte_zone.width() + e.screenX - resizer.event.screenX);
40 | var h = Math.max(1, resizer.rte_obj.height() + e.screenY - resizer.event.screenY);
41 | resizer.rte_zone.width(w);
42 | resizer.rte_obj.height(h);
43 | resizer.event = e;
44 | }
45 | return false;
46 | }
47 |
48 | var lwRTE = function (textarea, options) {
49 | this.css = [];
50 | this.css_class = options.frame_class || '';
51 | this.base_url = options.base_url || '';
52 | this.width = options.width || $(textarea).width() || '100%';
53 | this.height = options.height || $(textarea).height() || 350;
54 | this.iframe = null;
55 | this.iframe_doc = null;
56 | this.textarea = null;
57 | this.event = null;
58 | this.range = null;
59 | this.toolbars = {rte: '', html : ''};
60 | this.controls = {rte: {disable: {hint: 'Source editor'}}, html: {enable: {hint: 'Visual editor'}}};
61 | this.panel = null; // FIXME: Did
62 |
63 | $.extend(this.controls.rte, options.controls_rte || {});
64 | $.extend(this.controls.html, options.controls_html || {});
65 | $.extend(this.css, options.css || {});
66 |
67 | if(document.designMode || document.contentEditable) {
68 | $(textarea).wrap($('
').addClass('rte-zone').width(this.width));
69 | $('
').insertAfter(textarea);
70 |
71 | var resizer = new lwRTE_resizer(textarea);
72 |
73 | $(".rte-resizer a", $(textarea).parents('.rte-zone')).mousedown(function(e) {
74 | $(document).mousemove(function(e) {
75 | return lwRTE_resizer.mousemove(resizer, e);
76 | });
77 |
78 | $(document).mouseup(function(e) {
79 | return lwRTE_resizer.mouseup(resizer, e)
80 | });
81 |
82 | return lwRTE_resizer.mousedown(resizer, e);
83 | });
84 |
85 | this.textarea = textarea;
86 | this.enable_design_mode();
87 | }
88 | }
89 |
90 | lwRTE.prototype.editor_cmd = function(command, args) {
91 | this.iframe.contentWindow.focus();
92 | try {
93 | this.iframe_doc.execCommand(command, false, args);
94 | } catch(e) {
95 | }
96 | this.iframe.contentWindow.focus();
97 | }
98 |
99 | lwRTE.prototype.get_toolbar = function() {
100 | var editor = (this.iframe) ? $(this.iframe) : $(this.textarea);
101 | return (editor.prev().hasClass('rte-toolbar')) ? editor.prev() : null;
102 | }
103 |
104 | lwRTE.prototype.activate_toolbar = function(editor, tb) {
105 | var old_tb = this.get_toolbar();
106 |
107 | if(old_tb)
108 | old_tb.remove();
109 |
110 | $(editor).before($(tb).clone(true));
111 | }
112 |
113 | lwRTE.prototype.enable_design_mode = function() {
114 | var self = this;
115 |
116 | // need to be created this way
117 | self.iframe = document.createElement("iframe");
118 | self.iframe.frameBorder = 0;
119 | self.iframe.frameMargin = 0;
120 | self.iframe.framePadding = 0;
121 | self.iframe.width = '100%';
122 | self.iframe.height = self.height || '100%';
123 | self.iframe.src = "javascript:void(0);";
124 |
125 | if($(self.textarea).attr('class'))
126 | self.iframe.className = $(self.textarea).attr('class');
127 |
128 | if($(self.textarea).attr('id'))
129 | self.iframe.id = $(self.textarea).attr('id');
130 |
131 | if($(self.textarea).attr('name'))
132 | self.iframe.title = $(self.textarea).attr('name');
133 |
134 | var content = $(self.textarea).val();
135 |
136 | $(self.textarea).hide().after(self.iframe).remove();
137 | self.textarea = null;
138 |
139 | var css = '';
140 |
141 | for(var i in self.css)
142 | css += "
";
143 |
144 | var base = (self.base_url) ? "
" : '';
145 | var style = (self.css_class) ? "class='" + self.css_class + "'" : '';
146 |
147 | // Mozilla need this to display caret
148 | /*if($.trim(content) == '')
149 | content = '
';*/
150 |
151 | var doc = "" + base + css + "" + content + "";
152 |
153 | self.iframe_doc = self.iframe.contentWindow.document;
154 |
155 | try {
156 | self.iframe_doc.designMode = 'on';
157 | } catch ( e ) {
158 | // Will fail on Gecko if the editor is placed in an hidden container element
159 | // The design mode will be set ones the editor is focused
160 | $(self.iframe_doc).focus(function() { self.iframe_doc.designMode(); } );
161 | }
162 |
163 | self.iframe_doc.open();
164 | self.iframe_doc.write(doc);
165 | self.iframe_doc.close();
166 |
167 | if(!self.toolbars.rte)
168 | self.toolbars.rte = self.create_toolbar(self.controls.rte);
169 |
170 | self.activate_toolbar(self.iframe, self.toolbars.rte);
171 |
172 | $(self.iframe).parents('form').submit(
173 | function() { self.disable_design_mode(true); }
174 | );
175 |
176 | $(self.iframe_doc).mouseup(function(event) {
177 | if(self.iframe_doc.selection)
178 | self.range = self.iframe_doc.selection.createRange(); //store to restore later(IE fix)
179 |
180 | self.set_selected_controls( (event.target) ? event.target : event.srcElement, self.controls.rte);
181 | });
182 |
183 | $(self.iframe_doc).blur(function(event){
184 | if(self.iframe_doc.selection)
185 | self.range = self.iframe_doc.selection.createRange(); // same fix for IE as above
186 | });
187 |
188 | $(self.iframe_doc).keyup(function(event) { self.set_selected_controls( self.get_selected_element(), self.controls.rte); });
189 |
190 | // Mozilla CSS styling off
191 | if(!$.browser.msie)
192 | self.editor_cmd('styleWithCSS', false);
193 | }
194 |
195 | lwRTE.prototype.disable_design_mode = function(submit) {
196 | var self = this;
197 |
198 | self.textarea = (submit) ? $('
').get(0) : $('
').width('100%').height(self.height).get(0);
199 |
200 | if(self.iframe.className)
201 | self.textarea.className = self.iframe.className;
202 |
203 | if(self.iframe.id)
204 | self.textarea.id = self.iframe.id;
205 |
206 | if(self.iframe.title)
207 | self.textarea.name = self.iframe.title;
208 |
209 | $(self.textarea).val($('body', self.iframe_doc).html());
210 | $(self.iframe).before(self.textarea);
211 |
212 | if(!self.toolbars.html)
213 | self.toolbars.html = self.create_toolbar(self.controls.html);
214 |
215 | if(submit != true) {
216 | $(self.iframe_doc).remove(); //fix 'permission denied' bug in IE7 (jquery cache)
217 | $(self.iframe).remove();
218 | self.iframe = self.iframe_doc = null;
219 | self.activate_toolbar(self.textarea, self.toolbars.html);
220 | }
221 | }
222 |
223 | lwRTE.prototype.toolbar_click = function(obj, control) {
224 | var fn = control.exec;
225 | var args = control.args || [];
226 | var is_select = (obj.tagName.toUpperCase() == 'SELECT');
227 |
228 | $('.rte-panel', this.get_toolbar()).remove();
229 |
230 | if(fn) {
231 | if(is_select)
232 | args.push(obj);
233 |
234 | try {
235 | fn.apply(this, args);
236 | } catch(e) {
237 |
238 | }
239 | } else if(this.iframe && control.command) {
240 | if(is_select) {
241 | args = obj.options[obj.selectedIndex].value;
242 |
243 | if(args.length <= 0)
244 | return;
245 | }
246 |
247 | this.editor_cmd(control.command, args);
248 | }
249 | }
250 |
251 | lwRTE.prototype.create_toolbar = function(controls) {
252 | var self = this;
253 | var tb = $("
").addClass('rte-toolbar').width('100%').append($("
")).append($("
").addClass('clear'));
254 | var obj, li;
255 |
256 | for (var key in controls){
257 | if(controls[key].separator) {
258 | li = $("
").addClass('separator');
259 | } else {
260 | if(controls[key].init) {
261 | try {
262 | controls[key].init.apply(controls[key], [this]);
263 | } catch(e) {
264 | }
265 | }
266 |
267 | if(controls[key].select) {
268 | obj = $(controls[key].select)
269 | .change( function(e) {
270 | self.event = e;
271 | self.toolbar_click(this, controls[this.className]);
272 | return false;
273 | });
274 | } else {
275 | obj = $("
")
276 | .attr('title', (controls[key].hint) ? controls[key].hint : key)
277 | .attr('rel', key)
278 | .click( function(e) {
279 | self.event = e;
280 | self.toolbar_click(this, controls[this.rel]);
281 | return false;
282 | })
283 | }
284 |
285 | li = $("
").append(obj.addClass(key));
286 | }
287 |
288 | $("ul",tb).append(li);
289 | }
290 |
291 | $('.enable', tb).click(function() {
292 | self.enable_design_mode();
293 | return false;
294 | });
295 |
296 | $('.disable', tb).click(function() {
297 | self.disable_design_mode();
298 | return false;
299 | });
300 |
301 | return tb.get(0);
302 | }
303 |
304 | lwRTE.prototype.create_panel = function(title, content) {
305 | var self = this;
306 |
307 | if (this.panel == null) {
308 | this.panel = new Boxy(content, { title: title });
309 | } else {
310 | this.panel.setTitle(title);
311 | this.panel.setContent(content);
312 | }
313 |
314 | this.panel.show();
315 |
316 | return this.panel;
317 | // var tb = self.get_toolbar();
318 | //
319 | // if(!tb)
320 | // return false;
321 | //
322 | // $('.rte-panel', tb).remove();
323 | // var drag, event;
324 | // var left = self.event.pageX;
325 | // var top = self.event.pageY;
326 | //
327 | // var panel = $('
').hide().addClass('rte-panel').css({left: left, top: top});
328 | // $('
')
329 | // .addClass('rte-panel-title')
330 | // .html(title)
331 | // .append($("
X ")
332 | // .click( function() { panel.remove(); return false; }))
333 | // .mousedown( function() { drag = true; return false; })
334 | // .mouseup( function() { drag = false; return false; })
335 | // .mousemove(
336 | // function(e) {
337 | // if(drag && event) {
338 | // left -= event.pageX - e.pageX;
339 | // top -= event.pageY - e.pageY;
340 | // panel.css( {left: left, top: top} );
341 | // }
342 | //
343 | // event = e;
344 | // return false;
345 | // }
346 | // )
347 | // .appendTo(panel);
348 | //
349 | // if(width)
350 | // panel.width(width);
351 | //
352 | // tb.append(panel);
353 | // return panel;
354 | }
355 |
356 | lwRTE.prototype.get_content = function() {
357 | return (this.iframe) ? $('body', this.iframe_doc).html() : $(this.textarea).val();
358 | }
359 |
360 | lwRTE.prototype.set_content = function(content) {
361 | (this.iframe) ? $('body', this.iframe_doc).html(content) : $(this.textarea).val(content);
362 | }
363 |
364 | lwRTE.prototype.set_selected_controls = function(node, controls) {
365 | var toolbar = this.get_toolbar();
366 |
367 | if(!toolbar)
368 | return false;
369 |
370 | var key, i_node, obj, control, tag, i, value;
371 |
372 | try {
373 | for (key in controls) {
374 | control = controls[key];
375 | obj = $('.' + key, toolbar);
376 |
377 | obj.removeClass('active');
378 |
379 | if(!control.tags)
380 | continue;
381 |
382 | i_node = node;
383 | do {
384 | if(i_node.nodeType != 1)
385 | continue;
386 |
387 | tag = i_node.nodeName.toLowerCase();
388 | if($.inArray(tag, control.tags) < 0 )
389 | continue;
390 |
391 | if(control.select) {
392 | obj = obj.get(0);
393 | if(obj.tagName.toUpperCase() == 'SELECT') {
394 | obj.selectedIndex = 0;
395 |
396 | for(i = 0; i < obj.options.length; i++) {
397 | value = obj.options[i].value;
398 | if(value && ((control.tag_cmp && control.tag_cmp(i_node, value)) || tag == value)) {
399 | obj.selectedIndex = i;
400 | break;
401 | }
402 | }
403 | }
404 | } else
405 | obj.addClass('active');
406 | } while(i_node = i_node.parentNode)
407 | }
408 | } catch(e) {
409 | }
410 |
411 | return true;
412 | }
413 |
414 | lwRTE.prototype.get_selected_element = function () {
415 | var node, selection, range;
416 | var iframe_win = this.iframe.contentWindow;
417 |
418 | if (iframe_win.getSelection) {
419 | try {
420 | selection = iframe_win.getSelection();
421 | range = selection.getRangeAt(0);
422 | node = range.commonAncestorContainer;
423 | } catch(e){
424 | return false;
425 | }
426 | } else {
427 | try {
428 | selection = iframe_win.document.selection;
429 | range = selection.createRange();
430 | node = range.parentElement();
431 | } catch (e) {
432 | return false;
433 | }
434 | }
435 |
436 | return node;
437 | }
438 |
439 | lwRTE.prototype.get_selection_range = function() {
440 | var rng = null;
441 | var iframe_window = this.iframe.contentWindow;
442 | this.iframe.focus();
443 |
444 | if(iframe_window.getSelection) {
445 | rng = iframe_window.getSelection().getRangeAt(0);
446 | if($.browser.opera) { //v9.63 tested only
447 | var s = rng.startContainer;
448 | if(s.nodeType === Node.TEXT_NODE)
449 | rng.setStartBefore(s.parentNode);
450 | }
451 | } else {
452 | this.range.select(); //Restore selection, if IE lost focus.
453 | rng = this.iframe_doc.selection.createRange();
454 | }
455 |
456 | return rng;
457 | }
458 |
459 | lwRTE.prototype.get_selected_text = function() {
460 | var iframe_win = this.iframe.contentWindow;
461 |
462 | if(iframe_win.getSelection)
463 | return iframe_win.getSelection().toString();
464 |
465 | this.range.select(); //Restore selection, if IE lost focus.
466 | return iframe_win.document.selection.createRange().text;
467 | };
468 |
469 | lwRTE.prototype.get_selected_html = function() {
470 | var html = null;
471 | var iframe_window = this.iframe.contentWindow;
472 | var rng = this.get_selection_range();
473 |
474 | if(rng) {
475 | if(iframe_window.getSelection) {
476 | var e = document.createElement('div');
477 | e.appendChild(rng.cloneContents());
478 | html = e.innerHTML;
479 | } else {
480 | html = rng.htmlText;
481 | }
482 | }
483 |
484 | return html;
485 | };
486 |
487 | lwRTE.prototype.selection_replace_with = function(html) {
488 | var rng = this.get_selection_range();
489 | var iframe_window = this.iframe.contentWindow;
490 |
491 | if(!rng)
492 | return;
493 |
494 | this.editor_cmd('removeFormat'); // we must remove formating or we will get empty format tags!
495 |
496 | if(iframe_window.getSelection) {
497 | rng.deleteContents();
498 | rng.insertNode(rng.createContextualFragment(html));
499 | this.editor_cmd('delete');
500 | } else {
501 | this.editor_cmd('delete');
502 | rng.pasteHTML(html);
503 | }
504 | }
--------------------------------------------------------------------------------
/generators/plongo_admin/templates/public/javascripts/admin/plugins/rte.tb.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Lightweight RTE - jQuery Plugin, v1.2
3 | * Basic Toolbars
4 | * Copyright (c) 2009 Andrey Gayvoronsky - http://www.gayvoronsky.com
5 | */
6 | var rte_tag = '-rte-tmp-tag-';
7 |
8 | var rte_toolbar = {
9 | s1 : {separator: true},
10 | bold : {command: 'bold', tags:['b', 'strong']},
11 | italic : {command: 'italic', tags:['i', 'em']},
12 | strikeThrough : {command: 'strikethrough', tags: ['s', 'strike'] },
13 | underline : {command: 'underline', tags: ['u']},
14 | s2 : {separator: true },
15 | justifyLeft : {command: 'justifyleft'},
16 | justifyCenter : {command: 'justifycenter'},
17 | justifyRight : {command: 'justifyright'},
18 | justifyFull : {command: 'justifyfull'},
19 | s3 : {separator : true},
20 | indent : {command: 'indent'},
21 | outdent : {command: 'outdent'},
22 | s4 : {separator : true},
23 | subscript : {command: 'subscript', tags: ['sub']},
24 | superscript : {command: 'superscript', tags: ['sup']},
25 | s5 : {separator : true },
26 | orderedList : {command: 'insertorderedlist', tags: ['ol'] },
27 | unorderedList : {command: 'insertunorderedlist', tags: ['ul'] },
28 | s6 : {separator : true },
29 | block : {command: 'formatblock', select: '\
30 |
\
31 | - format - \
32 | Paragraph \
33 | Header 1 \
34 | Header 2\
35 | Header 3 \
36 | Header 4\
37 | Header 5 \
38 | Header 6\
39 | \
40 | ', tag_cmp: lwrte_block_compare, tags: ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6']},
41 | font : {command: 'fontname', select: '\
42 |
\
43 | - font - \
44 | Arial \
45 | Comic Sans \
46 | Courier New\
47 | Georgia \
48 | Helvetica\
49 | Impact \
50 | Times\
51 | Trebuchet\
52 | Verdana\
53 | \
54 | ', tags: ['font']},
55 | size : {command: 'fontsize', select: '\
56 |
\
57 | - \
58 | 1 (8pt) \
59 | 2 (10pt) \
60 | 3 (12pt)\
61 | 4 (14pt) \
62 | 5 (16pt)\
63 | 6 (18pt) \
64 | 7 (20pt)\
65 | \
66 | ', tags: ['font']},
67 | style : {exec: lwrte_style, init: lwrte_style_init},
68 | color : {exec: lwrte_color},
69 | image : {exec: lwrte_image, tags: ['img'] },
70 | link : {exec: lwrte_link, tags: ['a'] },
71 | unlink : {command: 'unlink'},
72 | s8 : {separator : true },
73 | removeFormat : {exec: lwrte_unformat},
74 | word : {exec: lwrte_cleanup_word},
75 | clear : {exec: lwrte_clear}
76 | };
77 |
78 | var html_toolbar = {
79 | s1 : {separator: true},
80 | word : {exec: lwrte_cleanup_word},
81 | clear : {exec: lwrte_clear}
82 | };
83 |
84 | /*** tag compare callbacks ***/
85 | function lwrte_block_compare(node, tag) {
86 | tag = tag.replace(/<([^>]*)>/, '$1');
87 | return (tag.toLowerCase() == node.nodeName.toLowerCase());
88 | }
89 |
90 | /*** init callbacks ***/
91 | function lwrte_style_init(rte) {
92 | var self = this;
93 | self.select = '
- no css - ';
94 |
95 | // load CSS info. javascript only issue is not working correctly, that's why ajax-php :(
96 | if(rte.css.length) {
97 | $.ajax({
98 | url: "styles.php",
99 | type: "POST",
100 | data: { css: rte.css[rte.css.length - 1] },
101 | async: false,
102 | success: function(data) {
103 | var list = data.split(',');
104 | var select = "";
105 |
106 | for(var name in list)
107 | select += '
' + list[name] + ' ';
108 |
109 | self.select = '
- css - ' + select + '';
110 | }});
111 | }
112 | }
113 |
114 | /*** exec callbacks ***/
115 | function lwrte_style(args) {
116 | if(args) {
117 | try {
118 | var css = args.options[args.selectedIndex].value
119 | var self = this;
120 | var html = self.get_selected_text();
121 | html = '
' + html + ' ';
122 | self.selection_replace_with(html);
123 | args.selectedIndex = 0;
124 | } catch(e) {
125 | }
126 | }
127 | }
128 |
129 | function lwrte_color(){
130 | var self = this;
131 | var mouse_down = false;
132 | var mouse_over = false;
133 |
134 | var panel = self.create_panel('Set color for text', '\
135 |
\
136 |
\
137 |
\
138 |
\
139 |
\
140 |
\
141 |
\
142 |
\
143 |
Ok Cancel
');
144 | var panel_dom = panel.boxy;
145 |
146 | var preview = $('#preview', panel_dom);
147 | var color = $("#color", panel_dom);
148 | var palette = $("#palette", panel_dom);
149 | var colors = [
150 | '#660000', '#990000', '#cc0000', '#ff0000', '#333333',
151 | '#006600', '#009900', '#00cc00', '#00ff00', '#666666',
152 | '#000066', '#000099', '#0000cc', '#0000ff', '#999999',
153 | '#909000', '#900090', '#009090', '#ffffff', '#cccccc',
154 | '#ffff00', '#ff00ff', '#00ffff', '#000000', '#eeeeee'
155 | ];
156 |
157 | for(var i = 0; i < colors.length; i++)
158 | $("
").addClass("item").css('background', colors[i]).appendTo(palette);
159 |
160 | var height = $('#rgb').height();
161 | var part_width = $('#rgb').width() / 6;
162 |
163 | $('#rgb,#gray,#palette', panel_dom)
164 | .mousedown( function(e) {mouse_down = true; return false; } )
165 | .mouseup( function(e) {mouse_down = false; return false; } )
166 | .mouseout( function(e) {mouse_over = false; return false; } )
167 | .mouseover( function(e) {mouse_over = true; return false; } );
168 |
169 | $('#rgb').mousemove( function(e) { if(mouse_down && mouse_over) compute_color(this, true, false, false, e); return false;} );
170 | $('#gray').mousemove( function(e) { if(mouse_down && mouse_over) compute_color(this, false, true, false, e); return false;} );
171 | $('#palette').mousemove( function(e) { if(mouse_down && mouse_over) compute_color(this, false, false, true, e); return false;} );
172 | $('#rgb').click( function(e) { compute_color(this, true, false, false, e); return false;} );
173 | $('#gray').click( function(e) { compute_color(this, false, true, false, e); return false;} );
174 | $('#palette').click( function(e) { compute_color(this, false, false, true, e); return false;} );
175 |
176 | $('#cancel', panel_dom).click( function() { panel_dom.hide(); return false; } );
177 | $('#ok', panel_dom).click(
178 | function() {
179 | var value = color.html();
180 |
181 | if(value.length > 0 && value.charAt(0) =='#') {
182 | if(self.iframe_doc.selection) //IE fix for lost focus
183 | self.range.select();
184 |
185 | self.editor_cmd('foreColor', value);
186 | }
187 |
188 | panel.hide();
189 | return false;
190 | }
191 | );
192 |
193 | function to_hex(n) {
194 | var s = "0123456789abcdef";
195 | return s.charAt(Math.floor(n / 16)) + s.charAt(n % 16);
196 | }
197 |
198 | function get_abs_pos(element) {
199 | var r = { x: element.offsetLeft, y: element.offsetTop };
200 |
201 | if (element.offsetParent) {
202 | var tmp = get_abs_pos(element.offsetParent);
203 | r.x += tmp.x;
204 | r.y += tmp.y;
205 | }
206 |
207 | return r;
208 | };
209 |
210 | function get_xy(obj, event) {
211 | var x, y;
212 | event = event || window.event;
213 | var el = event.target || event.srcElement;
214 |
215 | // use absolute coordinates
216 | var pos = get_abs_pos(obj);
217 |
218 | // subtract distance to middle
219 | x = event.pageX - pos.x;
220 | y = event.pageY - pos.y;
221 |
222 | return { x: x, y: y };
223 | }
224 |
225 | function compute_color(obj, is_rgb, is_gray, is_palette, e) {
226 | var r, g, b, c;
227 |
228 | var mouse = get_xy(obj, e);
229 | var x = mouse.x;
230 | var y = mouse.y;
231 |
232 | if(is_rgb) {
233 | r = (x >= 0)*(x < part_width)*255 + (x >= part_width)*(x < 2*part_width)*(2*255 - x * 255 / part_width) + (x >= 4*part_width)*(x < 5*part_width)*(-4*255 + x * 255 / part_width) + (x >= 5*part_width)*(x < 6*part_width)*255;
234 | g = (x >= 0)*(x < part_width)*(x * 255 / part_width) + (x >= part_width)*(x < 3*part_width)*255 + (x >= 3*part_width)*(x < 4*part_width)*(4*255 - x * 255 / part_width);
235 | b = (x >= 2*part_width)*(x < 3*part_width)*(-2*255 + x * 255 / part_width) + (x >= 3*part_width)*(x < 5*part_width)*255 + (x >= 5*part_width)*(x < 6*part_width)*(6*255 - x * 255 / part_width);
236 |
237 | var k = (height - y) / height;
238 |
239 | r = 128 + (r - 128) * k;
240 | g = 128 + (g - 128) * k;
241 | b = 128 + (b - 128) * k;
242 | } else if (is_gray) {
243 | r = g = b = (height - y) * 1.7;
244 | } else if(is_palette) {
245 | x = Math.floor(x / 10);
246 | y = Math.floor(y / 10);
247 | c = colors[x + y * 5];
248 | }
249 |
250 | if(!is_palette)
251 | c = '#' + to_hex(r) + to_hex(g) + to_hex(b);
252 |
253 | preview.css('background', c);
254 | color.html(c);
255 | }
256 | }
257 |
258 | function lwrte_image() {
259 | var self = this;
260 |
261 | var panel = self.create_panel("Insert image", '
');
262 |
263 | // assets
264 | jQuery.getJSON('/admin/image_assets.json', {}, function(data) {
265 | var assets = data.assets;
266 | var html = '
Click on an asset to insert it
';
272 |
273 | panel.setContent(html);
274 |
275 | $('#pick-assets li a', panel.boxy).click(function(e) {
276 | self.editor_cmd('insertImage', $(this).attr('href'));
277 | e.preventDefault();
278 | e.stopPropagation();
279 | panel.hide();
280 | return false;
281 | });
282 | });
283 |
284 | // var panel = self.create_panel('Insert image', 385);
285 | // panel.append('\
286 | //
URL Upload View
\
287 | //
\
288 | //
Ok Cancel
'
289 | // ).show();
290 | //
291 | // var url = $('#url', panel);
292 | // var upload = $('#file', panel).upload( {
293 | // autoSubmit: false,
294 | // action: 'uploader.php',
295 | // onSelect: function() {
296 | // var file = this.filename();
297 | // var ext = (/[.]/.exec(file)) ? /[^.]+$/.exec(file.toLowerCase()) : '';
298 | // if(!(ext && /^(jpg|png|jpeg|gif)$/.test(ext))){
299 | // alert('Invalid file extension');
300 | // return;
301 | // }
302 | //
303 | // this.submit();
304 | // },
305 | // onComplete: function(response) {
306 | // if(response.length <= 0)
307 | // return;
308 | //
309 | // response = eval("(" + response + ")");
310 | // if(response.error && response.error.length > 0)
311 | // alert(response.error);
312 | // else
313 | // url.val((response.file && response.file.length > 0) ? response.file : '');
314 | // }
315 | // });
316 | //
317 | // $('#view', panel).click( function() {
318 | // (url.val().length >0 ) ? window.open(url.val()) : alert("Enter URL of image to view");
319 | // return false;
320 | // }
321 | // );
322 | //
323 | // $('#cancel', panel).click( function() { panel.remove(); return false;} );
324 | // $('#ok', panel).click(
325 | // function() {
326 | // var file = url.val();
327 | // self.editor_cmd('insertImage', file);
328 | // panel.remove();
329 | // return false;
330 | // }
331 | // )
332 | }
333 |
334 | function lwrte_unformat() {
335 | this.editor_cmd('removeFormat');
336 | this.editor_cmd('unlink');
337 | }
338 |
339 | function lwrte_clear() {
340 | if(confirm('Clear Document?'))
341 | this.set_content('');
342 | }
343 |
344 | function lwrte_cleanup_word() {
345 | this.set_content(cleanup_word(this.get_content(), true, true, true));
346 |
347 | function cleanup_word(s, bIgnoreFont, bRemoveStyles, bCleanWordKeepsStructure) {
348 | s = s.replace(/
\s*<\/o:p>/g, '') ;
349 | s = s.replace(/[\s\S]*?<\/o:p>/g, ' ') ;
350 |
351 | // Remove mso-xxx styles.
352 | s = s.replace( /\s*mso-[^:]+:[^;"]+;?/gi, '' ) ;
353 |
354 | // Remove margin styles.
355 | s = s.replace( /\s*MARGIN: 0cm 0cm 0pt\s*;/gi, '' ) ;
356 | s = s.replace( /\s*MARGIN: 0cm 0cm 0pt\s*"/gi, "\"" ) ;
357 |
358 | s = s.replace( /\s*TEXT-INDENT: 0cm\s*;/gi, '' ) ;
359 | s = s.replace( /\s*TEXT-INDENT: 0cm\s*"/gi, "\"" ) ;
360 |
361 | s = s.replace( /\s*TEXT-ALIGN: [^\s;]+;?"/gi, "\"" ) ;
362 |
363 | s = s.replace( /\s*PAGE-BREAK-BEFORE: [^\s;]+;?"/gi, "\"" ) ;
364 |
365 | s = s.replace( /\s*FONT-VARIANT: [^\s;]+;?"/gi, "\"" ) ;
366 |
367 | s = s.replace( /\s*tab-stops:[^;"]*;?/gi, '' ) ;
368 | s = s.replace( /\s*tab-stops:[^"]*/gi, '' ) ;
369 |
370 | // Remove FONT face attributes.
371 | if (bIgnoreFont) {
372 | s = s.replace( /\s*face="[^"]*"/gi, '' ) ;
373 | s = s.replace( /\s*face=[^ >]*/gi, '' ) ;
374 |
375 | s = s.replace( /\s*FONT-FAMILY:[^;"]*;?/gi, '' ) ;
376 | }
377 |
378 | // Remove Class attributes
379 | s = s.replace(/<(\w[^>]*) class=([^ |>]*)([^>]*)/gi, "<$1$3") ;
380 |
381 | // Remove styles.
382 | if (bRemoveStyles)
383 | s = s.replace( /<(\w[^>]*) style="([^\"]*)"([^>]*)/gi, "<$1$3" ) ;
384 |
385 | // Remove style, meta and link tags
386 | s = s.replace( /