├── .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 | 9 | 10 | 11 | 12 | 13 | <%% @pages.each do |page| %> 14 | 15 | 16 | 17 | 18 | 23 | 24 | <%% end %> 25 |
NameUriView 
<%%= page.name %><%%= page.uri %><%%= page.path %> 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 |
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 |
  1. 15 | <%%= text_field_tag 'page[name]', @page.name %> 16 |
  2. 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 == 'nice' 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 == 'Banner' 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 == 'Banner' 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 == '' 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 == '' 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 == '' 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 | \ 40 | ', tag_cmp: lwrte_block_compare, tags: ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6']}, 41 | font : {command: 'fontname', select: '\ 42 | \ 54 | ', tags: ['font']}, 55 | size : {command: 'fontsize', select: '\ 56 | \ 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 = ''; 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 += ''; 108 | 109 | self.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 |

    '); 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 | //

    \ 287 | //
    \ 288 | //

    ' 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( /]*>[\s\S]*?<\/STYLE[^>]*>/gi, '' ) ; 387 | s = s.replace( /<(?:META|LINK)[^>]*>\s*/gi, '' ) ; 388 | 389 | // Remove empty styles. 390 | s = s.replace( /\s*style="\s*"/gi, '' ) ; 391 | 392 | s = s.replace( /]*>\s* \s*<\/SPAN>/gi, ' ' ) ; 393 | 394 | s = s.replace( /]*><\/SPAN>/gi, '' ) ; 395 | 396 | // Remove Lang attributes 397 | s = s.replace(/<(\w[^>]*) lang=([^ |>]*)([^>]*)/gi, "<$1$3") ; 398 | 399 | s = s.replace( /([\s\S]*?)<\/SPAN>/gi, '$1' ) ; 400 | 401 | s = s.replace( /([\s\S]*?)<\/FONT>/gi, '$1' ) ; 402 | 403 | // Remove XML elements and declarations 404 | s = s.replace(/<\\?\?xml[^>]*>/gi, '' ) ; 405 | 406 | // Remove w: tags with contents. 407 | s = s.replace( /]*>[\s\S]*?<\/w:[^>]*>/gi, '' ) ; 408 | 409 | // Remove Tags with XML namespace declarations: <\/o:p> 410 | s = s.replace(/<\/?\w+:[^>]*>/gi, '' ) ; 411 | 412 | // Remove comments [SF BUG-1481861]. 413 | s = s.replace(/<\!--[\s\S]*?-->/g, '' ) ; 414 | 415 | s = s.replace( /<(U|I|STRIKE)> <\/\1>/g, ' ' ) ; 416 | 417 | s = s.replace( /\s*<\/H\d>/gi, '' ) ; 418 | 419 | // Remove "display:none" tags. 420 | s = s.replace( /<(\w+)[^>]*\sstyle="[^"]*DISPLAY\s?:\s?none[\s\S]*?<\/\1>/ig, '' ) ; 421 | 422 | // Remove language tags 423 | s = s.replace( /<(\w[^>]*) language=([^ |>]*)([^>]*)/gi, "<$1$3") ; 424 | 425 | // Remove onmouseover and onmouseout events (from MS Word comments effect) 426 | s = s.replace( /<(\w[^>]*) onmouseover="([^\"]*)"([^>]*)/gi, "<$1$3") ; 427 | s = s.replace( /<(\w[^>]*) onmouseout="([^\"]*)"([^>]*)/gi, "<$1$3") ; 428 | 429 | if (bCleanWordKeepsStructure) { 430 | // The original tag send from Word is something like this: 431 | s = s.replace( /]*)>/gi, '' ) ; 432 | 433 | // Word likes to insert extra tags, when using MSIE. (Wierd). 434 | s = s.replace( /<(H\d)>]*>([\s\S]*?)<\/FONT><\/\1>/gi, '<$1>$2<\/$1>' ); 435 | s = s.replace( /<(H\d)>([\s\S]*?)<\/EM><\/\1>/gi, '<$1>$2<\/$1>' ); 436 | } else { 437 | s = s.replace( /]*)>/gi, '' ) ; 438 | s = s.replace( /]*)>/gi, '' ) ; 439 | s = s.replace( /]*)>/gi, '' ) ; 440 | s = s.replace( /]*)>/gi, '' ) ; 441 | s = s.replace( /]*)>/gi, '' ) ; 442 | s = s.replace( /]*)>/gi, '' ) ; 443 | 444 | s = s.replace( /<\/H\d>/gi, '<\/font><\/b><\/div>' ) ; 445 | 446 | // Transform

    to

    447 | var re = new RegExp( '(]*>[\\s\\S]*?)(<\/P>)', 'gi' ) ; // Different because of a IE 5.0 error 448 | s = s.replace( re, '' ) ; 449 | 450 | // Remove empty tags (three times, just to be sure). 451 | // This also removes any empty anchor 452 | s = s.replace( /<([^\s>]+)(\s[^>]*)?>\s*<\/\1>/g, '' ) ; 453 | s = s.replace( /<([^\s>]+)(\s[^>]*)?>\s*<\/\1>/g, '' ) ; 454 | s = s.replace( /<([^\s>]+)(\s[^>]*)?>\s*<\/\1>/g, '' ) ; 455 | } 456 | 457 | return s; 458 | } 459 | } 460 | function lwrte_link() { 461 | var self = this; 462 | 463 | var panel = self.create_panel("Create link / Attach file", '\ 464 |

    \ 465 |
    \ 466 |

    \ 467 |
    \ 468 |

    '); 469 | var panel_dom = panel.boxy; 470 | 471 | var url = $('#url', panel_dom); 472 | 473 | if (self.get_selected_html() && self.get_selected_html().length > 0) { 474 | var matches = self.get_selected_html().match(new RegExp('href=\"([^\"]*)\".*\>')); 475 | if (matches && matches.length > 0) url.val(matches[1]); 476 | 477 | matches = self.get_selected_html().match(new RegExp('title=\"([^\"]*)\".*\>')); 478 | if (matches && matches.length > 0) $('#title', panel_dom).val(matches[1]); 479 | } 480 | 481 | var upload = $('#file', panel_dom).upload({ 482 | name: 'asset[source]', 483 | autoSubmit: true, 484 | action: '/admin/assets', 485 | onComplete: function(response) { 486 | if(response.length <= 0) 487 | return; 488 | 489 | response = eval("(" + response + ")"); 490 | 491 | if(response.error && response.error.length > 0) 492 | alert(response.error); 493 | else 494 | url.val((response.url && response.url.length > 0) ? response.url : ''); 495 | }, 496 | params: JsonData 497 | }); 498 | 499 | $('#view', panel_dom).click( function() { 500 | (url.val().length >0 ) ? window.open(url.val()) : alert("Enter URL to view"); 501 | return false; 502 | }); 503 | 504 | $('#ok', panel_dom).click(function() { 505 | var url = $('#url', panel_dom).val(); 506 | var target = $('#target', panel_dom).val(); 507 | var title = $('#title', panel_dom).val(); 508 | 509 | if(self.get_selected_text().length <= 0) { 510 | alert('Select the text you wish to link!'); 511 | return false; 512 | } 513 | 514 | panel.hide(); 515 | 516 | if(url.length <= 0) 517 | return false; 518 | 519 | self.editor_cmd('unlink'); 520 | 521 | // we wanna well-formed linkage (

    ,

    and other block types can't be inside of link due to WC3) 522 | self.editor_cmd('createLink', rte_tag); 523 | var iframe_body = self.iframe.contentWindow.document.body; 524 | 525 | if(target.length > 0) 526 | $('a[href*="' + rte_tag + '"]', iframe_body).attr('target', target); 527 | 528 | if(title.length > 0) 529 | $('a[href*="' + rte_tag + '"]', iframe_body).attr('title', title); 530 | 531 | $('a[href*="' + rte_tag + '"]', iframe_body).attr('href', url); 532 | 533 | return false; 534 | }); 535 | } 536 | --------------------------------------------------------------------------------