locomotive_cms 0.0.3.1 → 0.0.3.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|