diff --git a/Gemfile b/Gemfile index ba9e1444..68460ae3 100644 --- a/Gemfile +++ b/Gemfile @@ -3,8 +3,8 @@ source 'http://rubygems.org' gem 'rails', '3.0.0.rc' -# gem 'liquid', :path => '../gems/liquid' # local -gem 'liquid', :git => 'git://github.com/locomotivecms/liquid.git', :ref => '9ec570927f5281e1f397' +gem 'liquid', :path => '../gems/liquid' # local +# gem 'liquid', :git => 'git://github.com/locomotivecms/liquid.git', :ref => '9ec570927f5281e1f397' gem 'bson_ext', '1.0.4' gem 'mongoid', '2.0.0.beta.16' diff --git a/Gemfile.lock b/Gemfile.lock index ee498073..b2fd02af 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -11,13 +11,6 @@ GIT specs: custom_fields (0.0.0.1) -GIT - remote: git://github.com/locomotivecms/liquid.git - revision: 9ec5709 - ref: 9ec570927f5281e1f397 - specs: - liquid (2.1.3) - GIT remote: http://github.com/ianwhite/pickle.git revision: 65ba8b7 @@ -28,6 +21,11 @@ GIT rspec (>= 1.3) yard +PATH + remote: /Users/didier/Desktop/NoCoffee/LocomotiveCMS/gems/liquid + specs: + liquid (2.1.3) + GEM remote: http://rubygems.org/ specs: diff --git a/app/models/editable_element.rb b/app/models/editable_element.rb index 0111ad89..20e4b5ff 100644 --- a/app/models/editable_element.rb +++ b/app/models/editable_element.rb @@ -3,10 +3,10 @@ class EditableElement include Mongoid::Document ## fields ## - field :kind + # field :kind field :slug field :block - field :content + # field :content field :default_content field :hint field :disabled, :type => Boolean, :default => false @@ -20,12 +20,12 @@ class EditableElement ## methods ## - def content - self.read_attribute(:content).blank? ? self.default_content : self.read_attribute(:content) - end - - def short_text?; self.kind == 'ShortText'; end - - def long_text?; self.kind == 'LongText'; end + # def content + # self.read_attribute(:content).blank? ? self.default_content : self.read_attribute(:content) + # end + # + # def short_text?; self._type == 'EditableShortText'; end + # + # def long_text?; self._type == 'EditableLongText'; end end \ No newline at end of file diff --git a/app/models/editable_file.rb b/app/models/editable_file.rb new file mode 100644 index 00000000..f96fa619 --- /dev/null +++ b/app/models/editable_file.rb @@ -0,0 +1,9 @@ +class EditableFile < EditableElement + + mount_uploader :source, EditableFileUploader + + def content + self.source? ? self.source.url : self.default_content + end + +end \ No newline at end of file diff --git a/app/models/editable_long_text.rb b/app/models/editable_long_text.rb new file mode 100644 index 00000000..2481aadc --- /dev/null +++ b/app/models/editable_long_text.rb @@ -0,0 +1,3 @@ +class EditableLongText < EditableShortText + +end \ No newline at end of file diff --git a/app/models/editable_short_text.rb b/app/models/editable_short_text.rb new file mode 100644 index 00000000..5e57a193 --- /dev/null +++ b/app/models/editable_short_text.rb @@ -0,0 +1,12 @@ +class EditableShortText < EditableElement + + ## fields ## + field :content + + ## methods ## + + def content + self.read_attribute(:content).blank? ? self.default_content : self.read_attribute(:content) + end + +end \ No newline at end of file diff --git a/app/models/extensions/page/editable_elements.rb b/app/models/extensions/page/editable_elements.rb index 76f9311b..2df768f7 100644 --- a/app/models/extensions/page/editable_elements.rb +++ b/app/models/extensions/page/editable_elements.rb @@ -8,6 +8,13 @@ module Models included do embeds_many :editable_elements + after_save :remove_disabled_editable_elements + + # editable file callbacks + after_save :store_file_sources! + before_save :write_file_source_identifiers + after_destroy :remove_file_sources! + accepts_nested_attributes_for :editable_elements end @@ -26,21 +33,26 @@ module Models end def editable_elements_grouped_by_blocks - groups = self.editable_elements.group_by(&:block) - groups.delete_if { |block, elements| elements.all? { |el| el.disabled? } } + all_enabled = self.editable_elements.reject { |el| el.disabled? } + groups = all_enabled.group_by(&:block) + groups.delete_if { |block, elements| elements.empty? } end def find_editable_element(block, slug) self.editable_elements.detect { |el| el.block == block && el.slug == slug } end - def add_or_update_editable_element(attributes) + def find_editable_files + self.editable_elements.find_all { |el| el.respond_to?(:source) } + end + + def add_or_update_editable_element(attributes, type) element = self.find_editable_element(attributes[:block], attributes[:slug]) if element element.attributes = attributes else - self.editable_elements.build(attributes) + self.editable_elements.build(attributes, type) end end @@ -50,18 +62,52 @@ module Models def merge_editable_elements_from_page(source) source.editable_elements.each do |el| + puts "\t*** merging #{el.class} / #{el.slug} / #{el.block} / #{el.disabled?} / #{el.from_parent?}" + next if el.disabled? existing_el = self.find_editable_element(el.block, el.slug) if existing_el.nil? # new one from parents - self.editable_elements.build(el.attributes.merge(:from_parent => true)) + new_attributes = el.attributes.merge(:from_parent => true) + new_attributes[:default_content] = el.content + + foo = self.editable_elements.build(new_attributes, el.class) + puts "\t\t*** building #{foo.inspect} / #{foo.valid?} / #{foo.errors.full_messages.inspect}" else - existing_el.disabled = false + existing_el.attributes = { :disabled => false, :default_content => el.content } end end end + def remove_disabled_editable_elements + return unless self.editable_elements.any? { |el| el.disabled? } + + puts "*** removing #{self.editable_elements.find_all { |el| el.disabled? }.size} elements" + + # super fast way to remove useless elements all in once (TODO callbacks) + self.collection.update(self._selector, '$pull' => { 'editable_elements' => { 'disabled' => true } }) + end + + protected + + ## callbacks for editable files + + # equivalent to "after_save :store_source!" in EditableFile + def store_file_sources! + self.find_editable_files.collect(&:store_source!) + end + + # equivalent to "before_save :write_source_identifier" in EditableFile + def write_file_source_identifiers + self.find_editable_files.collect(&:write_source_identifier) + end + + # equivalent to "after_destroy :remove_source!" in EditableFile + def remove_file_sources! + self.find_editable_files.collect(&:remove_source!) + end + end end diff --git a/app/models/extensions/page/parse.rb b/app/models/extensions/page/parse.rb index 7395b241..0efc2e9c 100644 --- a/app/models/extensions/page/parse.rb +++ b/app/models/extensions/page/parse.rb @@ -93,9 +93,11 @@ module Models end direct_descendants.each do |page| + puts "*** #{page.fullpath} descendant of #{self.fullpath}" page.send(:_parse_and_serialize_template, { :cached_parent => self, :cached_pages => cached }) page.send(:_update_direct_template_descendants, template_descendants, cached) + puts "-------- done -----------" end end diff --git a/app/uploaders/editable_file_uploader.rb b/app/uploaders/editable_file_uploader.rb new file mode 100644 index 00000000..db805737 --- /dev/null +++ b/app/uploaders/editable_file_uploader.rb @@ -0,0 +1,11 @@ +class EditableFileUploader < ::CarrierWave::Uploader::Base + + def store_dir + "sites/#{model.page.site_id}/pages/#{model.page.id}/files" + end + + def cache_dir + "#{Rails.root}/tmp/uploads" + end + +end \ No newline at end of file diff --git a/app/views/admin/custom_fields/_custom_form.html.haml b/app/views/admin/custom_fields/_custom_form.html.haml index 7d478ca2..fd7deece 100644 --- a/app/views/admin/custom_fields/_custom_form.html.haml +++ b/app/views/admin/custom_fields/_custom_form.html.haml @@ -22,7 +22,7 @@ = form.custom_input field._alias.to_sym, :label => field.label, :hint => field.hint, :css => 'file' do = form.file_field field._name.to_sym - if form.object.send(:"#{field._name}?") - %p + %p.remove %strong = link_to File.basename(form.object.send(field._name).url), form.object.send(field._name).url %span diff --git a/app/views/admin/pages/_editable_elements.html.haml b/app/views/admin/pages/_editable_elements.html.haml index 5d5889f9..c2d7c3d7 100644 --- a/app/views/admin/pages/_editable_elements.html.haml +++ b/app/views/admin/pages/_editable_elements.html.haml @@ -17,7 +17,19 @@ %ol - elements.each_with_index do |el, index| = f.fields_for 'editable_elements', el, :child_index => el._index do |g| - - if el.short_text? - = g.input :content, :label => el.slug.humanize, :hint => el.hint - - elsif el.long_text? - = g.input :content, :label => el.slug.humanize, :hint => el.hint, :as => :text, :input_html => { :class => 'html' } \ No newline at end of file + - case el + - when EditableLongText + = g.input :content, :label => el.slug.humanize, :hint => el.hint, :as => :text, :input_html => { :class => 'html' } + - when EditableShortText + = g.input :content, :label => el.slug.humanize, :hint => el.hint + - when EditableFile + = g.custom_input :source, :label => el.slug.humanize, :hint => el.hint, :css => 'file' do + = g.file_field :source + - if el.source? + %p.remove + %strong + = link_to File.basename(el.source.url), el.source.url + %span +  /  + != t('admin.pages.form.delete_file') + = g.check_box :remove_source \ No newline at end of file diff --git a/app/views/admin/pages/edit.html.haml b/app/views/admin/pages/edit.html.haml index 831b4a64..f25197a2 100644 --- a/app/views/admin/pages/edit.html.haml +++ b/app/views/admin/pages/edit.html.haml @@ -8,7 +8,7 @@ %p!= t('.help') -= semantic_form_for @page, :url => admin_page_url(@page), :html => { :class => 'save-with-shortcut' } do |form| += semantic_form_for @page, :url => admin_page_url(@page), :html => { :class => 'save-with-shortcut', :multipart => true } do |form| = render 'form', :f => form diff --git a/config/locales/admin_ui_en.yml b/config/locales/admin_ui_en.yml index b36a75f2..aedf7912 100644 --- a/config/locales/admin_ui_en.yml +++ b/config/locales/admin_ui_en.yml @@ -99,6 +99,7 @@ en: help: "The page title can be updated by clicking it." ask_for_title: "Please type the new page title" form: + delete_file: Delete file default_block: Default cache_strategy: none: None diff --git a/config/locales/admin_ui_fr.yml b/config/locales/admin_ui_fr.yml index 3419be89..17e8428c 100644 --- a/config/locales/admin_ui_fr.yml +++ b/config/locales/admin_ui_fr.yml @@ -99,6 +99,7 @@ fr: help: "Le titre de la page est modifiable en cliquant dessus." ask_for_title: "Veuillez entrer le nouveau titre" form: + delete_file: Supprimer fichier default_block: Défaut cache_strategy: none: Aucun diff --git a/doc/TODO b/doc/TODO index 0134cf8b..5ad4aa87 100644 --- a/doc/TODO +++ b/doc/TODO @@ -8,7 +8,11 @@ x theme assets selector in page editor x saving page in ajax x editable_long_text tag x blocking issue when modifying the parent of 2 templates => one of the 2 children has reference of the first child -- editable_file tag +x editable_file tag +x stylish file field +x remove not used editable element all in once +x default content from parent editable element +x unable to upload/remove editable file - refactor slugify method (use parameterize + create a module) - [content types] the "display column" selector should not include file types diff --git a/features/engine/editable_elements.feature b/features/engine/editable_elements.feature index 6591a4fa..de72d5a3 100644 --- a/features/engine/editable_elements.feature +++ b/features/engine/editable_elements.feature @@ -113,4 +113,17 @@ Scenario: Combine inheritance and update My application says Hello world Another Main Default sidebar title - """ \ No newline at end of file + """ + +Scenario: Insert editable files + Given a page named "hello-world" with the template: + """ + My application file is {% editable_file 'a_file', hint: 'please enter a new file' %}/default.pdf{% endeditable_file %} + """ + When I view the rendered page at "/hello-world" + Then the rendered output should look like: + """ + My application file is /default.pdf + """ + + diff --git a/lib/locomotive/liquid/tags/editable.rb b/lib/locomotive/liquid/tags/editable.rb index dd58d977..82609f12 100644 --- a/lib/locomotive/liquid/tags/editable.rb +++ b/lib/locomotive/liquid/tags/editable.rb @@ -1,2 +1,4 @@ +require 'locomotive/liquid/tags/editable/base' require 'locomotive/liquid/tags/editable/short_text' -require 'locomotive/liquid/tags/editable/long_text' \ No newline at end of file +require 'locomotive/liquid/tags/editable/long_text' +require 'locomotive/liquid/tags/editable/file' \ No newline at end of file diff --git a/lib/locomotive/liquid/tags/editable/base.rb b/lib/locomotive/liquid/tags/editable/base.rb new file mode 100644 index 00000000..1360bcab --- /dev/null +++ b/lib/locomotive/liquid/tags/editable/base.rb @@ -0,0 +1,60 @@ +module Locomotive + module Liquid + module Tags + module Editable + class Base < ::Liquid::Block + + Syntax = /(#{::Liquid::QuotedFragment})(\s*,\s*#{::Liquid::Expression}+)?/ + + def initialize(tag_name, markup, tokens, context) + if markup =~ Syntax + @slug = $1.gsub('\'', '') + @options = {} + markup.scan(::Liquid::TagAttributes) { |key, value| @options[key.to_sym] = value.gsub(/^'/, '').gsub(/'$/, '') } + else + raise ::Liquid::SyntaxError.new("Syntax Error in 'editable_xxx' - Valid syntax: editable_xxx (, )") + end + + super + end + + def end_tag + @context[:page].add_or_update_editable_element({ + :block => @context[:current_block].try(:name), + :slug => @slug, + :hint => @options[:hint], + :default_content => @nodelist.first.to_s, + :disabled => false, + :from_parent => false + }, document_type) + end + + def render(context) + current_page = context.registers[:page] + + element = current_page.find_editable_element(context['block'].try(:name), @slug) + + if element + render_element(element) + else + Locomotive.logger "[editable element] missing element #{context[:block].name} / #{@slug}" + '' + end + end + + protected + + def render_element(element) + raise 'FIXME: has to be overidden' + end + + def document_type + raise 'FIXME: has to be overidden' + end + + end + + end + end + end +end diff --git a/lib/locomotive/liquid/tags/editable/file.rb b/lib/locomotive/liquid/tags/editable/file.rb new file mode 100644 index 00000000..8a0bf669 --- /dev/null +++ b/lib/locomotive/liquid/tags/editable/file.rb @@ -0,0 +1,23 @@ +module Locomotive + module Liquid + module Tags + module Editable + class File < Base + + protected + + def render_element(element) + element.source? ? element.source.url : element.default_content + end + + def document_type + EditableFile + end + + end + + ::Liquid::Template.register_tag('editable_file', File) + end + end + end +end diff --git a/lib/locomotive/liquid/tags/editable/long_text.rb b/lib/locomotive/liquid/tags/editable/long_text.rb index 3b512db0..cbe10ad0 100644 --- a/lib/locomotive/liquid/tags/editable/long_text.rb +++ b/lib/locomotive/liquid/tags/editable/long_text.rb @@ -3,6 +3,13 @@ module Locomotive module Tags module Editable class LongText < ShortText + + protected + + def document_type + EditableLongText + end + end ::Liquid::Template.register_tag('editable_long_text', LongText) diff --git a/lib/locomotive/liquid/tags/editable/short_text.rb b/lib/locomotive/liquid/tags/editable/short_text.rb index a14b8097..26c41079 100644 --- a/lib/locomotive/liquid/tags/editable/short_text.rb +++ b/lib/locomotive/liquid/tags/editable/short_text.rb @@ -2,51 +2,16 @@ module Locomotive module Liquid module Tags module Editable - class ShortText < ::Liquid::Block - - Syntax = /(#{::Liquid::QuotedFragment})(\s*,\s*#{::Liquid::Expression}+)?/ - - def initialize(tag_name, markup, tokens, context) - if markup =~ Syntax - @slug = $1.gsub('\'', '') - @options = {} - markup.scan(::Liquid::TagAttributes) { |key, value| @options[key.to_sym] = value.gsub(/^'/, '').gsub(/'$/, '') } - else - raise ::Liquid::SyntaxError.new("Syntax Error in 'editable_short_text' - Valid syntax: editable_short_text (, )") - end - - super - end - - def end_tag - @context[:page].add_or_update_editable_element({ - :kind => self.kind, - :block => @context[:current_block].try(:name), - :slug => @slug, - :hint => @options[:hint], - :default_content => @nodelist.first.to_s, - :disabled => false, - :from_parent => false - }) - end - - def render(context) - current_page = context.registers[:page] - - element = current_page.find_editable_element(context['block'].try(:name), @slug) - - if element - element.content - else - Locomotive.logger "[editable short text] missing editable short text #{context[:block].name} / #{@slug}" - '' - end - end + class ShortText < Base protected - def kind - self.class.name.demodulize + def render_element(element) + element.content + end + + def document_type + EditableShortText end end diff --git a/lib/locomotive/liquid/tags/extends.rb b/lib/locomotive/liquid/tags/extends.rb index 44814192..71618910 100644 --- a/lib/locomotive/liquid/tags/extends.rb +++ b/lib/locomotive/liquid/tags/extends.rb @@ -8,6 +8,8 @@ module Locomotive parent_page = @context[:parent_page] + @context[:page].merge_editable_elements_from_page(parent_page) + @context[:snippets] = parent_page.snippet_dependencies @context[:templates] = ([*parent_page.template_dependencies] + [parent_page.id]).compact end @@ -29,8 +31,6 @@ module Locomotive raise PageNotFound.new("Page with fullpath '#{@template_name}' was not found") if @context[:parent_page].nil? - @context[:page].merge_editable_elements_from_page(@context[:parent_page]) - @context[:parent_page].template end diff --git a/lib/locomotive/mongoid/patches.rb b/lib/locomotive/mongoid/patches.rb index 987386a7..419601ea 100644 --- a/lib/locomotive/mongoid/patches.rb +++ b/lib/locomotive/mongoid/patches.rb @@ -15,3 +15,58 @@ module Mongoid #:nodoc: end end + +# http://github.com/emk/mongoid/blob/503e346b1b7b250d682a12332ad9d5872f1575e6/lib/mongoid/atomicity.rb +module Mongoid #:nodoc: + module Atomicity #:nodoc: + extend ActiveSupport::Concern + + def _updates + processed = {} + + _children.inject({ "$set" => _sets, "$pushAll" => {}, :other => {} }) do |updates, child| + changes = child._sets + updates["$set"].update(changes) + unless changes.empty? + processed[child._conficting_modification_key] = true + end + + # MongoDB does not allow "conflicting modifications" to be + # performed in a single operation. Conflicting modifications are + # detected by the 'haveConflictingMod' function in MongoDB. + # Examination of the code suggests that two modifications (a $set + # and a $pushAll, for example) conflict if (1) the key paths being + # modified are equal or (2) one key path is a prefix of the other. + # So a $set of 'addresses.0.street' will conflict with a $pushAll + # to 'addresses', and we will need to split our update into two + # pieces. We do not, however, attempt to match MongoDB's logic + # exactly. Instead, we assume that two updates conflict if the + # first component of the two key paths matches. + if processed.has_key?(child._conficting_modification_key) + target = :other + else + target = "$pushAll" + end + + child._pushes.each do |attr, val| + if updates[target].has_key?(attr) + updates[target][attr] << val + else + updates[target].update({attr => [val]}) + end + end + updates + end.delete_if do |key, value| + value.empty? + end + end + + protected + # Get the key used to check for conflicting modifications. For now, we + # just use the first component of _path, and discard the first period + # and everything that follows. + def _conficting_modification_key + _path.sub(/\..*/, '') + end + end +end \ No newline at end of file diff --git a/public/stylesheets/admin/editable_elements.css b/public/stylesheets/admin/editable_elements.css index 1a12f0e9..67163e18 100644 --- a/public/stylesheets/admin/editable_elements.css +++ b/public/stylesheets/admin/editable_elements.css @@ -59,5 +59,6 @@ #editable-elements .wrapper ul li fieldset ol { margin-top: 0px; border-top: 0px; background: #EBEDF4; } #editable-elements .wrapper ul li fieldset ol li label { padding-left: 0px; padding-right: 3em; } +#editable-elements .wrapper ul li fieldset ol li p.remove, #editable-elements .wrapper ul li fieldset ol li p.inline-hints { margin-left: 13.3em; } diff --git a/public/stylesheets/admin/formtastic_changes.css b/public/stylesheets/admin/formtastic_changes.css index 7cac8463..83880a61 100644 --- a/public/stylesheets/admin/formtastic_changes.css +++ b/public/stylesheets/admin/formtastic_changes.css @@ -380,22 +380,22 @@ form.formtastic fieldset.validations ol li.added em.key { width: 180px; } -/* ___ content instance ___ */ +/* ___ content instance / editable elements___ */ form.content_instance fieldset ol li.text textarea { width: 75%; } -form.content_instance fieldset ol li.file p { +form.formtastic fieldset ol li.file p.remove { margin: 5px 0 0 20%; } -form.content_instance fieldset ol li.file p a { +form.formtastic fieldset ol li.file p.remove a { text-decoration: none; color: #333; } -form.content_instance fieldset ol li.file p a:hover { text-decoration: underline; } +form.formtastic fieldset ol li.file p.remove a:hover { text-decoration: underline; } /* ___ my account ___ */