locomotive_cms 0.0.3.1 → 0.0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/app/models/content_instance.rb +16 -1
- data/app/models/extensions/page/templatized.rb +32 -0
- data/app/models/page.rb +3 -2
- data/app/uploaders/asset_uploader.rb +0 -2
- data/app/views/admin/contents/_list.html.haml +0 -1
- data/app/views/admin/my_accounts/edit.html.haml +3 -0
- data/app/views/admin/pages/_form.html.haml +7 -2
- data/app/views/admin/pages/_page.html.haml +1 -1
- data/app/views/admin/shared/_head.html.haml +1 -1
- data/config/locales/admin_ui_en.yml +1 -0
- data/config/locales/admin_ui_fr.yml +1 -0
- data/lib/locomotive.rb +0 -1
- data/lib/locomotive/engine.rb +2 -0
- data/lib/locomotive/liquid/drops/content.rb +3 -1
- data/lib/locomotive/liquid/drops/page.rb +9 -1
- data/lib/locomotive/liquid/tags/nav.rb +4 -2
- data/lib/locomotive/render.rb +22 -2
- data/public/images/admin/list/item-left.png +0 -0
- data/public/javascripts/admin/account.js +5 -0
- data/public/javascripts/admin/pages.js +13 -0
- data/public/javascripts/admin/plugins/shortcut.js +1 -0
- data/public/javascripts/admin/plugins/subscribe.js +367 -0
- data/public/javascripts/admin/plugins/toggle.js +3 -0
- data/public/javascripts/admin/site.js +5 -2
- data/public/stylesheets/admin/application.css +6 -1
- data/public/stylesheets/admin/formtastic_changes.css +2 -1
- data/public/stylesheets/admin/page_parts.css +7 -0
- data/spec/lib/locomotive/render_spec.rb +30 -5
- data/spec/models/page_spec.rb +22 -0
- metadata +6 -3
@@ -7,6 +7,7 @@ class ContentInstance
|
|
7
7
|
include CustomFields::ProxyClassEnabler
|
8
8
|
|
9
9
|
## fields (dynamic fields) ##
|
10
|
+
field :_slug
|
10
11
|
field :_position_in_list, :type => Integer, :default => 0
|
11
12
|
|
12
13
|
## validations ##
|
@@ -16,6 +17,7 @@ class ContentInstance
|
|
16
17
|
embedded_in :content_type, :inverse_of => :contents
|
17
18
|
|
18
19
|
## callbacks ##
|
20
|
+
before_save :set_slug
|
19
21
|
before_create :add_to_list_bottom
|
20
22
|
|
21
23
|
## named scopes ##
|
@@ -29,16 +31,29 @@ class ContentInstance
|
|
29
31
|
|
30
32
|
protected
|
31
33
|
|
34
|
+
def set_slug
|
35
|
+
_alias = self.highlighted_field_alias
|
36
|
+
self._slug = self.send(_alias).parameterize('_')
|
37
|
+
end
|
38
|
+
|
32
39
|
def add_to_list_bottom
|
33
40
|
Rails.logger.debug "add_to_list_bottom"
|
34
41
|
self._position_in_list = self.content_type.contents.size
|
35
42
|
end
|
36
43
|
|
37
44
|
def require_highlighted_field
|
38
|
-
_alias = self.
|
45
|
+
_alias = self.highlighted_field_alias
|
39
46
|
if self.send(_alias).blank?
|
40
47
|
self.errors.add(_alias, :blank)
|
41
48
|
end
|
42
49
|
end
|
43
50
|
|
51
|
+
def highlighted_field_value
|
52
|
+
self.send(self.content_type.highlighted_field._name)
|
53
|
+
end
|
54
|
+
|
55
|
+
def highlighted_field_alias
|
56
|
+
self.content_type.highlighted_field._alias.to_sym
|
57
|
+
end
|
58
|
+
|
44
59
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Models
|
2
|
+
module Extensions
|
3
|
+
module Page
|
4
|
+
module Templatized
|
5
|
+
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
|
10
|
+
belongs_to_related :content_type
|
11
|
+
|
12
|
+
field :templatized, :type => Boolean, :default => false
|
13
|
+
|
14
|
+
field :content_type_visible_column
|
15
|
+
|
16
|
+
before_validate :set_slug_if_templatized
|
17
|
+
end
|
18
|
+
|
19
|
+
module InstanceMethods
|
20
|
+
|
21
|
+
def set_slug_if_templatized
|
22
|
+
self.slug = 'content_type_template' if self.templatized?
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
|
data/app/models/page.rb
CHANGED
@@ -6,6 +6,7 @@ class Page
|
|
6
6
|
include Models::Extensions::Page::Tree
|
7
7
|
include Models::Extensions::Page::Parts
|
8
8
|
include Models::Extensions::Page::Render
|
9
|
+
include Models::Extensions::Page::Templatized
|
9
10
|
|
10
11
|
## fields ##
|
11
12
|
field :title
|
@@ -31,8 +32,8 @@ class Page
|
|
31
32
|
|
32
33
|
## named scopes ##
|
33
34
|
named_scope :latest_updated, :order_by => [[:updated_at, :desc]], :limit => Locomotive.config.lastest_items_nb
|
34
|
-
named_scope :index, :where => { :slug => 'index', :depth => 0
|
35
|
-
named_scope :not_found, :where => { :slug => '404', :depth => 0
|
35
|
+
named_scope :index, :where => { :slug => 'index', :depth => 0 }
|
36
|
+
named_scope :not_found, :where => { :slug => '404', :depth => 0 }
|
36
37
|
named_scope :published, :where => { :published => true }
|
37
38
|
|
38
39
|
## behaviours ##
|
@@ -1,7 +1,6 @@
|
|
1
1
|
- if contents.empty?
|
2
2
|
%p.no-items= t('.no_items', :url => new_admin_content_url(@content_type.slug))
|
3
3
|
- else
|
4
|
-
- puts contents.inspect
|
5
4
|
%ul{ :id => 'contents-list', :class => "list #{'sortable' if @content_type.order_by == '_position_in_list'}" }
|
6
5
|
- contents.each do |content|
|
7
6
|
%li.content{ :id => "content-#{content._id}" }
|
@@ -1,5 +1,8 @@
|
|
1
1
|
- title link_to(@account.name.blank? ? @account.name_was : @account.name, '#', :rel => 'my_account_name', :title => t('.ask_for_name'), :class => 'editable')
|
2
2
|
|
3
|
+
- content_for :head do
|
4
|
+
= javascript_include_tag 'admin/account'
|
5
|
+
|
3
6
|
- content_for :submenu do
|
4
7
|
= render 'admin/shared/menu/settings'
|
5
8
|
|
@@ -11,8 +11,13 @@
|
|
11
11
|
- if not @page.index? and not @page.not_found?
|
12
12
|
= f.input :parent_id, :as => :select, :collection => parent_pages_options, :include_blank => false
|
13
13
|
|
14
|
-
= f.input :slug, :required => false, :hint => @page.slug.blank? ? ' ' : @page.url, :input_html => { :data_url => get_path_admin_pages_url, :disabled => @page.index? || @page.not_found? }
|
15
|
-
|
14
|
+
= f.input :slug, :required => false, :hint => @page.slug.blank? ? ' ' : @page.url, :input_html => { :data_url => get_path_admin_pages_url, :disabled => @page.index? || @page.not_found? }, :wrapper_html => { :style => "#{'display: none' if @page.templatized?}" }
|
15
|
+
|
16
|
+
= f.custom_input :templatized, :css => 'toggle' do
|
17
|
+
= f.check_box :templatized
|
18
|
+
|
19
|
+
= 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?}" }
|
20
|
+
|
16
21
|
= f.custom_input :published, :css => 'toggle' do
|
17
22
|
= f.check_box :published
|
18
23
|
|
@@ -1,4 +1,4 @@
|
|
1
|
-
%li{ :id => "item-#{page.id}", :class => "#{'not-found' if page.not_found? }"}
|
1
|
+
%li{ :id => "item-#{page.id}", :class => "#{'not-found' if page.not_found? } #{'templatized' if page.templatized?}"}
|
2
2
|
- if not page.index? and not page.children.empty?
|
3
3
|
= image_tag 'admin/list/icons/node_closed.png', :class => 'toggler'
|
4
4
|
%em
|
@@ -10,7 +10,7 @@
|
|
10
10
|
|
11
11
|
= stylesheet_link_tag 'admin/layout', 'admin/jquery/ui', 'admin/plugins/toggle', 'admin/menu', 'admin/buttons', 'admin/formtastic', 'admin/formtastic_changes', 'admin/application', :media => 'screen', :cache => Rails.env.production? && !Locomotive.heroku?
|
12
12
|
|
13
|
-
= javascript_include_tag 'admin/jquery', 'admin/jquery.ui', 'admin/rails', 'admin/utils', 'admin/plugins/shortcut', 'admin/plugins/toggle', 'admin/plugins/growl', 'admin/plugins/cookie', 'admin/application', 'admin/locales/datepicker_fr', :cache => Rails.env.production? && !Locomotive.heroku?
|
13
|
+
= javascript_include_tag 'admin/jquery', 'admin/jquery.ui', 'admin/rails', 'admin/utils', 'admin/plugins/subscribe', 'admin/plugins/shortcut', 'admin/plugins/toggle', 'admin/plugins/growl', 'admin/plugins/cookie', 'admin/application', 'admin/locales/datepicker_fr', :cache => Rails.env.production? && !Locomotive.heroku?
|
14
14
|
|
15
15
|
%script{ :type => 'text/javascript' }
|
16
16
|
= find_and_preserve(growl_message)
|
@@ -268,6 +268,7 @@ en:
|
|
268
268
|
page:
|
269
269
|
published: "Only authenticated accounts can view unpublished pages."
|
270
270
|
cache_strategy: "Cache the page for better performance. The 'Simple' choice is a good compromise."
|
271
|
+
templatized: "Use the page as a template for a model you defined."
|
271
272
|
snippet:
|
272
273
|
slug: "You need to know it in order to insert the snippet inside a page or a layout"
|
273
274
|
site:
|
@@ -290,6 +290,7 @@ fr:
|
|
290
290
|
page:
|
291
291
|
published: "Seuls les administrateurs authentifiés peuvent voir une page non publiée."
|
292
292
|
cache_strategy: "Cache la page pour de meilleure performance. L'option 'Simple' est le meilleur compromis."
|
293
|
+
templatized: "Utilise la page comme un template pour un modèle défini."
|
293
294
|
snippet:
|
294
295
|
slug: "Utilisé pour insérer le snippet dans une page ou un gabarit."
|
295
296
|
site:
|
data/lib/locomotive.rb
CHANGED
data/lib/locomotive/engine.rb
CHANGED
@@ -8,6 +8,8 @@ require 'formtastic'
|
|
8
8
|
require 'mongoid'
|
9
9
|
require 'mongoid_acts_as_tree'
|
10
10
|
require 'httparty'
|
11
|
+
require 'redcloth'
|
12
|
+
require 'actionmailer_with_request'
|
11
13
|
|
12
14
|
# FIXME: get rid of it once custom_fields is a gem
|
13
15
|
require File.dirname(__FILE__) + '/../../vendor/plugins/custom_fields/init.rb'
|
@@ -3,7 +3,15 @@ module Locomotive
|
|
3
3
|
module Drops
|
4
4
|
class Page < Base
|
5
5
|
|
6
|
-
liquid_attributes << :title << :slug
|
6
|
+
# liquid_attributes << :title << :slug
|
7
|
+
|
8
|
+
def title
|
9
|
+
@source.templatized? ? @context['content_instance'].highlighted_field_value : @source.title
|
10
|
+
end
|
11
|
+
|
12
|
+
def slug
|
13
|
+
@source.templatized? ? @source.content_type.slug.singularize : @source.slug
|
14
|
+
end
|
7
15
|
|
8
16
|
def children
|
9
17
|
@children ||= liquify(*@source.children)
|
@@ -29,10 +29,12 @@ module Locomotive
|
|
29
29
|
|
30
30
|
source = context.registers[@site_or_page.to_sym]
|
31
31
|
|
32
|
+
puts "#{@site_or_page.to_sym} / source = #{source.inspect}"
|
33
|
+
|
32
34
|
if source.respond_to?(:name) # site ?
|
33
|
-
source = source.pages.first # start from home page
|
35
|
+
source = source.pages.index.first # start from home page
|
34
36
|
else
|
35
|
-
source = source.parent
|
37
|
+
source = source.parent || source
|
36
38
|
end
|
37
39
|
|
38
40
|
output = %{<ul id="nav">}
|
data/lib/locomotive/render.rb
CHANGED
@@ -23,13 +23,28 @@ module Locomotive
|
|
23
23
|
path.gsub!(/^\//, '')
|
24
24
|
path = 'index' if path.blank?
|
25
25
|
|
26
|
-
if
|
26
|
+
if path != 'index'
|
27
|
+
dirname = File.dirname(path).gsub(/^\.$/, '') # also look for templatized page path
|
28
|
+
path = [path, File.join(dirname, 'content_type_template').gsub(/^\//, '')]
|
29
|
+
end
|
30
|
+
|
31
|
+
# TODO: path is not correctly built + find content instance in order to render a 404 page if not found
|
32
|
+
|
33
|
+
if page = current_site.pages.any_in(:fullpath => [*path]).first
|
27
34
|
if not page.published? and current_admin.nil?
|
28
35
|
page = nil
|
36
|
+
else
|
37
|
+
if page.templatized?
|
38
|
+
@content_instance = page.content_type.contents.where(:_slug => File.basename(path.first)).first
|
39
|
+
|
40
|
+
if @content_instance.nil? # content instance not found
|
41
|
+
page = nil
|
42
|
+
end
|
43
|
+
end
|
29
44
|
end
|
30
45
|
end
|
31
46
|
|
32
|
-
page || current_site.pages.not_found.first
|
47
|
+
page || current_site.pages.not_found.published.first
|
33
48
|
end
|
34
49
|
|
35
50
|
def locomotive_context
|
@@ -43,6 +58,11 @@ module Locomotive
|
|
43
58
|
'current_page' => self.params[:page]
|
44
59
|
}
|
45
60
|
|
61
|
+
if @page.templatized? # add instance from content type
|
62
|
+
assigns['content_instance'] = @content_instance
|
63
|
+
assigns[@page.content_type.slug.singularize] = @content_instance # just here to help to write readable liquid code
|
64
|
+
end
|
65
|
+
|
46
66
|
registers = { :controller => self, :site => current_site, :page => @page }
|
47
67
|
|
48
68
|
::Liquid::Context.new(assigns, registers)
|
Binary file
|
@@ -31,6 +31,19 @@ $(document).ready(function() {
|
|
31
31
|
}
|
32
32
|
});
|
33
33
|
|
34
|
+
// templatized feature
|
35
|
+
|
36
|
+
$.subscribe('toggle.page_templatized.checked', function(event, data) {
|
37
|
+
$('#page_slug_input').hide();
|
38
|
+
$('#page_content_type_id_input').show();
|
39
|
+
}, []);
|
40
|
+
|
41
|
+
$.subscribe('toggle.page_templatized.unchecked', function(event, data) {
|
42
|
+
$('#page_slug_input').show();
|
43
|
+
$('#page_slug').val(makeSlug($('#page_title').val())).addClass('touched');
|
44
|
+
$('#page_content_type_id_input').hide();
|
45
|
+
}, []);
|
46
|
+
|
34
47
|
// automatic slug from page title
|
35
48
|
$('#page_title').keypress(function() {
|
36
49
|
var input = $(this);
|
@@ -0,0 +1,367 @@
|
|
1
|
+
/*
|
2
|
+
* jquery.subscribe.1.1
|
3
|
+
*
|
4
|
+
* Implementation of publish/subcription framework for jQuery
|
5
|
+
* Requires use of jQuery. Tested with jQuery 1.3 and above
|
6
|
+
*
|
7
|
+
*
|
8
|
+
* Copyright (c) 2008 Eric Chijioke (obinna a-t g mail dot c o m)
|
9
|
+
*
|
10
|
+
*
|
11
|
+
* Dual licensed under the MIT and GPL licenses:
|
12
|
+
* http://www.opensource.org/licenses/mit-license.php
|
13
|
+
* http://www.gnu.org/licenses/gpl.html
|
14
|
+
*
|
15
|
+
* Release Notes:
|
16
|
+
*
|
17
|
+
* version 1.1:
|
18
|
+
*
|
19
|
+
* Fixed unexpected behavior which can occur when a script in a embedded page (page loaded in div,tab etc.) subscribes a handler for a topic using
|
20
|
+
* the jQuery subscribe ($.subscribe) or a no-id element but this subscribe plugin is not reloaded within that embedded page (for example, when
|
21
|
+
* script is included in containing page) . In this case, if the embedded page is reloaded without reloading the entire page (and plugin), the
|
22
|
+
* subscription could be made multiple times for the topic, which will call the handler multiple times each time the topic is published.
|
23
|
+
* Code has been added to prevent this when the subscription is made using the non-element subscribe ($.subscribe()), which assures that only one
|
24
|
+
* subscription is made for a topic for a given window/frame. To prevent this from happening for an element subscription ($elem.subscribe()), make
|
25
|
+
* sure that the element has an id attribute.
|
26
|
+
*/
|
27
|
+
|
28
|
+
|
29
|
+
(function($){
|
30
|
+
|
31
|
+
_subscribe_topics = {};
|
32
|
+
_subscribe_handlers = {};
|
33
|
+
|
34
|
+
_subscribe_getDocumentWindow = function(document){
|
35
|
+
|
36
|
+
return document.parentWindow || document.defaultView;
|
37
|
+
};
|
38
|
+
|
39
|
+
$.fn.extend({
|
40
|
+
|
41
|
+
/**
|
42
|
+
* Creates a new topic without any subscribers.
|
43
|
+
* Not usually used explicitly
|
44
|
+
*/
|
45
|
+
createTopic : function(topic) {
|
46
|
+
if(topic && !_subscribe_topics[topic]) {
|
47
|
+
|
48
|
+
_subscribe_topics[topic] = {};
|
49
|
+
_subscribe_topics[topic].objects = {};
|
50
|
+
_subscribe_topics[topic].objects['__noId__'] = [];
|
51
|
+
}
|
52
|
+
|
53
|
+
return this;
|
54
|
+
},
|
55
|
+
|
56
|
+
/**
|
57
|
+
* Destroy an existing topic and unsubscribe all subscribers
|
58
|
+
*/
|
59
|
+
destroyTopic : function(topic) {
|
60
|
+
|
61
|
+
if(topic && _subscribe_topics[topic]) {
|
62
|
+
|
63
|
+
for(i in _subscribe_topics[topic].objects) {
|
64
|
+
|
65
|
+
var object = _subscribe_topics[topic].objects[i];
|
66
|
+
|
67
|
+
if($.isArray(object)) { // handle '__noId__' elements
|
68
|
+
|
69
|
+
if(object.length > 0) {
|
70
|
+
|
71
|
+
for(j in object) {
|
72
|
+
|
73
|
+
object[j].unbind(topic);
|
74
|
+
}
|
75
|
+
}
|
76
|
+
|
77
|
+
} else {
|
78
|
+
|
79
|
+
object.unbind(topic,data);
|
80
|
+
}
|
81
|
+
}
|
82
|
+
}
|
83
|
+
|
84
|
+
delete _subscribe_topics[topic];
|
85
|
+
|
86
|
+
return this;
|
87
|
+
},
|
88
|
+
|
89
|
+
/**
|
90
|
+
* Subscribes an object to particular topic with a handler.
|
91
|
+
* When the topic is published, this handler will be executed.
|
92
|
+
*
|
93
|
+
* Parameters:
|
94
|
+
* -topic- is the string name of the topic
|
95
|
+
* -handler- is a handler function and is of the form function(event, data), in which the 'this' refers to the element itself.
|
96
|
+
* handler can be a function or can be a string referring to a function previously registered using the $.subscribeHandler() function
|
97
|
+
* Note: returning 'false' from the handler will prevent subsequent handlers from being executed on this element during
|
98
|
+
* this call.
|
99
|
+
* -data- (optional) is additional data that is passed to the event handler as event.data when the topic is published
|
100
|
+
*
|
101
|
+
* Note: Unexpected behavior can occur when a script in a embedded page (page loaded in div,tab etc.) subscribes a handler for a topic using
|
102
|
+
* the jQuery subscribe ($.subscribe) or a no-id element but this subscribe plugin is not reloaded within that embedded page (for example, when
|
103
|
+
* script is included in containing page) . In this case, if the embedded page is reloaded without reloading the entire page (and plugin), the
|
104
|
+
* subscription could be made multiple times for the topic, which will call the handler multiple times each time the topic is published.
|
105
|
+
* Code has been added to prevent this when the subscription is made using the non-element subscribe ($.subscribe()), which assures that only one
|
106
|
+
* subscription is made for a topic for a given window/frame. To prevent this from happening for an element subscription ($elem.subscribe()), make
|
107
|
+
* sure that the element has an id attribute.
|
108
|
+
*/
|
109
|
+
subscribe : function(topic, handler, data) {
|
110
|
+
|
111
|
+
if(this[0] && topic && handler) {
|
112
|
+
|
113
|
+
this.createTopic(topic);
|
114
|
+
|
115
|
+
if(this.attr('id')) {
|
116
|
+
|
117
|
+
_subscribe_topics[topic].objects[this.attr('id')] = this;
|
118
|
+
|
119
|
+
} else {
|
120
|
+
|
121
|
+
//do not subscribe the same window/frame document multiple times, this causes unexpected behavior of executing embedded scripts multiple times
|
122
|
+
var noIdObjects = _subscribe_topics[topic].objects['__noId__'];
|
123
|
+
|
124
|
+
if(this[0].nodeType == 9) { //if document is being bound (the case for non-element jQuery subscribing ($.subscribe)
|
125
|
+
|
126
|
+
for ( var index in noIdObjects) {
|
127
|
+
|
128
|
+
var noIdObject = noIdObjects[index];
|
129
|
+
|
130
|
+
if(noIdObject[0].nodeType == 9 && _subscribe_getDocumentWindow(this[0]).frameElement == _subscribe_getDocumentWindow(noIdObject[0]).frameElement ) {
|
131
|
+
|
132
|
+
return this;
|
133
|
+
}
|
134
|
+
}
|
135
|
+
}
|
136
|
+
|
137
|
+
var exists = false;
|
138
|
+
for(var i = 0; i < noIdObjects.length; i++){
|
139
|
+
if(noIdObjects[i] == this){
|
140
|
+
exists = true;
|
141
|
+
break;
|
142
|
+
}
|
143
|
+
}
|
144
|
+
|
145
|
+
if(!exists) {
|
146
|
+
|
147
|
+
_subscribe_topics[topic].objects['__noId__'].push(this);
|
148
|
+
}
|
149
|
+
}
|
150
|
+
|
151
|
+
if(typeof(handler) == 'function') {
|
152
|
+
|
153
|
+
this.bind(topic, data, handler);
|
154
|
+
|
155
|
+
} else if(typeof(handler) == 'string' && typeof(_subscribe_handlers[handler]) == 'function') {
|
156
|
+
|
157
|
+
this.bind(topic, data, _subscribe_handlers[handler]);
|
158
|
+
}
|
159
|
+
}
|
160
|
+
|
161
|
+
return this;
|
162
|
+
},
|
163
|
+
|
164
|
+
/**
|
165
|
+
* Remove a subscription of an element to a topic.
|
166
|
+
* This will unbind stop all handlers from executing on this element when the topic
|
167
|
+
* is published
|
168
|
+
*/
|
169
|
+
unsubscribe : function(topic) {
|
170
|
+
|
171
|
+
if(topic) {
|
172
|
+
|
173
|
+
if(_subscribe_topics[topic]) {
|
174
|
+
|
175
|
+
if(this.attr('id')) {
|
176
|
+
|
177
|
+
var object = _subscribe_topics[topic].objects[this.attr('id')];
|
178
|
+
|
179
|
+
if(object) {
|
180
|
+
|
181
|
+
delete _subscribe_topics[topic].objects[this.attr('id')];
|
182
|
+
}
|
183
|
+
|
184
|
+
} else {
|
185
|
+
|
186
|
+
var noIdObjects = _subscribe_topics[topic].objects['__noId__'];
|
187
|
+
|
188
|
+
for(var i = 0; i < noIdObjects.length; i++){
|
189
|
+
|
190
|
+
if(noIdObjects[i] == this){
|
191
|
+
|
192
|
+
subscribe_topics[topic].objects['__noId__'].splice(index,1);
|
193
|
+
break;
|
194
|
+
}
|
195
|
+
}
|
196
|
+
}
|
197
|
+
}
|
198
|
+
|
199
|
+
this.unbind(topic);
|
200
|
+
}
|
201
|
+
|
202
|
+
return this;
|
203
|
+
},
|
204
|
+
|
205
|
+
/**
|
206
|
+
* Publishes a topic (triggers handlers on all topic subscribers)
|
207
|
+
* This ends up calling any subscribed handlers which are functions of the form function (event, data)
|
208
|
+
* where: event - is a standard jQuery event object
|
209
|
+
* data - is the data parameter that was passed to this publish() method
|
210
|
+
* event.data - is the data parameter passed to the subscribe() function when this published topic was subscribed to
|
211
|
+
* event.target - is the dom element that subscribed to the event (or the document element if $.subscribe() was used)
|
212
|
+
*
|
213
|
+
* Parameters:
|
214
|
+
* -topic- is the string name of the topic
|
215
|
+
* -data- (optional) is additional data that is passed to the event handler 'data' parameter when the topic is published
|
216
|
+
* handler can be a function or can be a string referring to a function previously registered using the $.subscribeHandler() function
|
217
|
+
* -originalEvent- (optional) may be passed in a reference to an event which triggered this publishing. This will be passed as the
|
218
|
+
* 'originalEvent' field of the triggered event which will allow for controlling the propagation of higher level events
|
219
|
+
* from within the topic handler. In other words, this allows one to cancel execution of all subsequent handlers on the originalEvent
|
220
|
+
* for this element by return 'false' from a handler that is subscribed to the topic published here. This can be especially useful
|
221
|
+
* in conjunction with publishOnEvent(), where a topic is published when an event executes (such as a click) and we want our
|
222
|
+
* handler logic prevent additional topics from being published (For example if our topic displays a 'delete confirm' dialog on click and
|
223
|
+
* the user cancels, we may want to prevent subsequent topics bound to the original click event from being published).
|
224
|
+
*/
|
225
|
+
publish : function(topic, data, originalEvent) {
|
226
|
+
|
227
|
+
if(topic) {
|
228
|
+
|
229
|
+
this.createTopic(topic);
|
230
|
+
|
231
|
+
//if an orginal event exists, need to modify the event object to prevent execution of all
|
232
|
+
//other handlers if the result of the handler is false (which calls stopPropagation())
|
233
|
+
|
234
|
+
var subscriberStopPropagation = function(){
|
235
|
+
|
236
|
+
this.isImmediatePropagationStopped = function(){
|
237
|
+
return true;
|
238
|
+
};
|
239
|
+
|
240
|
+
(new $.Event).stopPropagation();
|
241
|
+
|
242
|
+
if(this.originalEvent) {
|
243
|
+
|
244
|
+
this.originalEvent.isImmediatePropagationStopped = function(){
|
245
|
+
return true;
|
246
|
+
};
|
247
|
+
|
248
|
+
this.originalEvent.stopPropagation = subscriberStopPropagation;
|
249
|
+
}
|
250
|
+
}
|
251
|
+
|
252
|
+
var event = jQuery.Event(topic);
|
253
|
+
$.extend(event,{originalEvent: originalEvent, stopPropagation: subscriberStopPropagation});
|
254
|
+
|
255
|
+
for(i in _subscribe_topics[topic].objects) {
|
256
|
+
|
257
|
+
var object = _subscribe_topics[topic].objects[i];
|
258
|
+
|
259
|
+
if($.isArray(object)) { // handle '__noId__' elements (if any)
|
260
|
+
|
261
|
+
if(object.length > 0) {
|
262
|
+
|
263
|
+
for(j in object) {
|
264
|
+
|
265
|
+
object[j].trigger( event,data);
|
266
|
+
}
|
267
|
+
}
|
268
|
+
|
269
|
+
} else {
|
270
|
+
|
271
|
+
object.trigger( event,data);
|
272
|
+
}
|
273
|
+
}
|
274
|
+
|
275
|
+
}
|
276
|
+
|
277
|
+
return this;
|
278
|
+
},
|
279
|
+
|
280
|
+
/**
|
281
|
+
* Binds an objects event handler to a publish call
|
282
|
+
*
|
283
|
+
* Upon the event triggering, this ends up calling any subscribed handlers which are functions of the form function (event, data)
|
284
|
+
* where: event- is a standard jQuery event object
|
285
|
+
* event.data- is the data parameter passed to the subscribe() function when this published topic was subscribed to
|
286
|
+
* data- is the data parameter that was passed to this publishOnEvent() method
|
287
|
+
* Parameters:
|
288
|
+
* -event- is the string name of the event upon which to publish the topic
|
289
|
+
* -topic- is the string name of the topic to publish when the event occurs
|
290
|
+
* -data- (optional) is additional data which will be passed in to the publish() method ant hen available as the second ('data')
|
291
|
+
* parameter to the topic handler
|
292
|
+
*/
|
293
|
+
publishOnEvent : function(event, topic, data) {
|
294
|
+
|
295
|
+
if(event && topic) {
|
296
|
+
|
297
|
+
this.createTopic(topic);
|
298
|
+
|
299
|
+
this.bind(event, data, function (e) {
|
300
|
+
|
301
|
+
$(this).publish(topic, e.data, e);
|
302
|
+
});
|
303
|
+
}
|
304
|
+
|
305
|
+
return this;
|
306
|
+
}
|
307
|
+
});
|
308
|
+
|
309
|
+
/**
|
310
|
+
* Make publish(), createTopic() and destroyTopic() callable without an element context
|
311
|
+
* Often don't need a context to subscribe, publish, create or destroy a topic.
|
312
|
+
* We will call from the document context
|
313
|
+
*/
|
314
|
+
$.extend({
|
315
|
+
|
316
|
+
/**
|
317
|
+
* Subscribe an event handler to a topic without an element context
|
318
|
+
*
|
319
|
+
* Note: Caution about subscribing using same document to topic multiple time (maybe by loading subscribe script multiple times)
|
320
|
+
*
|
321
|
+
*/
|
322
|
+
subscribe : function(topic, handler, data) {
|
323
|
+
return $(window).subscribe(topic, handler, data);
|
324
|
+
|
325
|
+
},
|
326
|
+
|
327
|
+
/**
|
328
|
+
* Unsubscribe an event handler for a topic without an element context
|
329
|
+
*
|
330
|
+
*/
|
331
|
+
unsubscribe : function(topic, handler, data) {
|
332
|
+
|
333
|
+
return $(window).unsubscribe(topic, handler, data);
|
334
|
+
|
335
|
+
},
|
336
|
+
|
337
|
+
/**
|
338
|
+
* Register a handler function which can then be referenced by name when calling subscribe()
|
339
|
+
*/
|
340
|
+
subscribeHandler: function(name, handler) {
|
341
|
+
|
342
|
+
if(name && handler && typeof(handler) == "function") {
|
343
|
+
|
344
|
+
_subscribe_handlers[name] = handler;
|
345
|
+
}
|
346
|
+
|
347
|
+
return $(window);
|
348
|
+
},
|
349
|
+
|
350
|
+
publish: function(topic, data) {
|
351
|
+
|
352
|
+
return $(window).publish(topic,data);
|
353
|
+
},
|
354
|
+
|
355
|
+
createTopic: function(topic) {
|
356
|
+
|
357
|
+
return $(window).createTopic(topic);
|
358
|
+
},
|
359
|
+
|
360
|
+
destroyTopic: function(topic) {
|
361
|
+
|
362
|
+
return $(window).destroyTopic(topic);
|
363
|
+
}
|
364
|
+
|
365
|
+
});
|
366
|
+
|
367
|
+
})(jQuery);
|
@@ -64,6 +64,8 @@
|
|
64
64
|
$(element).parent().css("background-color", settings.off_bg_color);
|
65
65
|
$(element).parent().parent().prev().removeAttr("checked");
|
66
66
|
$(element).removeClass("left").addClass("right");
|
67
|
+
|
68
|
+
$.publish('toggle.' + $(element).parent().parent().prev().attr('id') + '.unchecked', []);
|
67
69
|
});
|
68
70
|
|
69
71
|
}else{
|
@@ -78,6 +80,7 @@
|
|
78
80
|
$(element).parent().parent().prev().attr("checked","checked");
|
79
81
|
$(element).removeClass("right").addClass("left");
|
80
82
|
|
83
|
+
$.publish('toggle.' + $(element).parent().parent().prev().attr('id') + '.checked', []);
|
81
84
|
});
|
82
85
|
|
83
86
|
}
|
@@ -1,7 +1,7 @@
|
|
1
1
|
$(document).ready(function() {
|
2
2
|
|
3
3
|
var defaultValue = $('fieldset.editable-list li.template input[type=text]').val();
|
4
|
-
|
4
|
+
|
5
5
|
/* __ fields ___ */
|
6
6
|
$('fieldset.editable-list li.template input[type=text]').focus(function() {
|
7
7
|
if ($(this).hasClass('void') && $(this).parents('li').hasClass('template'))
|
@@ -32,5 +32,8 @@ $(document).ready(function() {
|
|
32
32
|
e.preventDefault();
|
33
33
|
e.stopPropagation();
|
34
34
|
});
|
35
|
-
|
35
|
+
|
36
|
+
$.subscribe('form.saved.success', function(event, data) {
|
37
|
+
$('#header h1 a').html($('#current_site_name').val());
|
38
|
+
}, []);
|
36
39
|
});
|
@@ -117,7 +117,7 @@ ul.assets li.asset.last {
|
|
117
117
|
margin-right: 0px;
|
118
118
|
}
|
119
119
|
|
120
|
-
ul.assets li.asset h4 { margin: 0px; height: 30px; }
|
120
|
+
ul.assets li.asset h4 { margin: 0px; height: 30px; border-bottom: 1px solid #ccced7; }
|
121
121
|
|
122
122
|
ul.assets li.asset h4 a {
|
123
123
|
position: relative;
|
@@ -205,6 +205,11 @@ div#uploadAssetsInputQueue { display: none; }
|
|
205
205
|
cursor: move;
|
206
206
|
}
|
207
207
|
|
208
|
+
#pages-list ul.folder li.templatized em {
|
209
|
+
background-position: left -62px;
|
210
|
+
cursor: pointer;
|
211
|
+
}
|
212
|
+
|
208
213
|
#pages-list li .toggler {
|
209
214
|
position: absolute;
|
210
215
|
top: 9px;
|
@@ -60,7 +60,7 @@ form.formtastic fieldset.foldable.folded legend span em {
|
|
60
60
|
form.formtastic fieldset.foldable ol {
|
61
61
|
clear: both;
|
62
62
|
width: 100%;
|
63
|
-
overflow: hidden;
|
63
|
+
overflow: hidden;
|
64
64
|
}
|
65
65
|
|
66
66
|
form.formtastic fieldset.foldable.folded ol { display: none; }
|
@@ -80,6 +80,7 @@ form.formtastic fieldset.inputs ol {
|
|
80
80
|
padding-top: 15px;
|
81
81
|
padding-bottom: 5px;
|
82
82
|
background: #ebedf4 url(/images/admin/form/footer.png) no-repeat 0 bottom;
|
83
|
+
border-top: 1px solid #ccced7;
|
83
84
|
}
|
84
85
|
|
85
86
|
@media screen and (-webkit-min-device-pixel-ratio:0) {
|
@@ -2,6 +2,7 @@
|
|
2
2
|
background: #ebedf4 url(/images/admin/form/footer.png) no-repeat 0 bottom;
|
3
3
|
width: 880px;
|
4
4
|
padding: 20px 20px;
|
5
|
+
border-top: 1px solid #ccced7;
|
5
6
|
}
|
6
7
|
|
7
8
|
#page-parts {
|
@@ -12,6 +13,12 @@
|
|
12
13
|
height: 30px;
|
13
14
|
}
|
14
15
|
|
16
|
+
#page-parts .nav {
|
17
|
+
position: relative;
|
18
|
+
top: 1px;
|
19
|
+
z-index: 990;
|
20
|
+
}
|
21
|
+
|
15
22
|
#page-parts .nav a {
|
16
23
|
float: left;
|
17
24
|
display: block;
|
@@ -52,19 +52,19 @@ describe 'Locomotive rendering system' do
|
|
52
52
|
|
53
53
|
it 'should retrieve the index page /' do
|
54
54
|
@controller.request.fullpath = '/'
|
55
|
-
@controller.current_site.pages.expects(:
|
55
|
+
@controller.current_site.pages.expects(:any_in).with({ :fullpath => %w{index} }).returns([@page])
|
56
56
|
@controller.send(:locomotive_page).should_not be_nil
|
57
57
|
end
|
58
58
|
|
59
59
|
it 'should also retrieve the index page (index.html)' do
|
60
60
|
@controller.request.fullpath = '/index.html'
|
61
|
-
@controller.current_site.pages.expects(:
|
61
|
+
@controller.current_site.pages.expects(:any_in).with({ :fullpath => %w{index} }).returns([@page])
|
62
62
|
@controller.send(:locomotive_page).should_not be_nil
|
63
63
|
end
|
64
64
|
|
65
65
|
it 'should retrieve it based on the full path' do
|
66
66
|
@controller.request.fullpath = '/about_us/team.html'
|
67
|
-
@controller.current_site.pages.expects(:
|
67
|
+
@controller.current_site.pages.expects(:any_in).with({ :fullpath => %w{about_us/team about_us/content_type_template} }).returns([@page])
|
68
68
|
@controller.send(:locomotive_page).should_not be_nil
|
69
69
|
end
|
70
70
|
|
@@ -74,6 +74,31 @@ describe 'Locomotive rendering system' do
|
|
74
74
|
@controller.send(:locomotive_page).should be_true
|
75
75
|
end
|
76
76
|
|
77
|
+
context 'templatized page' do
|
78
|
+
|
79
|
+
before(:each) do
|
80
|
+
@content_type = Factory.build(:content_type, :site => nil)
|
81
|
+
@page.templatized = true
|
82
|
+
@page.content_type = @content_type
|
83
|
+
@controller.request.fullpath = '/projects/edeneo.html'
|
84
|
+
@controller.current_site.pages.expects(:any_in).with({ :fullpath => %w{projects/edeneo projects/content_type_template} }).returns([@page])
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'sets the content_instance variable' do
|
88
|
+
@content_type.contents.stubs(:where).returns([42])
|
89
|
+
@controller.send(:locomotive_page).should_not be_nil
|
90
|
+
@controller.instance_variable_get(:@content_instance).should == 42
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'returns the 404 page if the instance does not exist' do
|
94
|
+
@content_type.contents.stubs(:where).returns([])
|
95
|
+
@controller.current_site.pages.expects(:not_found).returns([true])
|
96
|
+
@controller.send(:locomotive_page).should be_true
|
97
|
+
@controller.instance_variable_get(:@content_instance).should be_nil
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
77
102
|
context 'non published page' do
|
78
103
|
|
79
104
|
before(:each) do
|
@@ -83,7 +108,7 @@ describe 'Locomotive rendering system' do
|
|
83
108
|
|
84
109
|
it 'should return the 404 page if the page has not been published yet' do
|
85
110
|
@controller.request.fullpath = '/contact'
|
86
|
-
@controller.current_site.pages.expects(:
|
111
|
+
@controller.current_site.pages.expects(:any_in).with({ :fullpath => %w{contact content_type_template} }).returns([@page])
|
87
112
|
@controller.current_site.pages.expects(:not_found).returns([true])
|
88
113
|
@controller.send(:locomotive_page).should be_true
|
89
114
|
end
|
@@ -91,7 +116,7 @@ describe 'Locomotive rendering system' do
|
|
91
116
|
it 'should not return the 404 page if the page has not been published yet and admin is logged in' do
|
92
117
|
@controller.current_admin = true
|
93
118
|
@controller.request.fullpath = '/contact'
|
94
|
-
@controller.current_site.pages.expects(:
|
119
|
+
@controller.current_site.pages.expects(:any_in).with({ :fullpath => %w{contact content_type_template} }).returns([@page])
|
95
120
|
@controller.send(:locomotive_page).should == @page
|
96
121
|
end
|
97
122
|
|
data/spec/models/page_spec.rb
CHANGED
@@ -333,4 +333,26 @@ describe Page do
|
|
333
333
|
end
|
334
334
|
|
335
335
|
end
|
336
|
+
|
337
|
+
describe 'templatized extension' do
|
338
|
+
|
339
|
+
before(:each) do
|
340
|
+
@page = Factory.build(:page, :site => nil, :templatized => true, :content_type_id => 42)
|
341
|
+
ContentType.stubs(:find).returns(Factory.build(:content_type, :site => nil))
|
342
|
+
end
|
343
|
+
|
344
|
+
it 'is considered as a templatized page' do
|
345
|
+
@page.templatized?.should be_true
|
346
|
+
end
|
347
|
+
|
348
|
+
it 'fills in the slug field' do
|
349
|
+
@page.valid?
|
350
|
+
@page.slug.should == 'content_type_template'
|
351
|
+
end
|
352
|
+
|
353
|
+
it 'does forget to set the content type id' do
|
354
|
+
@page.content_type_id.should == '42'
|
355
|
+
end
|
356
|
+
|
357
|
+
end
|
336
358
|
end
|
metadata
CHANGED
@@ -6,8 +6,8 @@ version: !ruby/object:Gem::Version
|
|
6
6
|
- 0
|
7
7
|
- 0
|
8
8
|
- 3
|
9
|
-
-
|
10
|
-
version: 0.0.3.
|
9
|
+
- 3
|
10
|
+
version: 0.0.3.3
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Didier Lafforgue
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-07-
|
18
|
+
date: 2010-07-16 00:00:00 +02:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -345,6 +345,7 @@ files:
|
|
345
345
|
- app/models/extensions/asset/vignette.rb
|
346
346
|
- app/models/extensions/page/parts.rb
|
347
347
|
- app/models/extensions/page/render.rb
|
348
|
+
- app/models/extensions/page/templatized.rb
|
348
349
|
- app/models/extensions/page/tree.rb
|
349
350
|
- app/models/layout.rb
|
350
351
|
- app/models/liquid_template.rb
|
@@ -812,6 +813,7 @@ files:
|
|
812
813
|
- public/images/admin/plugins/toggle_handle_right-bg.png
|
813
814
|
- public/images/admin/plugins/toggle_shadow-bg.png
|
814
815
|
- public/images/admin/rails.png
|
816
|
+
- public/javascripts/admin/account.js
|
815
817
|
- public/javascripts/admin/application.js
|
816
818
|
- public/javascripts/admin/asset_collections.js
|
817
819
|
- public/javascripts/admin/assets.js
|
@@ -859,6 +861,7 @@ files:
|
|
859
861
|
- public/javascripts/admin/plugins/plupload/plupload.silverlight.xap
|
860
862
|
- public/javascripts/admin/plugins/scrollTo.js
|
861
863
|
- public/javascripts/admin/plugins/shortcut.js
|
864
|
+
- public/javascripts/admin/plugins/subscribe.js
|
862
865
|
- public/javascripts/admin/plugins/tiny_mce/langs/en.js
|
863
866
|
- public/javascripts/admin/plugins/tiny_mce/license.txt
|
864
867
|
- public/javascripts/admin/plugins/tiny_mce/plugins/advhr/css/advhr.css
|