fix CSRF issues with tinymce and some ajax actions + begin to work on the roles feature powered by cancan (in progress, not stable)
This commit is contained in:
parent
33a29210ba
commit
09171555a7
1
Gemfile
1
Gemfile
@ -26,6 +26,7 @@ gem 'dragonfly', '~> 0.9.1'
|
|||||||
gem 'rack-cache', :require => 'rack/cache'
|
gem 'rack-cache', :require => 'rack/cache'
|
||||||
|
|
||||||
gem 'custom_fields', '1.0.0.beta.19'
|
gem 'custom_fields', '1.0.0.beta.19'
|
||||||
|
gem 'cancan'
|
||||||
gem 'fog', '0.8.2'
|
gem 'fog', '0.8.2'
|
||||||
gem 'mimetype-fu'
|
gem 'mimetype-fu'
|
||||||
gem 'actionmailer-with-request', :require => 'actionmailer_with_request'
|
gem 'actionmailer-with-request', :require => 'actionmailer_with_request'
|
||||||
|
@ -62,6 +62,7 @@ GEM
|
|||||||
highline (>= 1.6.1)
|
highline (>= 1.6.1)
|
||||||
json (>= 1.4.6)
|
json (>= 1.4.6)
|
||||||
rest-client (>= 1.6.1)
|
rest-client (>= 1.6.1)
|
||||||
|
cancan (1.6.0)
|
||||||
capybara (0.4.1.2)
|
capybara (0.4.1.2)
|
||||||
celerity (>= 0.7.9)
|
celerity (>= 0.7.9)
|
||||||
culerity (>= 0.2.4)
|
culerity (>= 0.2.4)
|
||||||
@ -280,6 +281,7 @@ DEPENDENCIES
|
|||||||
bson_ext (~> 1.3.0)
|
bson_ext (~> 1.3.0)
|
||||||
bushido
|
bushido
|
||||||
bushido_stub!
|
bushido_stub!
|
||||||
|
cancan
|
||||||
capybara
|
capybara
|
||||||
cucumber (= 0.8.5)
|
cucumber (= 0.8.5)
|
||||||
cucumber-rails
|
cucumber-rails
|
||||||
|
@ -15,7 +15,7 @@ module Admin
|
|||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
params[:asset] = { :name => params[:name], :source => params[:file] } if params[:file]
|
@asset = current_site.assets.build(:name => params[:name], :source => params[:file])
|
||||||
|
|
||||||
create! do |success, failure|
|
create! do |success, failure|
|
||||||
success.json do
|
success.json do
|
||||||
|
@ -9,11 +9,13 @@ module Admin
|
|||||||
|
|
||||||
before_filter :require_site
|
before_filter :require_site
|
||||||
|
|
||||||
|
load_and_authorize_resource
|
||||||
|
|
||||||
before_filter :validate_site_membership
|
before_filter :validate_site_membership
|
||||||
|
|
||||||
before_filter :set_locale
|
before_filter :set_locale
|
||||||
|
|
||||||
helper_method :sections, :current_site_url, :site_url, :page_url
|
helper_method :sections, :current_site_url, :site_url, :page_url, :current_ability
|
||||||
|
|
||||||
# https://rails.lighthouseapp.com/projects/8994/tickets/1905-apphelpers-within-plugin-not-being-mixed-in
|
# https://rails.lighthouseapp.com/projects/8994/tickets/1905-apphelpers-within-plugin-not-being-mixed-in
|
||||||
Dir[File.dirname(__FILE__) + "/../../helpers/**/*_helper.rb"].each do |file|
|
Dir[File.dirname(__FILE__) + "/../../helpers/**/*_helper.rb"].each do |file|
|
||||||
@ -26,8 +28,26 @@ module Admin
|
|||||||
|
|
||||||
respond_to :html
|
respond_to :html
|
||||||
|
|
||||||
|
rescue_from CanCan::AccessDenied do |exception|
|
||||||
|
puts "exception = #{exception.inspect}"
|
||||||
|
|
||||||
|
logger.debug "[CanCan::AccessDenied] #{exception.inspect}"
|
||||||
|
|
||||||
|
if request.xhr?
|
||||||
|
render :json => { :error => exception.message }
|
||||||
|
else
|
||||||
|
flash[:alert] = exception.message
|
||||||
|
|
||||||
|
redirect_to admin_pages_url
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
|
def current_ability
|
||||||
|
@current_ability ||= Ability.new(current_admin, current_site)
|
||||||
|
end
|
||||||
|
|
||||||
def require_admin
|
def require_admin
|
||||||
authenticate_admin!
|
authenticate_admin!
|
||||||
end
|
end
|
||||||
|
@ -7,8 +7,11 @@ module Admin
|
|||||||
|
|
||||||
respond_to :json, :only => :update
|
respond_to :json, :only => :update
|
||||||
|
|
||||||
|
# before_filter :authorize
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@contents = @content_type.list_or_group_contents
|
@contents = @content_type.list_or_group_contents
|
||||||
|
authorize! :index, ContentInstance
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
|
56
app/models/ability.rb
Normal file
56
app/models/ability.rb
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
class Ability
|
||||||
|
include CanCan::Ability
|
||||||
|
|
||||||
|
ROLES = %w(admin author designer)
|
||||||
|
|
||||||
|
def initialize(account, site)
|
||||||
|
@account, @site = account, site
|
||||||
|
|
||||||
|
alias_action :index, :show, :edit, :update, :to => :touch
|
||||||
|
|
||||||
|
@membership = @site.memberships.where(:account_id => @account.id).first
|
||||||
|
|
||||||
|
return false if @membership.blank?
|
||||||
|
|
||||||
|
if @membership.admin?
|
||||||
|
setup_admin_permissions!
|
||||||
|
else
|
||||||
|
setup_default_permissions!
|
||||||
|
|
||||||
|
setup_designer_permissions! if @membership.designer?
|
||||||
|
|
||||||
|
setup_author_permissions! if @membership.author?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def setup_default_permissions!
|
||||||
|
cannot :manage, :all
|
||||||
|
end
|
||||||
|
|
||||||
|
def setup_author_permissions!
|
||||||
|
can :touch, [Page, ThemeAsset]
|
||||||
|
can :sort, Page
|
||||||
|
|
||||||
|
can :manage, [ContentInstance, Asset]
|
||||||
|
end
|
||||||
|
|
||||||
|
def setup_designer_permissions!
|
||||||
|
can :manage, Page
|
||||||
|
|
||||||
|
can :manage, ContentInstance
|
||||||
|
|
||||||
|
can :manage, ContentType
|
||||||
|
|
||||||
|
can :manage, ThemeAsset
|
||||||
|
|
||||||
|
can :import, Site
|
||||||
|
|
||||||
|
can :point, Site
|
||||||
|
|
||||||
|
can :manage, Membership
|
||||||
|
end
|
||||||
|
|
||||||
|
def setup_admin_permissions!
|
||||||
|
can :manage, :all
|
||||||
|
end
|
||||||
|
end
|
@ -25,6 +25,8 @@ class Asset
|
|||||||
|
|
||||||
## methods ##
|
## methods ##
|
||||||
|
|
||||||
|
alias :name :source_filename
|
||||||
|
|
||||||
def extname
|
def extname
|
||||||
return nil unless self.source?
|
return nil unless self.source?
|
||||||
File.extname(self.source_filename).gsub(/^\./, '')
|
File.extname(self.source_filename).gsub(/^\./, '')
|
||||||
|
@ -6,7 +6,7 @@ module Extensions
|
|||||||
def create_first_one(attributes)
|
def create_first_one(attributes)
|
||||||
site = self.new(attributes)
|
site = self.new(attributes)
|
||||||
|
|
||||||
site.memberships.build :account => Account.first, :admin => true
|
site.memberships.build :account => Account.first, :role => 'admin'
|
||||||
|
|
||||||
site.save
|
site.save
|
||||||
|
|
||||||
|
@ -3,7 +3,8 @@ class Membership
|
|||||||
include Locomotive::Mongoid::Document
|
include Locomotive::Mongoid::Document
|
||||||
|
|
||||||
## fields ##
|
## fields ##
|
||||||
field :admin, :type => Boolean, :default => false
|
# field :admin, :type => Boolean, :default => false
|
||||||
|
field :role, :default => 'author'
|
||||||
|
|
||||||
## associations ##
|
## associations ##
|
||||||
referenced_in :account, :validate => false
|
referenced_in :account, :validate => false
|
||||||
@ -12,8 +13,17 @@ class Membership
|
|||||||
## validations ##
|
## validations ##
|
||||||
validates_presence_of :account
|
validates_presence_of :account
|
||||||
|
|
||||||
|
## callbacks ##
|
||||||
|
before_save :define_role
|
||||||
|
|
||||||
## methods ##
|
## methods ##
|
||||||
|
|
||||||
|
Ability::ROLES.each do |_role|
|
||||||
|
define_method("#{_role}?") do
|
||||||
|
self.role == _role
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def email; @email; end
|
def email; @email; end
|
||||||
|
|
||||||
def email=(email)
|
def email=(email)
|
||||||
@ -36,4 +46,14 @@ class Membership
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def ability
|
||||||
|
@ability ||= Ability.new(self.account, self.site)
|
||||||
|
end
|
||||||
|
|
||||||
|
protected
|
||||||
|
|
||||||
|
def define_role
|
||||||
|
self.role = Ability::ROLES.include?(role.downcase) ? role.downcase : Ability::ROLES.first
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
@ -28,6 +28,7 @@ class Site
|
|||||||
|
|
||||||
## behaviours ##
|
## behaviours ##
|
||||||
enable_subdomain_n_domains_if_multi_sites
|
enable_subdomain_n_domains_if_multi_sites
|
||||||
|
accepts_nested_attributes_for :memberships
|
||||||
|
|
||||||
## methods ##
|
## methods ##
|
||||||
|
|
||||||
|
@ -41,12 +41,27 @@
|
|||||||
%button{ :class => 'button light add', :type => 'button' }
|
%button{ :class => 'button light add', :type => 'button' }
|
||||||
%span!= t('admin.buttons.new_item')
|
%span!= t('admin.buttons.new_item')
|
||||||
|
|
||||||
= f.foldable_inputs :name => :memberships, :class => 'memberships' do
|
- if current_ability.can?(:touch, Membership)
|
||||||
- @site.memberships.each_with_index do |membership, index|
|
|
||||||
- account = membership.account
|
= f.foldable_inputs :name => :memberships, :class => 'memberships off' do
|
||||||
%li{ :class => "item #{'last' if index == @site.memberships.size - 1}" }
|
= f.semantic_fields_for :memberships do |fm|
|
||||||
%strong= account.name
|
|
||||||
%em= account.email
|
- membership, account = fm.object, fm.object.account
|
||||||
- if account != current_admin
|
|
||||||
%span.actions
|
%li.item.membership{ :'data-role' => membership.role }
|
||||||
= link_to image_tag('admin/form/icons/trash.png'), admin_membership_url(membership), :class => 'remove first', :confirm => t('admin.messages.confirm'), :method => :delete
|
%strong= account.name
|
||||||
|
|
||||||
|
%em.email= account.email
|
||||||
|
|
||||||
|
- if current_admin != account && current_ability.can?(:touch, Membership)
|
||||||
|
.role
|
||||||
|
%em.editable= t("admin.memberships.roles.#{membership.role}")
|
||||||
|
|
||||||
|
= fm.select :role, Ability::ROLES.collect { |r| [t("admin.memberships.roles.#{r}"), r] }, :include_blank => false
|
||||||
|
|
||||||
|
%span.actions
|
||||||
|
= link_to image_tag('admin/form/icons/trash.png'), admin_membership_url(membership), :class => 'remove first', :confirm =>t('admin.messages.confirm'), :method => :delete
|
||||||
|
|
||||||
|
- else
|
||||||
|
.role
|
||||||
|
%em.locked= t("admin.memberships.roles.#{membership.role}")
|
||||||
|
@ -29,7 +29,7 @@
|
|||||||
|
|
||||||
—
|
—
|
||||||
|
|
||||||
%em {{kind_name}}
|
%em.editable {{kind_name}}
|
||||||
|
|
||||||
= select_tag '{{base_name}}[kind]', options_for_select(options_for_field_kind), :'data-field' => 'kind'
|
= select_tag '{{base_name}}[kind]', options_for_select(options_for_field_kind), :'data-field' => 'kind'
|
||||||
|
|
||||||
|
@ -2,14 +2,18 @@
|
|||||||
= include_javascripts :image_picker, :edit_page
|
= include_javascripts :image_picker, :edit_page
|
||||||
= include_stylesheets :editable_elements, :fancybox
|
= include_stylesheets :editable_elements, :fancybox
|
||||||
|
|
||||||
= f.foldable_inputs :name => :information do
|
- if can?(:manage, @page)
|
||||||
|
|
||||||
= f.input :title
|
= f.foldable_inputs :name => :information do
|
||||||
|
|
||||||
- if not @page.index? and not @page.not_found?
|
= f.input :title
|
||||||
= f.input :parent_id, :as => :select, :collection => parent_pages_options, :include_blank => false
|
|
||||||
|
|
||||||
= f.input :slug, :required => false, :hint => @page.slug.blank? ? ' ' : page_url(@page), :input_html => { :data_url => get_path_admin_pages_url, :disabled => @page.index? || @page.not_found? }, :wrapper_html => { :style => "#{'display: none' if @page.templatized?}; height: 50px" }
|
- if not @page.index? and not @page.not_found?
|
||||||
|
= f.input :parent_id, :as => :select, :collection => parent_pages_options, :include_blank => false
|
||||||
|
|
||||||
|
= f.input :slug, :required => false, :hint => @page.slug.blank? ? ' ' : page_url(@page), :input_html => { :data_url => get_path_admin_pages_url, :disabled => @page.index? || @page.not_found? }, :wrapper_html => { :style => "#{'display: none' if @page.templatized?}; height: 50px" }
|
||||||
|
|
||||||
|
= render 'editable_elements', :page => @page
|
||||||
|
|
||||||
= f.foldable_inputs :name => :seo do
|
= f.foldable_inputs :name => :seo do
|
||||||
|
|
||||||
@ -17,33 +21,34 @@
|
|||||||
= f.input :meta_keywords
|
= f.input :meta_keywords
|
||||||
= f.input :meta_description
|
= f.input :meta_description
|
||||||
|
|
||||||
= f.foldable_inputs :name => :advanced_options do
|
- if can?(:manage, @page)
|
||||||
|
|
||||||
= f.input :content_type_id, :as => :select, :collection => current_site.content_types.all.to_a, :include_blank => false, :wrapper_html => { :style => "#{'display: none' unless @page.templatized?}; height: 50px" }
|
= f.foldable_inputs :name => :advanced_options do
|
||||||
|
|
||||||
= f.custom_input :templatized, :css => 'toggle', :style => "#{'display: none' if @page.redirect?}" do
|
= f.input :content_type_id, :as => :select, :collection => current_site.content_types.all.to_a, :include_blank => false, :wrapper_html => { :style => "#{'display: none' unless @page.templatized?}; height: 50px" }
|
||||||
= f.check_box :templatized
|
|
||||||
|
|
||||||
= f.custom_input :published, :css => 'toggle' do
|
= f.custom_input :templatized, :css => 'toggle', :style => "#{'display: none' if @page.redirect?}" do
|
||||||
= f.check_box :published
|
= f.check_box :templatized
|
||||||
|
|
||||||
= f.custom_input :listed, :css => 'toggle' do
|
= f.custom_input :published, :css => 'toggle' do
|
||||||
= f.check_box :listed
|
= f.check_box :published
|
||||||
|
|
||||||
= f.custom_input :redirect, :css => 'toggle', :style => "#{'display: none' if @page.templatized?}" do
|
= f.custom_input :listed, :css => 'toggle' do
|
||||||
= f.check_box :redirect
|
= f.check_box :listed
|
||||||
|
|
||||||
= f.input :cache_strategy, :as => :select, :collection => options_for_page_cache_strategy, :include_blank => false, :wrapper_html => { :style => "#{'display: none' if @page.redirect?}" }
|
= f.custom_input :redirect, :css => 'toggle', :style => "#{'display: none' if @page.templatized?}" do
|
||||||
|
= f.check_box :redirect
|
||||||
|
|
||||||
= f.input :redirect_url, :required => true, :wrapper_html => { :style => "#{'display: none' unless @page.redirect?}" }
|
= f.input :cache_strategy, :as => :select, :collection => options_for_page_cache_strategy, :include_blank => false, :wrapper_html => { :style => "#{'display: none' if @page.redirect?}" }
|
||||||
|
|
||||||
= render 'editable_elements', :page => @page
|
= f.input :redirect_url, :required => true, :wrapper_html => { :style => "#{'display: none' unless @page.redirect?}" }
|
||||||
|
|
||||||
= f.foldable_inputs :name => :raw_template do
|
|
||||||
= f.custom_input :value, :css => 'code full', :with_label => false do
|
= f.foldable_inputs :name => :raw_template do
|
||||||
= f.label :raw_template
|
= f.custom_input :value, :css => 'code full', :with_label => false do
|
||||||
%code{ :class => 'html' }
|
= f.label :raw_template
|
||||||
= f.text_area :raw_template
|
%code{ :class => 'html' }
|
||||||
= f.errors_on :template
|
= f.text_area :raw_template
|
||||||
.more
|
= f.errors_on :template
|
||||||
= link_to t('admin.image_picker.link'), admin_theme_assets_path, :id => 'image-picker-link'
|
.more
|
||||||
|
= link_to t('admin.image_picker.link'), admin_theme_assets_path, :id => 'image-picker-link'
|
@ -1,5 +1,8 @@
|
|||||||
%li{ :id => "item-#{page.id}", :class => "#{'not-found' if page.not_found? } #{'templatized' if page.templatized?}"}
|
%li{ :id => "item-#{page.id}", :class => "#{'not-found' if page.not_found? } #{'templatized' if page.templatized?}"}
|
||||||
- with_children = !page.children.empty?
|
|
||||||
|
- children = can?(:manage, page) ? page.children : page.children.find_all { |p| !p.templatized? }
|
||||||
|
|
||||||
|
- with_children = !children.empty?
|
||||||
|
|
||||||
- if not page.index? and with_children
|
- if not page.index? and with_children
|
||||||
= image_tag 'admin/list/icons/node_closed.png', :class => 'toggler'
|
= image_tag 'admin/list/icons/node_closed.png', :class => 'toggler'
|
||||||
@ -10,9 +13,10 @@
|
|||||||
%span!= t('.updated_at')
|
%span!= t('.updated_at')
|
||||||
= l page.updated_at, :format => :short
|
= l page.updated_at, :format => :short
|
||||||
|
|
||||||
- if not page.index? and not page.not_found?
|
- if !page.index_or_not_found? && can?(:manage, page)
|
||||||
= link_to image_tag('admin/list/icons/trash.png'), admin_page_url(page), :class => 'remove', :confirm => t('admin.messages.confirm'), :method => :delete
|
= link_to image_tag('admin/list/icons/trash.png'), admin_page_url(page), :class => 'remove', :confirm => t('admin.messages.confirm'), :method => :delete
|
||||||
|
|
||||||
- if with_children
|
- if with_children
|
||||||
%ul{ :id => "folder-#{page._id}", :class => "folder depth-#{(page.depth || 0) + 1}", :data_url => sort_admin_page_url(page), :style => "display: #{cookies["folder-#{page._id}"] || 'block'}" }
|
%ul{ :id => "folder-#{page._id}", :class => "folder depth-#{(page.depth || 0) + 1}", :data_url => sort_admin_page_url(page), :style => "display: #{cookies["folder-#{page._id}"] || 'block'}" }
|
||||||
= render page.children
|
|
||||||
|
= render children
|
@ -5,12 +5,13 @@
|
|||||||
|
|
||||||
- content_for :submenu do
|
- content_for :submenu do
|
||||||
= render 'admin/shared/menu/contents'
|
= render 'admin/shared/menu/contents'
|
||||||
|
|
||||||
- content_for :actions do
|
- content_for :actions do
|
||||||
= render 'admin/shared/actions/contents'
|
= render 'admin/shared/actions/contents'
|
||||||
|
|
||||||
- content_for :buttons do
|
- if can? :create, Page
|
||||||
= admin_button_tag :new, new_admin_page_url, :class => 'new'
|
- content_for :buttons do
|
||||||
|
= admin_button_tag :new, new_admin_page_url, :class => 'new'
|
||||||
|
|
||||||
%p!= t('.help')
|
%p!= t('.help')
|
||||||
|
|
||||||
|
@ -1 +1,2 @@
|
|||||||
= link_to content_tag(:em) + content_tag(:span, t('admin.content_types.index.new')), new_admin_content_type_url
|
- if can? :manage, ContentType
|
||||||
|
= link_to content_tag(:em) + content_tag(:span, t('admin.content_types.index.new')), new_admin_content_type_url
|
@ -1,6 +1,7 @@
|
|||||||
= admin_submenu_item 'pages', admin_pages_url do
|
= admin_submenu_item 'pages', admin_pages_url do
|
||||||
.header
|
- if can? :manage, @page
|
||||||
%p= link_to t('admin.pages.index.new'), new_admin_page_url
|
.header
|
||||||
|
%p= link_to t('admin.pages.index.new'), new_admin_page_url
|
||||||
.inner
|
.inner
|
||||||
%h2!= t('admin.pages.index.lastest_items')
|
%h2!= t('admin.pages.index.lastest_items')
|
||||||
%ul
|
%ul
|
||||||
@ -12,7 +13,10 @@
|
|||||||
- each_content_type_menu_item do |content_type|
|
- each_content_type_menu_item do |content_type|
|
||||||
.header
|
.header
|
||||||
%p= link_to t('admin.contents.index.new'), new_admin_content_url(content_type.slug)
|
%p= link_to t('admin.contents.index.new'), new_admin_content_url(content_type.slug)
|
||||||
%p.edit= link_to t('admin.contents.index.edit'), edit_admin_content_type_url(content_type)
|
|
||||||
|
- if can? :manage, content_type
|
||||||
|
%p.edit= link_to t('admin.contents.index.edit'), edit_admin_content_type_url(content_type)
|
||||||
|
|
||||||
.inner
|
.inner
|
||||||
%h2!= t('admin.contents.index.lastest_items')
|
%h2!= t('admin.contents.index.lastest_items')
|
||||||
%ul
|
%ul
|
||||||
|
@ -138,10 +138,14 @@ en:
|
|||||||
edit:
|
edit:
|
||||||
import: import
|
import: import
|
||||||
new_membership: add account
|
new_membership: add account
|
||||||
help: "The site name can be updated by clicking it."
|
help: "The site name can be updated by clicking it. To apply your changes, click on the \"Update\" button."
|
||||||
ask_for_name: "Please type the new site name"
|
ask_for_name: "Please type the new site name"
|
||||||
|
|
||||||
memberships:
|
memberships:
|
||||||
|
roles:
|
||||||
|
admin: Administrator
|
||||||
|
designer: Designer
|
||||||
|
author: Author
|
||||||
new:
|
new:
|
||||||
title: New membership
|
title: New membership
|
||||||
help: "Please give the account email to add. If it does not exist, you will be redirected to the account creation form."
|
help: "Please give the account email to add. If it does not exist, you will be redirected to the account creation form."
|
||||||
@ -153,7 +157,7 @@ en:
|
|||||||
|
|
||||||
my_accounts:
|
my_accounts:
|
||||||
edit:
|
edit:
|
||||||
help: "Your name can be updated by clicking it."
|
help: "Your name can be updated by clicking it. To apply your changes, click on the \"Update\" button."
|
||||||
new_site: new site
|
new_site: new site
|
||||||
en: English
|
en: English
|
||||||
de: German
|
de: German
|
||||||
|
@ -138,7 +138,7 @@ fr:
|
|||||||
edit:
|
edit:
|
||||||
import: importer
|
import: importer
|
||||||
new_membership: ajouter compte
|
new_membership: ajouter compte
|
||||||
help: "Le nom du site est modifiable en cliquant dessus."
|
help: "Le nom du site est modifiable en cliquant dessus. Pour appliquer votre modification, cliquez après sur le bouton \"Modifier\""
|
||||||
ask_for_name: "Veuillez entrer le nouveau nom"
|
ask_for_name: "Veuillez entrer le nouveau nom"
|
||||||
|
|
||||||
memberships:
|
memberships:
|
||||||
@ -153,7 +153,7 @@ fr:
|
|||||||
|
|
||||||
my_accounts:
|
my_accounts:
|
||||||
edit:
|
edit:
|
||||||
help: "Votre nom est modifiable en cliquant dessus."
|
help: "Votre nom est modifiable en cliquant dessus. Pour appliquer votre modification, cliquez après sur le bouton \"Modifier\""
|
||||||
new_site: nouveau site
|
new_site: nouveau site
|
||||||
en: en Anglais
|
en: en Anglais
|
||||||
de: en Allemand
|
de: en Allemand
|
||||||
|
17
doc/TODO
17
doc/TODO
@ -31,9 +31,22 @@ x Has_one => group by in the select
|
|||||||
x better hints:
|
x better hints:
|
||||||
x notify the user that after changing the page title, they still have to click "update" for the change to be saved
|
x notify the user that after changing the page title, they still have to click "update" for the change to be saved
|
||||||
x created_by ASC => "Creation date ascending"
|
x created_by ASC => "Creation date ascending"
|
||||||
- cancan: authors / designers
|
- cancan: (authors / designers / admin)
|
||||||
|
x model
|
||||||
|
x ui
|
||||||
|
- controllers / views:
|
||||||
|
- page
|
||||||
|
- asset
|
||||||
|
- content type
|
||||||
|
- site
|
||||||
|
- account
|
||||||
|
- snippet
|
||||||
|
- theme asset
|
||||||
|
- better ui: increase text field length + refactor error message
|
||||||
- convert existing templates (the 2 of the themes section)
|
- convert existing templates (the 2 of the themes section)
|
||||||
- bug heroku: unable to upload a new file
|
- bugs
|
||||||
|
- heroku: unable to upload a new file
|
||||||
|
- import
|
||||||
|
|
||||||
BACKLOG:
|
BACKLOG:
|
||||||
|
|
||||||
|
@ -13,6 +13,22 @@ namespace :locomotive do
|
|||||||
|
|
||||||
namespace :upgrade do
|
namespace :upgrade do
|
||||||
|
|
||||||
|
desc 'Set roles to the existing users'
|
||||||
|
task :set_roles => :environment do
|
||||||
|
Site.all.each do |site|
|
||||||
|
site.memberships.each do |membership|
|
||||||
|
if membership.attributes['admin'] == true
|
||||||
|
puts "...[#{site.name}] #{membership.account.name} has now the admin role"
|
||||||
|
membership.role = 'admin'
|
||||||
|
else
|
||||||
|
puts "...[#{site.name}] #{membership.account.name} has now the author role"
|
||||||
|
membership.role = 'author'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
site.save
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
desc 'Remove asset collections and convert them into content types'
|
desc 'Remove asset collections and convert them into content types'
|
||||||
task :remove_asset_collections => :environment do
|
task :remove_asset_collections => :environment do
|
||||||
puts "Processing #{AssetCollection.count} asset collection(s)..."
|
puts "Processing #{AssetCollection.count} asset collection(s)..."
|
||||||
|
BIN
public/images/admin/icons/membership_edit.png
Normal file
BIN
public/images/admin/icons/membership_edit.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 302 B |
BIN
public/images/admin/icons/membership_lock.png
Normal file
BIN
public/images/admin/icons/membership_lock.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 219 B |
@ -24,6 +24,7 @@ $(document).ready(function() {
|
|||||||
'update': function(event, ui) {
|
'update': function(event, ui) {
|
||||||
var params = $(this).sortable('serialize', { 'key': 'children[]' });
|
var params = $(this).sortable('serialize', { 'key': 'children[]' });
|
||||||
params += '&_method=put';
|
params += '&_method=put';
|
||||||
|
params += '&' + $('meta[name=csrf-param]').attr('content') + '=' + $('meta[name=csrf-token]').attr('content');
|
||||||
|
|
||||||
$.post($(this).attr('data_url'), params, function(data) {
|
$.post($(this).attr('data_url'), params, function(data) {
|
||||||
var error = typeof(data.error) != 'undefined';
|
var error = typeof(data.error) != 'undefined';
|
||||||
|
@ -43,7 +43,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<a href="#" class="remove" data-confirm="{#locomedia_dlg.confirm}" data-method="delete" rel="nofollow">
|
<a href="#" class="remove" data-remote="true" data-confirm="{#locomedia_dlg.confirm}" data-method="delete" rel="nofollow">
|
||||||
<img src="/images/admin/list/icons/cross.png">
|
<img src="/images/admin/list/icons/cross.png">
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1 +1 @@
|
|||||||
(function(){tinymce.create('tinymce.plugins.LocoMediaPlugin',{init:function(ed,url){ed.addCommand('locoMedia',function(){ed.windowManager.open({file:url+'/dialog.htm',width:645,height:650,inline:1},{plugin_url:url})});ed.addButton('locomedia',{title:'locomedia.image_desc',cmd:'locoMedia'})},getInfo:function(){return{longname:'Locomotive Media File',author:'Didier Lafforgue',authorurl:'http://www.locomotivecms.com',infourl:'http://www.locomotivecms.com',version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.PluginManager.add('locomedia',tinymce.plugins.LocoMediaPlugin)})();
|
(function(){tinymce.create('tinymce.plugins.LocoMediaPlugin',{init:function(ed,url){ed.addCommand('locoMedia',function(){ed.windowManager.open({file:url+'/dialog.htm?7',width:645,height:650,inline:1},{plugin_url:url})});ed.addButton('locomedia',{title:'locomedia.image_desc',cmd:'locoMedia'})},getInfo:function(){return{longname:'Locomotive Media File',author:'Didier Lafforgue',authorurl:'http://www.locomotivecms.com',infourl:'http://www.locomotivecms.com',version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.PluginManager.add('locomedia',tinymce.plugins.LocoMediaPlugin)})();
|
@ -15,6 +15,13 @@ var MediafileDialog = {
|
|||||||
init : function(ed) {
|
init : function(ed) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
|
with(window.parent) {
|
||||||
|
var csrf_token = $('meta[name=csrf-token]').attr('content'),
|
||||||
|
csrf_param = $('meta[name=csrf-param]').attr('content');
|
||||||
|
}
|
||||||
|
|
||||||
|
$.fn.setCsrfSettings(csrf_token, csrf_param);
|
||||||
|
|
||||||
formElement = $(document.forms[0]);
|
formElement = $(document.forms[0]);
|
||||||
|
|
||||||
listElement = formElement.find('ul');
|
listElement = formElement.find('ul');
|
||||||
@ -153,13 +160,6 @@ var MediafileDialog = {
|
|||||||
|
|
||||||
asset.find('.actions a')
|
asset.find('.actions a')
|
||||||
.attr('href', data.destroy_url)
|
.attr('href', data.destroy_url)
|
||||||
.bind('click', function(e) {
|
|
||||||
if (confirm($(this).attr('data-confirm'))) {
|
|
||||||
self.showSpinner('destroying');
|
|
||||||
$(this).callRemote();
|
|
||||||
}
|
|
||||||
e.preventDefault(); e.stopPropagation();
|
|
||||||
})
|
|
||||||
.bind('ajax:success', function(event, data) {
|
.bind('ajax:success', function(event, data) {
|
||||||
self._destroyAsset(asset);
|
self._destroyAsset(asset);
|
||||||
});
|
});
|
||||||
|
@ -3,6 +3,12 @@ jQuery(function ($) {
|
|||||||
csrf_param = $('meta[name=csrf-param]').attr('content');
|
csrf_param = $('meta[name=csrf-param]').attr('content');
|
||||||
|
|
||||||
$.fn.extend({
|
$.fn.extend({
|
||||||
|
|
||||||
|
setCsrfSettings: function(token, param) {
|
||||||
|
csrf_token = token;
|
||||||
|
csrf_param = param;
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Triggers a custom event on an element and returns the event result
|
* Triggers a custom event on an element and returns the event result
|
||||||
* this is used to get around not being able to ensure callbacks are placed
|
* this is used to get around not being able to ensure callbacks are placed
|
||||||
@ -32,6 +38,10 @@ jQuery(function ($) {
|
|||||||
} else {
|
} else {
|
||||||
if (el.triggerAndReturn('ajax:before')) {
|
if (el.triggerAndReturn('ajax:before')) {
|
||||||
var data = el.is('form') ? el.serializeArray() : [];
|
var data = el.is('form') ? el.serializeArray() : [];
|
||||||
|
|
||||||
|
if (!el.is('form') && method != 'GET')
|
||||||
|
data.push({ 'name': csrf_param, 'value': csrf_token });
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: url,
|
url: url,
|
||||||
data: data,
|
data: data,
|
||||||
|
@ -46,4 +46,29 @@ $(document).ready(function() {
|
|||||||
$('#header h1 a span.ui-selectmenu-status').html(value);
|
$('#header h1 a span.ui-selectmenu-status').html(value);
|
||||||
$('#site-selector-menu li.ui-selectmenu-item-selected a').html(value);
|
$('#site-selector-menu li.ui-selectmenu-item-selected a').html(value);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// account roles
|
||||||
|
$('.membership .role em.editable').click(function() {
|
||||||
|
$(this).hide();
|
||||||
|
$(this).next().show();
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.membership .role select').each(function() {
|
||||||
|
var select = $(this);
|
||||||
|
select.hover(function() {
|
||||||
|
clearTimeout($.data(select, 'timer'));
|
||||||
|
},
|
||||||
|
function() {
|
||||||
|
$.data(select, 'timer', setTimeout(function() {
|
||||||
|
select.hide();
|
||||||
|
select.prev().show();
|
||||||
|
}, 1000));
|
||||||
|
}).change(function() {
|
||||||
|
select.hide().prev()
|
||||||
|
.show()
|
||||||
|
.html(select[0].options[select[0].options.selectedIndex].text);
|
||||||
|
});
|
||||||
|
}).hide();
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -24,6 +24,7 @@ form.formtastic legend span {
|
|||||||
color: #1e1f26;
|
color: #1e1f26;
|
||||||
font-size: 0.7em;
|
font-size: 0.7em;
|
||||||
padding: 4px 0 0 20px;
|
padding: 4px 0 0 20px;
|
||||||
|
text-shadow: #fff 0px 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
form.formtastic legend span small {
|
form.formtastic legend span small {
|
||||||
@ -246,6 +247,28 @@ form.formtastic fieldset ol li.item em {
|
|||||||
color: #757575;
|
color: #757575;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
form.formtastic fieldset ol li em.editable {
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
top: -1px;
|
||||||
|
color: #8b8d9a;
|
||||||
|
font-size: 0.9em;
|
||||||
|
font-style: italic;
|
||||||
|
margin-left: 3px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
padding: 2px 5px;
|
||||||
|
height: 18px;
|
||||||
|
line-height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
form.formtastic fieldset ol li em.editable:hover {
|
||||||
|
background: #fffbe5;
|
||||||
|
border: 1px dotted #efe4a5;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #17171D;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
form.formtastic fieldset ol li.item span.actions {
|
form.formtastic fieldset ol li.item span.actions {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 5px;
|
top: 5px;
|
||||||
@ -281,27 +304,6 @@ form.formtastic fieldset.editable-list ol li.added select {
|
|||||||
top: -1px;
|
top: -1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
form.formtastic fieldset.editable-list ol li.added em {
|
|
||||||
display: inline-block;
|
|
||||||
position: relative;
|
|
||||||
top: -1px;
|
|
||||||
color: #8b8d9a;
|
|
||||||
font-size: 0.9em;
|
|
||||||
font-style: italic;
|
|
||||||
margin-left: 3px;
|
|
||||||
border: 1px solid transparent;
|
|
||||||
padding: 2px 5px;
|
|
||||||
height: 18px;
|
|
||||||
line-height: 16px;
|
|
||||||
}
|
|
||||||
form.formtastic fieldset.editable-list ol li.added em:hover {
|
|
||||||
background: #fffbe5;
|
|
||||||
border: 1px dotted #efe4a5;
|
|
||||||
cursor: pointer;
|
|
||||||
color: #17171D;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
form.formtastic fieldset.editable-list ol li.added select,
|
form.formtastic fieldset.editable-list ol li.added select,
|
||||||
form.formtastic fieldset.editable-list ol li.added em {
|
form.formtastic fieldset.editable-list ol li.added em {
|
||||||
width: 150px;
|
width: 150px;
|
||||||
@ -552,6 +554,42 @@ form.formtastic fieldset.email li.full input {
|
|||||||
margin-left: 20px;
|
margin-left: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
form.formtastic fieldset.memberships ol li .role {
|
||||||
|
position: absolute;
|
||||||
|
top: 2px;
|
||||||
|
right: 30px;
|
||||||
|
width: 170px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
form.formtastic fieldset.memberships ol li .role em {
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
top: -1px;
|
||||||
|
color: #757575;
|
||||||
|
font-size: 0.8em;
|
||||||
|
padding: 2px 5px 2px 17px;
|
||||||
|
height: 18px;
|
||||||
|
line-height: 16px;
|
||||||
|
margin-left: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
form.formtastic fieldset.memberships ol li .role em.locked {
|
||||||
|
background: transparent url(/images/admin/icons/membership_lock.png) no-repeat 1px 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
form.formtastic fieldset.memberships ol li .role em.editable {
|
||||||
|
background: transparent url(/images/admin/icons/membership_edit.png) no-repeat left 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
form.formtastic fieldset.memberships ol li .role em.editable { font-style: normal; font-size: 0.8em; }
|
||||||
|
form.formtastic fieldset.memberships ol li .role em.editable:hover { color: #000; font-style: normal; background: #fffbe5; padding-left: 5px; }
|
||||||
|
|
||||||
|
form.formtastic fieldset.memberships ol li select {
|
||||||
|
position: relative;
|
||||||
|
top: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
/* ___ assets ___ */
|
/* ___ assets ___ */
|
||||||
|
|
||||||
.selector {
|
.selector {
|
||||||
|
@ -154,17 +154,19 @@
|
|||||||
text-decoration: none; }
|
text-decoration: none; }
|
||||||
#submenu .popup a:hover {
|
#submenu .popup a:hover {
|
||||||
text-decoration: underline; }
|
text-decoration: underline; }
|
||||||
|
#submenu .popup .header {
|
||||||
|
border-bottom: 1px dotted #bbbbbd;
|
||||||
|
padding-bottom: 6px;
|
||||||
|
margin: 0px 16px; }
|
||||||
#submenu .popup .inner {
|
#submenu .popup .inner {
|
||||||
padding: 8px 16px; }
|
padding: 8px 16px; }
|
||||||
#submenu .popup h2 {
|
#submenu .popup h2 {
|
||||||
font-size: 0.7em;
|
font-size: 0.7em;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: #1e1f26;
|
color: #1e1f26;
|
||||||
border-top: 1px dotted #bbbbbd;
|
|
||||||
padding-top: 6px;
|
|
||||||
margin-bottom: 0px; }
|
margin-bottom: 0px; }
|
||||||
#submenu .popup p {
|
#submenu .popup p {
|
||||||
margin: 0px 15px;
|
margin: 0px;
|
||||||
padding: 10px 0 0 0px; }
|
padding: 10px 0 0 0px; }
|
||||||
#submenu .popup p a {
|
#submenu .popup p a {
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
|
@ -164,19 +164,23 @@
|
|||||||
&:hover { text-decoration: underline; }
|
&:hover { text-decoration: underline; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
border-bottom: 1px dotted #bbbbbd;
|
||||||
|
padding-bottom: 6px;
|
||||||
|
margin: 0px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
.inner { padding: 8px 16px; }
|
.inner { padding: 8px 16px; }
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
font-size: 0.7em;
|
font-size: 0.7em;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: #1e1f26;
|
color: #1e1f26;
|
||||||
border-top: 1px dotted #bbbbbd;
|
|
||||||
padding-top: 6px;
|
|
||||||
margin-bottom: 0px;
|
margin-bottom: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
margin: 0px 15px;
|
margin: 0px;
|
||||||
padding: 10px 0 0 0px;
|
padding: 10px 0 0 0px;
|
||||||
|
|
||||||
a {
|
a {
|
||||||
|
Loading…
Reference in New Issue
Block a user