regulate 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/Gemfile.lock +6 -1
- data/LICENSE +1 -1
- data/README.md +78 -2
- data/app/controllers/regulate/admin/pages_controller.rb +23 -0
- data/app/views/regulate/admin/pages/_form.html.erb +11 -4
- data/app/views/regulate/admin/pages/index.html.erb +11 -2
- data/lib/generators/regulate/{install_generator.rb → mount_up_generator.rb} +2 -17
- data/lib/generators/regulate/{views_generator.rb → strap_generator.rb} +16 -7
- data/lib/generators/templates/regulate.rb +19 -0
- data/lib/generators/templates/regulate.yml +2 -2
- data/lib/generators/templates/regulate_admin.js +167 -0
- data/lib/regulate.rb +7 -0
- data/lib/regulate/engine.rb +2 -2
- data/lib/regulate/git.rb +0 -3
- data/lib/regulate/git/interface.rb +1 -1
- data/lib/regulate/git/model/base.rb +54 -29
- data/lib/regulate/version.rb +1 -1
- data/public/javascripts/regulate_admin.js +1 -1
- data/public/javascripts/regulate_index.js +32 -0
- data/regulate.gemspec +1 -0
- data/test/dummy/app/controllers/application_controller.rb +6 -0
- data/test/dummy/app/models/user.rb +20 -0
- data/test/dummy/config/initializers/regulate.rb +14 -0
- data/test/generators/mount_up_generator_test.rb +15 -0
- data/test/generators/strap_generator_test.rb +22 -0
- data/test/models/regulate_git_model_base_test.rb +176 -7
- data/test/{git_test.rb → regulate_git_interface_test.rb} +2 -2
- metadata +44 -28
- data/lib/generators/templates/regulate.css +0 -2
- data/lib/generators/templates/regulate.js +0 -44
- data/test/tmp/config/initializers/regulate.rb +0 -12
- data/test/tmp/config/regulate.yml +0 -10
- data/test/tmp/public/javascripts/regulate.js +0 -49
- data/test/tmp/public/stylesheets/regulate.css +0 -3
data/.gitignore
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
regulate (0.0
|
4
|
+
regulate (0.1.0)
|
5
|
+
abstract_auth (~> 0.1.0)
|
5
6
|
grit (~> 2.3.0)
|
6
7
|
rails (~> 3.0.0)
|
7
8
|
|
@@ -9,6 +10,8 @@ GEM
|
|
9
10
|
remote: http://rubygems.org/
|
10
11
|
specs:
|
11
12
|
abstract (1.0.0)
|
13
|
+
abstract_auth (0.1.0)
|
14
|
+
module_ext (~> 0.1.0)
|
12
15
|
actionmailer (3.0.3)
|
13
16
|
actionpack (= 3.0.3)
|
14
17
|
mail (~> 2.2.9)
|
@@ -67,6 +70,7 @@ GEM
|
|
67
70
|
mime-types (~> 1.16)
|
68
71
|
treetop (~> 1.4.8)
|
69
72
|
mime-types (1.16)
|
73
|
+
module_ext (0.1.0)
|
70
74
|
nokogiri (1.4.4)
|
71
75
|
polyglot (0.3.1)
|
72
76
|
rack (1.2.1)
|
@@ -107,6 +111,7 @@ PLATFORMS
|
|
107
111
|
ruby
|
108
112
|
|
109
113
|
DEPENDENCIES
|
114
|
+
abstract_auth (~> 0.1.0)
|
110
115
|
bluecloth (~> 2.0.9)
|
111
116
|
bundler (~> 1.0.0)
|
112
117
|
capybara (~> 0.4.0)
|
data/LICENSE
CHANGED
data/README.md
CHANGED
@@ -1,3 +1,79 @@
|
|
1
|
-
|
1
|
+
Regulate
|
2
|
+
===========
|
3
|
+
Rails 3 engine that provides a Git backed CMS that allows for an admin to define editable regions in a page view.
|
4
|
+
|
5
|
+
|
6
|
+
### Why
|
7
|
+
We often have applications that need fairly basic CMS solutions but the
|
8
|
+
client may not have someone on staff that knows HTML. We needed a CMS
|
9
|
+
that wasn't bloated, lived as an engine, allowed us to regulate what
|
10
|
+
parts of a page could be editable, and we wanted it to be Git backed so
|
11
|
+
we could have version control over the edits. We also designed regulate
|
12
|
+
to work with any authentication / authorization service.
|
13
|
+
|
14
|
+
## How it works
|
15
|
+
Video coming soon!
|
16
|
+
|
17
|
+
### Quick Start
|
18
|
+
|
19
|
+
Add regulate to your Rails 3 Gemfile
|
20
|
+
|
21
|
+
gem "regulate"
|
22
|
+
|
23
|
+
bundle install
|
24
|
+
|
25
|
+
Install the initializer
|
26
|
+
|
27
|
+
rails g regulate:mount_up
|
28
|
+
|
29
|
+
You MUST edit the initializer located in `config/initializers/regulate.rb`. Please read the comments located in the regulate initializer for details on configuring regulate to work with your authentication and authorization systems and for defining custom routes. NOTHING works until you do this!
|
30
|
+
|
31
|
+
|
32
|
+
### Options
|
33
|
+
|
34
|
+
More than likely you will want to customize the views in regulate. To copy the view files, javascript in the admin and the git repo config yaml file simply run...
|
35
|
+
|
36
|
+
rails g regulate:strap
|
37
|
+
|
38
|
+
The `strap` generator will copy the following files into your Rails application like so.
|
39
|
+
|
40
|
+
create app/views/regulate
|
41
|
+
create app/views/regulate/admin/pages/_form.html.haml
|
42
|
+
create app/views/regulate/admin/pages/edit.html.haml
|
43
|
+
create app/views/regulate/admin/pages/index.html.haml
|
44
|
+
create app/views/regulate/admin/pages/new.html.haml
|
45
|
+
create app/views/regulate/pages/show.html.haml
|
46
|
+
create public/javascripts/regulate_admin.js
|
47
|
+
create config/regulate.yml
|
48
|
+
|
49
|
+
|
50
|
+
|
51
|
+
### Issues
|
52
|
+
|
53
|
+
Please report issues to the [Regulate issue tracker](http://github.com/quickleft/regulate/issues/).
|
54
|
+
|
55
|
+
|
56
|
+
### Patches/Pull Requests
|
57
|
+
|
58
|
+
* Fork it.
|
59
|
+
* Add tests for it.
|
60
|
+
* Make your changes.
|
61
|
+
* Commit.
|
62
|
+
* Send a pull request.
|
63
|
+
|
64
|
+
|
65
|
+
### Thanks
|
66
|
+
|
67
|
+
Thanks to mojombo for writing grit and gollum. We use grit to talk to Git and reading the source of gollum was a major help. Thanks to brennandunn for inspiring
|
68
|
+
us with rack-cms. Thanks to josevalim for writing devise, it is an excellent example of a Rails 3 engine. Thanks to Nate Dogg and Warren G for insipiring the name.
|
69
|
+
|
70
|
+
|
71
|
+
### Copyright
|
72
|
+
|
73
|
+
Copyright (c) 2011 Quick Left. We regulate any stealing of his property (actually we don't it's MIT). See LICENSE for details.
|
74
|
+
|
75
|
+
Regulators we regulate any stealing of his property and we damn good too But you can't be any geek off the street, gotta be handy with the steel if you know what I mean, earn your keep!
|
76
|
+
|
77
|
+
|
78
|
+
REGULATORS!!! MOUNT UP!
|
2
79
|
|
3
|
-
The CMS we always wanted.
|
@@ -5,6 +5,12 @@ module Regulate
|
|
5
5
|
|
6
6
|
# Standard CRUD Controller
|
7
7
|
class PagesController < ActionController::Base
|
8
|
+
# Check that a user is authenticated
|
9
|
+
before_filter :is_authorized?
|
10
|
+
# Check that the user is an admin
|
11
|
+
before_filter :is_admin?, :only => [:new, :create, :destroy]
|
12
|
+
# Check that the user is at least an editor
|
13
|
+
before_filter :is_editor?, :not => [:new, :create, :destroy]
|
8
14
|
# Load in our page object based on the ID
|
9
15
|
before_filter :load_page, :only => [:edit,:update,:destroy]
|
10
16
|
|
@@ -23,6 +29,7 @@ module Regulate
|
|
23
29
|
|
24
30
|
# PUT method to persist changes to a Page object
|
25
31
|
def update
|
32
|
+
params[:page].delete(:view) if !@is_admin
|
26
33
|
if @page.update_attributes(params[:page])
|
27
34
|
flash[:notice] = "Successfully updated #{params[:page][:title]}"
|
28
35
|
redirect_to regulate_admin_regulate_pages_path
|
@@ -56,6 +63,22 @@ module Regulate
|
|
56
63
|
|
57
64
|
private
|
58
65
|
|
66
|
+
def is_authorized?
|
67
|
+
@authorized_user = AbstractAuth.invoke(:authorized_user)
|
68
|
+
@is_admin = AbstractAuth.invoke(:is_admin)
|
69
|
+
# Uncomment the following line to test out admin interface
|
70
|
+
#@is_admin = true
|
71
|
+
@is_editor = AbstractAuth.invoke(:is_editor)
|
72
|
+
end
|
73
|
+
|
74
|
+
def is_editor?
|
75
|
+
redirect_to root_path if !@is_editor
|
76
|
+
end
|
77
|
+
|
78
|
+
def is_admin?
|
79
|
+
redirect_to regulate_admin_regulate_pages_path if !@is_admin
|
80
|
+
end
|
81
|
+
|
59
82
|
# Grab a page resource based on the ID passed to the URI
|
60
83
|
def load_page
|
61
84
|
@page = Regulate::Page.find(params[:id])
|
@@ -20,10 +20,17 @@ textarea {height:250px;}
|
|
20
20
|
Title: <%= @page.title %>
|
21
21
|
</div>
|
22
22
|
<% end %>
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
23
|
+
<% if @is_admin %>
|
24
|
+
<div class="form_row">
|
25
|
+
<%= f.label :view %>
|
26
|
+
<%= f.text_area :view %>
|
27
|
+
</div>
|
28
|
+
<% else %>
|
29
|
+
<div class="form_row">
|
30
|
+
View:<br /> <%= @page.view %>
|
31
|
+
<%= f.hidden_field :view %>
|
32
|
+
</div>
|
33
|
+
<% end %>
|
27
34
|
<div id="edit_regions"></div>
|
28
35
|
<div class="form_row">
|
29
36
|
<%= f.submit "Save" %>
|
@@ -1,10 +1,19 @@
|
|
1
1
|
<h2>Pages</h2>
|
2
|
-
|
2
|
+
<% if @is_admin %>
|
3
|
+
<%= link_to "New Page", new_regulate_admin_regulate_page_path %>
|
4
|
+
<% end %>
|
3
5
|
<ul>
|
4
6
|
<% @pages.each do |page| %>
|
5
7
|
<li>
|
6
8
|
<%= page.title %>
|
7
|
-
<%= link_to "Edit" , edit_regulate_admin_regulate_page_path(page
|
9
|
+
<%= link_to "Edit" , edit_regulate_admin_regulate_page_path(page) %>
|
10
|
+
<% if @is_admin %>
|
11
|
+
<%= link_to "Delete" , regulate_admin_regulate_page_path(page), :confirm => 'Are you sure?', :method => :delete, :class => 'delete_link' %>
|
12
|
+
<% end %>
|
8
13
|
</li>
|
9
14
|
<% end %>
|
10
15
|
</ul>
|
16
|
+
<% if @is_admin %>
|
17
|
+
<%= javascript_include_tag "jquery.min.js" %>
|
18
|
+
<%= javascript_include_tag "regulate_index.js" %>
|
19
|
+
<% end %>
|
@@ -4,27 +4,12 @@ module Regulate
|
|
4
4
|
module Generators
|
5
5
|
|
6
6
|
# Standard install that lets a user copy files over so they can override some functionality
|
7
|
-
class
|
7
|
+
class MountUpGenerator < Rails::Generators::Base
|
8
8
|
|
9
9
|
# Tell Rails where to find our templates
|
10
10
|
source_root File.expand_path("../../templates", __FILE__)
|
11
11
|
|
12
|
-
desc "Copies
|
13
|
-
|
14
|
-
# Copy over our CSS
|
15
|
-
def copy_css
|
16
|
-
template "regulate.css", "public/stylesheets/regulate.css"
|
17
|
-
end
|
18
|
-
|
19
|
-
# Copy over our JS
|
20
|
-
def copy_js
|
21
|
-
template "regulate.js", "public/javascripts/regulate.js"
|
22
|
-
end
|
23
|
-
|
24
|
-
# Copy over the repo config YAML file
|
25
|
-
def copy_yml
|
26
|
-
template "regulate.yml", "config/regulate.yml"
|
27
|
-
end
|
12
|
+
desc "Copies the regulate initializer because you can't be any geek off the street."
|
28
13
|
|
29
14
|
# Copy over the Regulate initializer
|
30
15
|
def copy_initializer
|
@@ -3,12 +3,11 @@ module Regulate
|
|
3
3
|
module Generators
|
4
4
|
|
5
5
|
# Class lifted from Devise. Thanks josevalim!
|
6
|
-
class
|
6
|
+
class StrapGenerator < Rails::Generators::Base
|
7
7
|
|
8
8
|
# Tell Rails where our view files are
|
9
|
-
source_root File.expand_path("
|
10
|
-
desc "Copies all Regulate views to your application in app/views/
|
11
|
-
|
9
|
+
source_root File.expand_path("../../../..", __FILE__)
|
10
|
+
desc "Copies all Regulate views, js, and yaml files to your application in app/views/regulate/."
|
12
11
|
class_option :template_engine, :type => :string, :aliases => "-t",
|
13
12
|
:desc => "Template engine for the views. Available options are 'erb' and 'haml'."
|
14
13
|
|
@@ -20,10 +19,20 @@ module Regulate
|
|
20
19
|
verify_haml_version
|
21
20
|
create_and_copy_haml_views
|
22
21
|
else
|
23
|
-
directory "
|
22
|
+
directory "app/views/regulate", "app/views/regulate"
|
24
23
|
end
|
25
24
|
end
|
26
25
|
|
26
|
+
# Copy over our JS
|
27
|
+
def copy_js
|
28
|
+
template "public/javascripts/regulate_admin.js", "public/javascripts/regulate_admin.js"
|
29
|
+
end
|
30
|
+
|
31
|
+
# Copy over the repo config YAML file
|
32
|
+
def copy_yml
|
33
|
+
template "config/regulate.yml", "config/regulate.yml"
|
34
|
+
end
|
35
|
+
|
27
36
|
protected
|
28
37
|
|
29
38
|
# Check whether the current environment has HAML
|
@@ -47,7 +56,7 @@ module Regulate
|
|
47
56
|
# Copy over the HAML files
|
48
57
|
def create_and_copy_haml_views
|
49
58
|
require 'tmpdir'
|
50
|
-
html_root = "#{self.class.source_root}/
|
59
|
+
html_root = "#{self.class.source_root}/app/views/regulate"
|
51
60
|
|
52
61
|
Dir.mktmpdir("regulate-haml.") do |haml_root|
|
53
62
|
Dir["#{html_root}/**/*"].each do |path|
|
@@ -61,7 +70,7 @@ module Regulate
|
|
61
70
|
end
|
62
71
|
end
|
63
72
|
|
64
|
-
directory haml_root, "app/views/
|
73
|
+
directory haml_root, "app/views/regulate"
|
65
74
|
end
|
66
75
|
end
|
67
76
|
|
@@ -11,3 +11,22 @@ Regulate.setup do |config|
|
|
11
11
|
|
12
12
|
end
|
13
13
|
|
14
|
+
# AbstractAuth Implementations
|
15
|
+
# You NEED to set these methods appropriately for Regulate to function
|
16
|
+
# This method should return a the currently authenticated resource
|
17
|
+
#AbstractAuth.implement :authorized_user do
|
18
|
+
#::ApplicationController.send('current_user')
|
19
|
+
#end
|
20
|
+
# This method should return whether the currently authenticated resource is an admin or not
|
21
|
+
# Admins are able to edit the view field on a CMS page and define the editable regions
|
22
|
+
# If you want any authenticated user to be able to have this ability, simply return true in your implementation
|
23
|
+
#AbstractAuth.implement :is_admin do
|
24
|
+
#::ApplicationController.send('current_user').is_admin?
|
25
|
+
#end
|
26
|
+
# This method should return whether the currently authenticated resource is an admin or not
|
27
|
+
# Editors only have the ability to change the content of editable regions on the page
|
28
|
+
# If you want any authenticated user to be able to have this ability, simply return true in your implementation
|
29
|
+
#AbstractAuth.implement :is_editor do
|
30
|
+
#::ApplicationController.send('current_user').is_editor?
|
31
|
+
#end
|
32
|
+
|
@@ -0,0 +1,167 @@
|
|
1
|
+
(function( $ ) {
|
2
|
+
|
3
|
+
var timer,
|
4
|
+
stored_keys = null,
|
5
|
+
|
6
|
+
Regulate = {
|
7
|
+
|
8
|
+
longPoll: function longPoll( force, callback ){
|
9
|
+
var i, text = $('#page_view').val(),
|
10
|
+
|
11
|
+
// This RegEx will catch anything inside the `{{ }}`
|
12
|
+
m = /\{\{(\w|\s)*\}\}/g,
|
13
|
+
// A more generic mustache `{ }` grab
|
14
|
+
clean_key = /[\{,\}]/g,
|
15
|
+
|
16
|
+
keys = [],
|
17
|
+
invalid_keys = [],
|
18
|
+
deleted_keys = [],
|
19
|
+
|
20
|
+
// To import data, we've attached an object to `window.existing_custom_fields`
|
21
|
+
existing_keys = existing_custom_fields || {},
|
22
|
+
|
23
|
+
// Define a blacklist of keys we know we'll never want
|
24
|
+
blacklist = ['{{view}}', '{{title}}'],
|
25
|
+
|
26
|
+
// This assigns our initial match of all keys in the textarea
|
27
|
+
initial_match = text.match( m ) || [],
|
28
|
+
|
29
|
+
// This allows to skip the early return
|
30
|
+
f = force || false;
|
31
|
+
|
32
|
+
// Prevent this core of this from running while someone's typing
|
33
|
+
if( $('#page_view').hasClass('active') && f === false) {
|
34
|
+
// Set the timer agains and return early
|
35
|
+
timer = setTimeout( longPoll , 1e3);
|
36
|
+
return false;
|
37
|
+
}
|
38
|
+
|
39
|
+
// Loop through the keys that our regex grabbed, and determine if they're ok to use
|
40
|
+
$.each( initial_match, function( i, v ) {
|
41
|
+
var invalid = false;
|
42
|
+
|
43
|
+
// Keys can't be in the blacklist, also don't double up the errors
|
44
|
+
if( ~blacklist.indexOf( v ) && !~invalid_keys.indexOf( v ) ) {
|
45
|
+
invalid = true;
|
46
|
+
invalid_keys.push({ 'key':v, 'msg': 'The key '+v+' is reserved.' });
|
47
|
+
}
|
48
|
+
|
49
|
+
// Keys can't contain spaces
|
50
|
+
if( /\s/.test(v) && !~invalid_keys.indexOf( v ) ) {
|
51
|
+
invalid = true;
|
52
|
+
invalid_keys.push({ 'key':v, 'msg': 'The key '+v+' contains a space. It can\'t do that.' });
|
53
|
+
}
|
54
|
+
|
55
|
+
// Keys can't start with a number
|
56
|
+
if( /\{\{\d/.test(v) && !~invalid_keys.indexOf( v ) ) {
|
57
|
+
invalid = true;
|
58
|
+
invalid_keys.push({ 'key':v, 'msg': 'The key '+v+' starts with a number. It can\'t do that.' });
|
59
|
+
}
|
60
|
+
|
61
|
+
// If we're still valid after this, add the key into the keys array
|
62
|
+
if( invalid === false ) {
|
63
|
+
keys.push( v );
|
64
|
+
}
|
65
|
+
});
|
66
|
+
|
67
|
+
// Loops through the stored keys and checks for any deleted keys
|
68
|
+
// stored_key[] - keys[] == any deleted keys
|
69
|
+
if( stored_keys !== null ) {
|
70
|
+
i = stored_keys.length;
|
71
|
+
while(i--){
|
72
|
+
if( ! ~keys.indexOf( stored_keys[i] ) ) {
|
73
|
+
deleted_keys.push( stored_keys[i] );
|
74
|
+
// Remove the label and text area within #edit_regions
|
75
|
+
$('#edit_regions .'+ stored_keys[i].replace( clean_key, "") ).remove();
|
76
|
+
}
|
77
|
+
}
|
78
|
+
}
|
79
|
+
|
80
|
+
// Loop through the valid keys and add labels and textareas for them
|
81
|
+
$.each( keys, function( i, v ) {
|
82
|
+
var new_key = v.replace( clean_key, ""),
|
83
|
+
|
84
|
+
textarea = $("<textarea>", {
|
85
|
+
'name': 'page[edit_regions][' + new_key + ']',
|
86
|
+
'class': new_key,
|
87
|
+
'value': existing_keys[new_key] || 'Default Text for '+v
|
88
|
+
}),
|
89
|
+
|
90
|
+
label = $("<label>", {
|
91
|
+
'for': 'page[edit_regions][' + new_key + ']',
|
92
|
+
'text': new_key.replace('_', " "),
|
93
|
+
'class': new_key
|
94
|
+
});
|
95
|
+
|
96
|
+
if( $('textarea').hasClass( new_key )) {
|
97
|
+
return;
|
98
|
+
}
|
99
|
+
|
100
|
+
// Add our textarea to the end of #edit_regions, and inset it's label before it
|
101
|
+
textarea.appendTo('#edit_regions').before(label);
|
102
|
+
});
|
103
|
+
|
104
|
+
// Make any old errors go away
|
105
|
+
$('#custom_field_errors').empty();
|
106
|
+
|
107
|
+
// Add error messages for an invalid keys
|
108
|
+
$.each( invalid_keys, function( i, v ) {
|
109
|
+
if( v.key !== "{{title}}" ) {
|
110
|
+
$('#custom_field_errors').append(v.msg+"</br>");
|
111
|
+
}
|
112
|
+
});
|
113
|
+
|
114
|
+
// Globally persist our keys for the next pass
|
115
|
+
stored_keys = keys;
|
116
|
+
|
117
|
+
// Set a 1 second timer
|
118
|
+
timer = setTimeout( longPoll , 1e3);
|
119
|
+
|
120
|
+
if( $.isFunction( callback ) ){
|
121
|
+
callback();
|
122
|
+
}
|
123
|
+
},
|
124
|
+
|
125
|
+
init: function(){
|
126
|
+
var buffer;
|
127
|
+
|
128
|
+
// Start the party with checking the form's inital value
|
129
|
+
Regulate.longPoll();
|
130
|
+
|
131
|
+
// Forces the longPoll to run one more time before you submit the form
|
132
|
+
$('form').bind('submit', function( e ){
|
133
|
+
e.preventDefault();
|
134
|
+
|
135
|
+
var f = e.target;
|
136
|
+
|
137
|
+
clearTimeout( timer );
|
138
|
+
|
139
|
+
Regulate.longPoll( null, function(){
|
140
|
+
$(f).unbind('submit').submit();
|
141
|
+
});
|
142
|
+
|
143
|
+
});
|
144
|
+
|
145
|
+
// Runs the longPoll if `}}` is entered into the form
|
146
|
+
$(window).bind('keyup', function(e) {
|
147
|
+
if( e.keyCode == 221 && buffer == 221 ) {
|
148
|
+
Regulate.longPoll( true );
|
149
|
+
}
|
150
|
+
|
151
|
+
buffer = e.keyCode;
|
152
|
+
});
|
153
|
+
|
154
|
+
// Prevents the longPoll from running if you're focused on the textarea
|
155
|
+
$('#page_view').bind('focus blur', function(e){
|
156
|
+
$(this).toggleClass('active');
|
157
|
+
});
|
158
|
+
}
|
159
|
+
|
160
|
+
};
|
161
|
+
|
162
|
+
$( Regulate.init );
|
163
|
+
|
164
|
+
window.Regulate = Regulate;
|
165
|
+
|
166
|
+
})(jQuery);
|
167
|
+
|