allow users to use shortcut for theme images when editing snippets / layouts / stylesheets + fix minor ui bugs
This commit is contained in:
parent
a03b631a71
commit
2608430cae
@ -19,7 +19,7 @@ h2. Gems
|
|||||||
|
|
||||||
Here is a short list of main gems used in the application.
|
Here is a short list of main gems used in the application.
|
||||||
|
|
||||||
* Rails 3 (beta 3)
|
* Rails 3 (beta 4)
|
||||||
* Mongoid
|
* Mongoid
|
||||||
* Liquid
|
* Liquid
|
||||||
* Devise
|
* Devise
|
||||||
@ -30,12 +30,17 @@ h2. Installation
|
|||||||
|
|
||||||
See the "official website":http://www.locomotiveapp.org
|
See the "official website":http://www.locomotiveapp.org
|
||||||
|
|
||||||
|
h2. Team
|
||||||
|
|
||||||
|
* Developers: "Didier Lafforgue":http://www.nocoffee.fr, "Jacques Crocker":http://www.railsjedi.com
|
||||||
|
* UI Designer: "Sacha Greif":http://www.sachagreif.com
|
||||||
|
|
||||||
h2. Credits
|
h2. Credits
|
||||||
|
|
||||||
Many thanks to "Sacha Greif":http://www.sachagreif.com for his great work on the user interface and the LocomotiveApp website front page.
|
|
||||||
|
|
||||||
"Rodrigo Alvarez":http://blog.codecaster.es/ for his plugin named Congo which gave us a good starting point and for his availability for (very late) tech discussions.
|
"Rodrigo Alvarez":http://blog.codecaster.es/ for his plugin named Congo which gave us a good starting point and for his availability for (very late) tech discussions.
|
||||||
|
|
||||||
|
"Emmanuel Grard":http://www.grardesign.com designed the awesome locomotive illustration in the LocomotiveApp.org landing page.
|
||||||
|
|
||||||
h2. Contact
|
h2. Contact
|
||||||
|
|
||||||
Feel free to contact me at didier at nocoffee dot fr.
|
Feel free to contact me at didier at nocoffee dot fr.
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
module Admin
|
module Admin
|
||||||
class ThemeAssetsController < BaseController
|
class ThemeAssetsController < BaseController
|
||||||
|
|
||||||
|
include ActionView::Helpers::SanitizeHelper
|
||||||
|
extend ActionView::Helpers::SanitizeHelper::ClassMethods
|
||||||
include ActionView::Helpers::TextHelper
|
include ActionView::Helpers::TextHelper
|
||||||
|
|
||||||
sections 'settings', 'theme_assets'
|
sections 'settings', 'theme_assets'
|
||||||
@ -26,8 +28,10 @@ module Admin
|
|||||||
render :json => {
|
render :json => {
|
||||||
:status => 'success',
|
:status => 'success',
|
||||||
:name => truncate(@theme_asset.slug, :length => 22),
|
:name => truncate(@theme_asset.slug, :length => 22),
|
||||||
|
:slug => @theme_asset.slug,
|
||||||
:url => @theme_asset.source.url,
|
:url => @theme_asset.source.url,
|
||||||
:vignette_url => @theme_asset.vignette_url
|
:vignette_url => @theme_asset.vignette_url,
|
||||||
|
:shortcut_url => @theme_asset.shortcut_url
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
failure.json { render :json => { :status => 'error' } }
|
failure.json { render :json => { :status => 'error' } }
|
||||||
|
@ -11,6 +11,7 @@ class ThemeAsset
|
|||||||
field :width, :type => Integer
|
field :width, :type => Integer
|
||||||
field :height, :type => Integer
|
field :height, :type => Integer
|
||||||
field :size, :type => Integer
|
field :size, :type => Integer
|
||||||
|
field :plain_text
|
||||||
mount_uploader :source, ThemeAssetUploader
|
mount_uploader :source, ThemeAssetUploader
|
||||||
|
|
||||||
## associations ##
|
## associations ##
|
||||||
@ -44,16 +45,17 @@ class ThemeAsset
|
|||||||
end
|
end
|
||||||
|
|
||||||
def plain_text
|
def plain_text
|
||||||
@plain_text ||= (if self.stylesheet_or_javascript?
|
if self.stylesheet_or_javascript?
|
||||||
self.source.read
|
self.plain_text = self.source.read if read_attribute(:plain_text).blank?
|
||||||
|
read_attribute(:plain_text)
|
||||||
else
|
else
|
||||||
nil
|
nil
|
||||||
end)
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def plain_text=(source)
|
def plain_text=(source)
|
||||||
self.performing_plain_text = true if self.performing_plain_text.nil?
|
self.performing_plain_text = true if self.performing_plain_text.nil?
|
||||||
@plain_text = source
|
write_attribute(:plain_text, source)
|
||||||
end
|
end
|
||||||
|
|
||||||
def performing_plain_text?
|
def performing_plain_text?
|
||||||
@ -65,12 +67,32 @@ class ThemeAsset
|
|||||||
def store_plain_text
|
def store_plain_text
|
||||||
return if self.plain_text.blank?
|
return if self.plain_text.blank?
|
||||||
|
|
||||||
|
# replace /theme/<content_type>/<slug> occurences by the real amazon S3 url or local files
|
||||||
|
sanitized_source = self.plain_text.gsub(/(\/theme\/([a-z]+)\/([a-z_\-0-9]+)\.[a-z]{2,3})/) do |url|
|
||||||
|
content_type, slug = url.split('/')[2..-1]
|
||||||
|
|
||||||
|
content_type = content_type.singularize
|
||||||
|
slug = slug.split('.').first
|
||||||
|
|
||||||
|
if asset = self.site.theme_assets.where(:content_type => content_type, :slug => slug).first
|
||||||
|
asset.source.url
|
||||||
|
else
|
||||||
|
url
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
self.source = CarrierWave::SanitizedFile.new({
|
self.source = CarrierWave::SanitizedFile.new({
|
||||||
:tempfile => StringIO.new(self.plain_text),
|
:tempfile => StringIO.new(sanitized_source),
|
||||||
:filename => "#{self.slug}.#{self.stylesheet? ? 'css' : 'js'}"
|
:filename => "#{self.slug}.#{self.stylesheet? ? 'css' : 'js'}"
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def shortcut_url # ex: /theme/stylesheets/application.css is a shortcut for a theme asset (content_type => stylesheet, slug => 'application')
|
||||||
|
File.join('/theme', self.content_type.pluralize, "#{self.slug}#{File.extname(self.source_filename)}")
|
||||||
|
rescue
|
||||||
|
''
|
||||||
|
end
|
||||||
|
|
||||||
def to_liquid
|
def to_liquid
|
||||||
{ :url => self.source.url }.merge(self.attributes)
|
{ :url => self.source.url }.merge(self.attributes)
|
||||||
end
|
end
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
- content_for :head do
|
- content_for :head do
|
||||||
= javascript_include_tag 'admin/plugins/codemirror/codemirror'
|
= javascript_include_tag 'admin/plugins/codemirror/codemirror', 'admin/layouts.js'
|
||||||
= image_picker_include_tags
|
= image_picker_include_tags
|
||||||
|
|
||||||
= f.inputs :name => :information do
|
= f.inputs :name => :information do
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
- edit = local_assigns.key?(:edit) ? edit : true
|
- edit = local_assigns.key?(:edit) ? edit : true
|
||||||
|
|
||||||
%li{ :class => "#{asset.new_record? ? 'new-asset' : 'asset'} #{'last' if (asset_counter + 1) % per_row == 0}"}
|
%li{ :class => "#{asset.new_record? ? 'new-asset' : 'asset'} #{'last' if (asset_counter + 1) % per_row == 0}"}
|
||||||
%h4= link_to truncate(asset.slug, :length => 18), edit ? edit_admin_theme_asset_path(asset) : asset.source.url
|
%h4= link_to truncate(asset.slug, :length => 18), edit ? edit_admin_theme_asset_path(asset) : asset.source.url, :"data-slug" => asset.slug, :"data-shortcut-url" => asset.shortcut_url
|
||||||
.image
|
.image
|
||||||
.inside
|
.inside
|
||||||
= vignette_tag(asset)
|
= vignette_tag(asset)
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
#file-selector{ :class => "selector #{'hidden' if @theme_asset.performing_plain_text?}" }
|
#file-selector{ :class => "selector #{'hidden' if @theme_asset.performing_plain_text?}" }
|
||||||
= f.inputs :name => :information do
|
= f.inputs :name => :information do
|
||||||
= f.input :source
|
= f.input :source
|
||||||
|
= f.input :slug
|
||||||
|
|
||||||
- if @theme_asset.new_record? || @theme_asset.stylesheet_or_javascript?
|
- if @theme_asset.new_record? || @theme_asset.stylesheet_or_javascript?
|
||||||
%span.alt
|
%span.alt
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
- content_for :buttons do
|
- content_for :buttons do
|
||||||
= admin_button_tag t('admin.theme_assets.index.new'), new_admin_theme_asset_url, :class => 'new'
|
= admin_button_tag t('admin.theme_assets.index.new'), new_admin_theme_asset_url, :class => 'new'
|
||||||
|
|
||||||
%p!= t('.help', :url => @theme_asset.source.url)
|
%p!= t('.help', :url => @theme_asset.source.url, :shortcut_url => @theme_asset.shortcut_url)
|
||||||
|
|
||||||
= semantic_form_for @theme_asset, :url => admin_theme_asset_url(@theme_asset), :html => { :multipart => true, :class => 'save-with-shortcut' } do |form|
|
= semantic_form_for @theme_asset, :url => admin_theme_asset_url(@theme_asset), :html => { :multipart => true, :class => 'save-with-shortcut' } do |form|
|
||||||
|
|
||||||
|
@ -172,7 +172,7 @@ en:
|
|||||||
help: "You have the choice to either upload any file or to copy/paste a stylesheet or a javascript in plain text."
|
help: "You have the choice to either upload any file or to copy/paste a stylesheet or a javascript in plain text."
|
||||||
edit:
|
edit:
|
||||||
title: "Editing %{file}"
|
title: "Editing %{file}"
|
||||||
help: "You can use it by copying/pasting the following url: %{url}"
|
help: "You can insert the following shortcut url in your stylesheets: <strong>%{shortcut_url}</strong> OR use the direct url <strong>%{url}</strong>"
|
||||||
form:
|
form:
|
||||||
picker_link: Insert a file into the code
|
picker_link: Insert a file into the code
|
||||||
choose_file: Choose file
|
choose_file: Choose file
|
||||||
|
@ -194,7 +194,7 @@ fr:
|
|||||||
help: "Vous avez le choix de soit uploader n'importe quel fichier ou bien soit de copier/coller du code css ou javascript."
|
help: "Vous avez le choix de soit uploader n'importe quel fichier ou bien soit de copier/coller du code css ou javascript."
|
||||||
edit:
|
edit:
|
||||||
title: "Edition %{file}"
|
title: "Edition %{file}"
|
||||||
help: "Vous pouvez utiliser ce fichier grâce a l'url suivante: %{url}"
|
help: "Vous pouvez insérer le raccourci suivant dans vos feuilles de style: <strong>%{shortcut_url}</strong> OU utiliser directement l'url : %{url}"
|
||||||
form:
|
form:
|
||||||
choose_file: Choisir fichier
|
choose_file: Choisir fichier
|
||||||
choose_plain_text: Passer en mode texte
|
choose_plain_text: Passer en mode texte
|
||||||
|
6
doc/TODO
6
doc/TODO
@ -1,13 +1,10 @@
|
|||||||
BOARD:
|
BOARD:
|
||||||
|
|
||||||
- refactor slugify method (use parameterize + create a module)
|
- refactor slugify method (use parameterize + create a module)
|
||||||
- send email when new content added thru api
|
|
||||||
|
|
||||||
BACKLOG:
|
BACKLOG:
|
||||||
|
|
||||||
- rack app to map pretty asset url to S3
|
|
||||||
- notify accounts when new instance of models (opt): none, one or many accounts. Used for contact form.
|
- notify accounts when new instance of models (opt): none, one or many accounts. Used for contact form.
|
||||||
- theme assets: disable version if not image
|
|
||||||
- new custom field types:
|
- new custom field types:
|
||||||
- belongs_to => association
|
- belongs_to => association
|
||||||
- cucumber features for admin pages
|
- cucumber features for admin pages
|
||||||
@ -28,6 +25,7 @@ NICE TO HAVE:
|
|||||||
- page with regexp url ?
|
- page with regexp url ?
|
||||||
- page redirection (option)
|
- page redirection (option)
|
||||||
- automatic update !
|
- automatic update !
|
||||||
|
- import / export site
|
||||||
|
|
||||||
DONE:
|
DONE:
|
||||||
|
|
||||||
@ -65,3 +63,5 @@ x publish event when saving form in ajax (for instance, in order to update accou
|
|||||||
x page templatized (bound to a model)
|
x page templatized (bound to a model)
|
||||||
x theme asset picker when editing layout / snippet
|
x theme asset picker when editing layout / snippet
|
||||||
x templatized: do not display content with visible / active set to false
|
x templatized: do not display content with visible / active set to false
|
||||||
|
x theme assets: disable version if not image (handled by the new version of Carrierwave)
|
||||||
|
x rack app to map pretty asset url to S3 => shortcut urls instead
|
19
lib/locomotive/liquid/drops/theme_images.rb
Normal file
19
lib/locomotive/liquid/drops/theme_images.rb
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
module Locomotive
|
||||||
|
module Liquid
|
||||||
|
module Drops
|
||||||
|
class ThemeImages < ::Liquid::Drop
|
||||||
|
|
||||||
|
def initialize(site)
|
||||||
|
@site = site
|
||||||
|
end
|
||||||
|
|
||||||
|
def before_method(meth)
|
||||||
|
asset = @site.theme_assets.where(:content_type => 'image', :slug => meth.to_s).first
|
||||||
|
!asset.nil? ? asset.source.url : nil
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -52,6 +52,7 @@ module Locomotive
|
|||||||
'asset_collections' => Locomotive::Liquid::Drops::AssetCollections.new(current_site),
|
'asset_collections' => Locomotive::Liquid::Drops::AssetCollections.new(current_site),
|
||||||
'stylesheets' => Locomotive::Liquid::Drops::Stylesheets.new(current_site),
|
'stylesheets' => Locomotive::Liquid::Drops::Stylesheets.new(current_site),
|
||||||
'javascripts' => Locomotive::Liquid::Drops::Javascripts.new(current_site),
|
'javascripts' => Locomotive::Liquid::Drops::Javascripts.new(current_site),
|
||||||
|
'theme_images' => Locomotive::Liquid::Drops::ThemeImages.new(current_site),
|
||||||
'contents' => Locomotive::Liquid::Drops::Contents.new(current_site),
|
'contents' => Locomotive::Liquid::Drops::Contents.new(current_site),
|
||||||
'current_page' => self.params[:page]
|
'current_page' => self.params[:page]
|
||||||
}
|
}
|
||||||
|
@ -5,10 +5,12 @@ module Locomotive
|
|||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
included do
|
included do
|
||||||
|
if self.respond_to?(:before_filter)
|
||||||
before_filter :fetch_site
|
before_filter :fetch_site
|
||||||
|
|
||||||
helper_method :current_site
|
helper_method :current_site
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
module InstanceMethods
|
module InstanceMethods
|
||||||
|
|
||||||
|
7
public/javascripts/admin/layouts.js
Normal file
7
public/javascripts/admin/layouts.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
$(document).ready(function() {
|
||||||
|
$('a#image-picker-link').imagepicker({
|
||||||
|
insertFn: function(link) {
|
||||||
|
return "{{ theme_images." + link.attr('data-slug') + " }}";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
@ -1,10 +1,17 @@
|
|||||||
$(document).ready(function() {
|
$.fn.imagepicker = function(options) {
|
||||||
|
|
||||||
|
var defaults = {
|
||||||
|
insertFn: null
|
||||||
|
};
|
||||||
|
var options = $.extend(defaults, options);
|
||||||
|
|
||||||
var copyLinkToEditor = function(link, event) {
|
var copyLinkToEditor = function(link, event) {
|
||||||
var editor = CodeMirrorEditors[0].editor;
|
var editor = CodeMirrorEditors[0].editor;
|
||||||
var handle = editor.cursorLine(), position = editor.cursorPosition(handle).character;
|
var handle = editor.cursorLine(), position = editor.cursorPosition(handle).character;
|
||||||
|
|
||||||
editor.insertIntoLine(handle, position, link.attr('href'));
|
var value = options.insertFn != null ? options.insertFn(link) : link.attr('href');
|
||||||
|
|
||||||
|
editor.insertIntoLine(handle, position, value);
|
||||||
|
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@ -40,7 +47,10 @@ $(document).ready(function() {
|
|||||||
.insertBefore($('.asset-picker ul li.clear'))
|
.insertBefore($('.asset-picker ul li.clear'))
|
||||||
.addClass('asset');
|
.addClass('asset');
|
||||||
|
|
||||||
asset.find('h4 a').attr('href', json.url).html(json.name).bind('click', function(e) {
|
asset.find('h4 a').attr('href', json.url)
|
||||||
|
.attr('data-slug', json.slug)
|
||||||
|
.attr('data-shortcut-url', json.shortcut_url)
|
||||||
|
.html(json.name).bind('click', function(e) {
|
||||||
copyLinkToEditor($(this), e);
|
copyLinkToEditor($(this), e);
|
||||||
});
|
});
|
||||||
asset.find('.image .inside img').attr('src', json.vignette_url);
|
asset.find('.image .inside img').attr('src', json.vignette_url);
|
||||||
@ -59,7 +69,8 @@ $(document).ready(function() {
|
|||||||
uploader.init();
|
uploader.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
$('a#image-picker-link').fancybox({
|
return this.each(function() {
|
||||||
|
$(this).fancybox({
|
||||||
'onComplete': function() {
|
'onComplete': function() {
|
||||||
setupUploader();
|
setupUploader();
|
||||||
|
|
||||||
@ -67,3 +78,4 @@ $(document).ready(function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
};
|
@ -13,4 +13,10 @@ $(document).ready(function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
$('#snippet_slug').keypress(function() { $(this).addClass('filled'); });
|
$('#snippet_slug').keypress(function() { $(this).addClass('filled'); });
|
||||||
|
|
||||||
|
$('a#image-picker-link').imagepicker({
|
||||||
|
insertFn: function(link) {
|
||||||
|
return "{{ theme_images." + link.attr('data-slug') + " }}";
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
@ -35,4 +35,9 @@ $(document).ready(function() {
|
|||||||
editor.setParser($(this).val() == 'stylesheet' ? 'CSSParser' : 'JSParser');
|
editor.setParser($(this).val() == 'stylesheet' ? 'CSSParser' : 'JSParser');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$('a#image-picker-link').imagepicker({
|
||||||
|
insertFn: function(link) {
|
||||||
|
return link.attr('data-shortcut-url');
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
@ -117,11 +117,11 @@ ul.assets li.asset.last {
|
|||||||
margin-right: 0px;
|
margin-right: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
ul.assets li.asset h4 { margin: 0px; height: 30px; border-bottom: 1px solid #ccced7; }
|
ul.assets li.asset h4 { margin: 0px; height: 30px; border-bottom: 1px solid #ccced7; position: relative; }
|
||||||
|
|
||||||
ul.assets li.asset h4 a {
|
ul.assets li.asset h4 a {
|
||||||
position: relative;
|
position: absolute;
|
||||||
top: 6px;
|
top: 7px;
|
||||||
left: 12px;
|
left: 12px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 0.6em;
|
font-size: 0.6em;
|
||||||
@ -149,7 +149,7 @@ ul.assets li.asset div.image div.inside {
|
|||||||
|
|
||||||
ul.assets li.asset div.actions {
|
ul.assets li.asset div.actions {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 7px;
|
top: 4px;
|
||||||
right: 12px;
|
right: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,7 +64,7 @@ describe ThemeAsset do
|
|||||||
|
|
||||||
before(:each) do
|
before(:each) do
|
||||||
ThemeAsset.any_instance.stubs(:site_id).returns('test')
|
ThemeAsset.any_instance.stubs(:site_id).returns('test')
|
||||||
@asset = Factory.build(:theme_asset)
|
@asset = Factory.build(:theme_asset, :site => Factory.build(:site))
|
||||||
@asset.performing_plain_text = true
|
@asset.performing_plain_text = true
|
||||||
@asset.slug = 'a file'
|
@asset.slug = 'a file'
|
||||||
@asset.plain_text = "Lorem ipsum"
|
@asset.plain_text = "Lorem ipsum"
|
||||||
@ -84,6 +84,24 @@ describe ThemeAsset do
|
|||||||
@asset.source.should_not be_nil
|
@asset.source.should_not be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'shortcut urls' do
|
||||||
|
|
||||||
|
before(:each) do
|
||||||
|
@image = Factory.build(:theme_asset, :source => FixturedAsset.open('5k.png'))
|
||||||
|
@image.source.stubs(:url).returns('5k.png')
|
||||||
|
@asset.stubs(:stylesheet?).returns(true)
|
||||||
|
@asset.site.theme_assets.stubs(:where).returns([@image])
|
||||||
|
@asset.plain_text = 'body { background-image: url("/theme/images/5k.png"); } h1 { background-image: url("/images/5k.png"); }'
|
||||||
|
@asset.store_plain_text
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'replaces shortcut url if present' do
|
||||||
|
@asset.plain_text.should == 'body { background-image: url("/theme/images/5k.png"); } h1 { background-image: url("/images/5k.png"); }'
|
||||||
|
@asset.source.read.should == 'body { background-image: url("5k.png"); } h1 { background-image: url("/images/5k.png"); }'
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
Loading…
Reference in New Issue
Block a user