trusty-snippets-extension 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +7 -0
- data/Rakefile +109 -0
- data/app/controllers/admin/snippets_controller.rb +8 -0
- data/app/helpers/admin/snippets_helper.rb +3 -0
- data/app/models/snippet.rb +23 -0
- data/app/models/snippet_finder.rb +24 -0
- data/app/models/snippet_tags.rb +80 -0
- data/app/views/admin/snippets/_form.html.haml +29 -0
- data/app/views/admin/snippets/_popups.html.haml +4 -0
- data/app/views/admin/snippets/edit.html.haml +11 -0
- data/app/views/admin/snippets/index.html.haml +33 -0
- data/app/views/admin/snippets/new.html.haml +9 -0
- data/app/views/admin/snippets/remove.html.haml +17 -0
- data/config/initializers/radiant_config.rb +3 -0
- data/config/locales/en.yml +16 -0
- data/config/routes.rb +8 -0
- data/cucumber.yml +1 -0
- data/features/admin/configuration.feature +9 -0
- data/features/admin/pagination.feature +27 -0
- data/features/admin/snippets_management.feature +82 -0
- data/features/step_definitions/admin/snippet_steps.rb +25 -0
- data/features/support/env.rb +11 -0
- data/features/support/paths.rb +22 -0
- data/lib/snippets/engine.rb +5 -0
- data/lib/tasks/snippets_extension_tasks.rake +47 -0
- data/lib/trusty-snippets-extension.rb +8 -0
- data/snippets_extension.rb +69 -0
- data/spec/ci/before_script +22 -0
- data/spec/ci/script +2 -0
- data/spec/controllers/admin/snippets_controller_spec.rb +110 -0
- data/spec/datasets/snippets_dataset.rb +44 -0
- data/spec/fixtures/snippet_template.radius +1 -0
- data/spec/lib/radiant/admin_ui_spec.rb +33 -0
- data/spec/models/page_spec.rb +81 -0
- data/spec/models/snippet_finder_spec.rb +20 -0
- data/spec/models/snippet_spec.rb +59 -0
- data/spec/models/user_action_observer_spec.rb +25 -0
- data/spec/snippets_spec_helper.rb +36 -0
- data/spec/spec.opts +6 -0
- data/trusty-snippets-extension.gemspec +26 -0
- metadata +120 -0
@@ -0,0 +1,82 @@
|
|
1
|
+
Feature: Managing snippets
|
2
|
+
In order to share content between layouts and pages, as a designer I want to
|
3
|
+
manage a collection of snippets
|
4
|
+
|
5
|
+
Background:
|
6
|
+
Given I am logged in as "designer"
|
7
|
+
|
8
|
+
Scenario: List snippets
|
9
|
+
When I follow "Design" within "#navigation"
|
10
|
+
And I follow "Snippets"
|
11
|
+
Then I should see "first"
|
12
|
+
And I should see "another"
|
13
|
+
And I should see "markdown"
|
14
|
+
# And a host of others
|
15
|
+
|
16
|
+
Scenario: Create a snippet
|
17
|
+
When I follow "Design" within "#navigation"
|
18
|
+
And I follow "Snippets"
|
19
|
+
And I follow "New Snippet"
|
20
|
+
And I fill in "Name" with "Mine"
|
21
|
+
And I fill in "Body" with "My snippet"
|
22
|
+
And I press "Create Snippet"
|
23
|
+
Then I should be on the snippets list
|
24
|
+
And I should see "Mine"
|
25
|
+
|
26
|
+
Scenario: Display form errors
|
27
|
+
When I follow "Design" within "#navigation"
|
28
|
+
And I follow "Snippets"
|
29
|
+
And I follow "New Snippet"
|
30
|
+
And I fill in "Body" with "My snippet"
|
31
|
+
And I press "Create Snippet"
|
32
|
+
Then I should see an error message
|
33
|
+
And I should see the form
|
34
|
+
|
35
|
+
Scenario: Continue editing
|
36
|
+
When I follow "Design" within "#navigation"
|
37
|
+
And I follow "Snippets"
|
38
|
+
And I follow "New Snippet"
|
39
|
+
And I fill in "Name" with "Mine"
|
40
|
+
And I fill in "Body" with "My snippet"
|
41
|
+
And I press "Save and Continue Editing"
|
42
|
+
Then I should see "Edit Snippet"
|
43
|
+
And I should see the form
|
44
|
+
|
45
|
+
Scenario: View a snippet
|
46
|
+
When I view a filtered snippet
|
47
|
+
Then I should see "Edit Snippet"
|
48
|
+
And I should see "Markdown"
|
49
|
+
|
50
|
+
Scenario: Delete a snippet with confirmation
|
51
|
+
When I follow "Design" within "#navigation"
|
52
|
+
And I follow "Snippets"
|
53
|
+
And I follow "Remove"
|
54
|
+
Then I should see "permanently remove"
|
55
|
+
And I should see "another"
|
56
|
+
When I press "Delete Snippet"
|
57
|
+
Then I should not see "another"
|
58
|
+
|
59
|
+
Scenario Outline: Admins and designers can see and edit snippets
|
60
|
+
Given I am logged in as "<username>"
|
61
|
+
And I should see "Design"
|
62
|
+
When I follow "Design" within "#navigation"
|
63
|
+
And I follow "Snippets"
|
64
|
+
And I should not see "You must have designer privileges"
|
65
|
+
And I follow "first"
|
66
|
+
Then I should see "Edit Snippet"
|
67
|
+
|
68
|
+
Examples:
|
69
|
+
| username |
|
70
|
+
| admin |
|
71
|
+
| designer |
|
72
|
+
|
73
|
+
Scenario Outline: Ordinary users cannot edit snippets
|
74
|
+
Given I am logged in as "<username>"
|
75
|
+
And I should not see "Design"
|
76
|
+
When I go to the "snippets" admin page
|
77
|
+
Then I should see "You must have designer privileges"
|
78
|
+
|
79
|
+
Examples:
|
80
|
+
| username |
|
81
|
+
| existing |
|
82
|
+
| another |
|
@@ -0,0 +1,25 @@
|
|
1
|
+
Given /^There are many snippets$/ do
|
2
|
+
100.times do |i|
|
3
|
+
Snippet.create(:name => "snippet_#{i}", :content => "This is snippet #{i}")
|
4
|
+
end
|
5
|
+
end
|
6
|
+
|
7
|
+
Given /^There are few snippets$/ do
|
8
|
+
#
|
9
|
+
end
|
10
|
+
|
11
|
+
Then /^I should see all the snippets$/ do
|
12
|
+
Snippet.all.each do |snippet|
|
13
|
+
response.body.should have_tag('tr.snippet') do
|
14
|
+
with_tag("a", :text => snippet.name)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
When /^I view a snippet$/ do
|
20
|
+
visit "/admin/snippets/#{snippets(:first).id}"
|
21
|
+
end
|
22
|
+
|
23
|
+
When /^I view a filtered snippet$/ do
|
24
|
+
visit "/admin/snippets/#{snippets(:markdown).id}"
|
25
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# Sets up the Rails environment for Cucumber
|
2
|
+
ENV["RAILS_ENV"] = "test"
|
3
|
+
# Extension root
|
4
|
+
extension_env = File.expand_path(File.dirname(__FILE__) + '/../../../../../config/environment')
|
5
|
+
require extension_env+'.rb'
|
6
|
+
|
7
|
+
Dir.glob(File.join(RADIANT_ROOT, "features", "**", "*.rb")).each {|step| require step unless step =~ /datasets_loader\.rb$/}
|
8
|
+
|
9
|
+
Cucumber::Rails::World.class_eval do
|
10
|
+
dataset :snippets
|
11
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module NavigationHelpers
|
2
|
+
|
3
|
+
# Extend the standard PathMatchers with your own paths
|
4
|
+
# to be used in your features.
|
5
|
+
#
|
6
|
+
# The keys and values here may be used in your standard web steps
|
7
|
+
# Using:
|
8
|
+
#
|
9
|
+
# When I go to the "snippets" admin page
|
10
|
+
#
|
11
|
+
# would direct the request to the path you provide in the value:
|
12
|
+
#
|
13
|
+
# admin_snippets_path
|
14
|
+
#
|
15
|
+
PathMatchers = {} unless defined?(PathMatchers)
|
16
|
+
PathMatchers.merge!({
|
17
|
+
/snippets/i => 'admin_snippets_path'
|
18
|
+
})
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
World(NavigationHelpers)
|
@@ -0,0 +1,47 @@
|
|
1
|
+
namespace :trusty do
|
2
|
+
namespace :extensions do
|
3
|
+
namespace :snippets do
|
4
|
+
|
5
|
+
desc "Runs the migration of the Snippets extension"
|
6
|
+
task :migrate => :environment do
|
7
|
+
require 'trusty_cms/extension_migrator'
|
8
|
+
if ENV["VERSION"]
|
9
|
+
SnippetsExtension.migrator.migrate(ENV["VERSION"].to_i)
|
10
|
+
Rake::Task['db:schema:dump'].invoke
|
11
|
+
else
|
12
|
+
SnippetsExtension.migrator.migrate
|
13
|
+
Rake::Task['db:schema:dump'].invoke
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
desc "Copies public assets of the Snippets to the instance public/ directory."
|
18
|
+
task :update => :environment do
|
19
|
+
is_svn_or_dir = proc {|path| path =~ /\.svn/ || File.directory?(path) }
|
20
|
+
puts "Copying assets from SnippetsExtension"
|
21
|
+
Dir[SnippetsExtension.root + "/public/**/*"].reject(&is_svn_or_dir).each do |file|
|
22
|
+
path = file.sub(SnippetsExtension.root, '')
|
23
|
+
directory = File.dirname(path)
|
24
|
+
mkdir_p RAILS_ROOT + directory, :verbose => false
|
25
|
+
cp file, RAILS_ROOT + path, :verbose => false
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
desc "Syncs all available translations for this ext to the English ext master"
|
30
|
+
task :sync => :environment do
|
31
|
+
# The main translation root, basically where English is kept
|
32
|
+
language_root = SnippetsExtension.root + "/config/locales"
|
33
|
+
words = TranslationSupport.get_translation_keys(language_root)
|
34
|
+
|
35
|
+
Dir["#{language_root}/*.yml"].each do |filename|
|
36
|
+
next if filename.match('_available_tags')
|
37
|
+
basename = File.basename(filename, '.yml')
|
38
|
+
puts "Syncing #{basename}"
|
39
|
+
(comments, other) = TranslationSupport.read_file(filename, basename)
|
40
|
+
words.each { |k,v| other[k] ||= words[k] } # Initializing hash variable as empty if it does not exist
|
41
|
+
other.delete_if { |k,v| !words[k] } # Remove if not defined in en.yml
|
42
|
+
TranslationSupport.write_file(filename, basename, comments, other)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
module TrustySnippetsExtension
|
2
|
+
VERSION = "1.0.0"
|
3
|
+
SUMMARY = "Snippets for Trusty CMS"
|
4
|
+
DESCRIPTION = "Makes Trusty better by adding snippets!"
|
5
|
+
URL = "http://github.com/pgharts/trusty-snippets-extension"
|
6
|
+
AUTHORS = ["Jim Gay", "Eric Sipple"]
|
7
|
+
EMAIL = ["sipple@trustarts.org"]
|
8
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# Uncomment this if you reference any of your controllers in activate
|
2
|
+
# require_dependency "application_controller"
|
3
|
+
require "trusty-snippets-extension"
|
4
|
+
|
5
|
+
class SnippetsExtension < TrustyCms::Extension
|
6
|
+
version TrustySnippetsExtension::VERSION
|
7
|
+
description TrustySnippetsExtension::DESCRIPTION
|
8
|
+
url TrustySnippetsExtension::URL
|
9
|
+
|
10
|
+
def activate
|
11
|
+
|
12
|
+
if defined?(Radiant::Exporter)
|
13
|
+
TrustyCms::Exporter.exportable_models << Snippet
|
14
|
+
TrustyCms::Exporter.template_models << Snippet
|
15
|
+
end
|
16
|
+
|
17
|
+
Page.class_eval do
|
18
|
+
include SnippetTags
|
19
|
+
end
|
20
|
+
|
21
|
+
TrustyCms::AdminUI.class_eval do
|
22
|
+
attr_accessor :snippet
|
23
|
+
|
24
|
+
alias_method :snippets, :snippet
|
25
|
+
|
26
|
+
def load_default_snippet_regions
|
27
|
+
OpenStruct.new.tap do |snippet|
|
28
|
+
snippet.edit = TrustyCms::AdminUI::RegionSet.new do |edit|
|
29
|
+
edit.main.concat %w{edit_header edit_form}
|
30
|
+
edit.form.concat %w{edit_title edit_content edit_filter}
|
31
|
+
edit.form_bottom.concat %w{edit_buttons edit_timestamp}
|
32
|
+
end
|
33
|
+
snippet.index = TrustyCms::AdminUI::RegionSet.new do |index|
|
34
|
+
index.top.concat %w{}
|
35
|
+
index.thead.concat %w{title_header actions_header}
|
36
|
+
index.tbody.concat %w{title_cell actions_cell}
|
37
|
+
index.bottom.concat %w{new_button}
|
38
|
+
end
|
39
|
+
snippet.new = snippet.edit
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def load_default_snippet_file_regions
|
44
|
+
OpenStruct.new.tap do |snippet|
|
45
|
+
snippet.show = TrustyCms::AdminUI::RegionSet.new do |edit|
|
46
|
+
edit.main.concat %w{ header }
|
47
|
+
edit.display_content.concat %w{ title content }
|
48
|
+
edit.bottom.concat %w{ timestamp }
|
49
|
+
end
|
50
|
+
snippet.index = TrustyCms::AdminUI::RegionSet.new do |index|
|
51
|
+
index.top.concat %w{}
|
52
|
+
index.thead.concat %w{title_header}
|
53
|
+
index.tbody.concat %w{title_cell}
|
54
|
+
index.bottom.concat %w{new_button}
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
admin.snippet ||= TrustyCms::AdminUI.load_default_snippet_regions
|
61
|
+
|
62
|
+
UserActionObserver.instance.send :add_observer!, ::Snippet
|
63
|
+
|
64
|
+
tab 'Design' do
|
65
|
+
add_item "Snippets", "/admin/snippets"
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
git clone git://github.com/radiant/radiant.git test_app
|
2
|
+
cd test_app
|
3
|
+
|
4
|
+
if [[ $RADIANT_VERSION != "master" ]]
|
5
|
+
then
|
6
|
+
git checkout -t origin/$RADIANT_VERSION
|
7
|
+
fi
|
8
|
+
gem install bundler --pre
|
9
|
+
echo 'gemspec' >> Gemfile
|
10
|
+
echo 'gem "radiant-snippets-extension", :path => ".."' >> Gemfile
|
11
|
+
bundle install
|
12
|
+
|
13
|
+
case $DB in
|
14
|
+
"mysql" )
|
15
|
+
mysql -e 'create database radiant_test;'
|
16
|
+
cp spec/ci/database.mysql.yml config/database.yml;;
|
17
|
+
"postgres" )
|
18
|
+
psql -c 'create database radiant_test;' -U postgres
|
19
|
+
cp spec/ci/database.postgresql.yml config/database.yml;;
|
20
|
+
esac
|
21
|
+
|
22
|
+
bundle exec rake db:migrate
|
data/spec/ci/script
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../snippets_spec_helper.rb')
|
2
|
+
|
3
|
+
describe Admin::SnippetsController do
|
4
|
+
dataset :users, :snippets
|
5
|
+
|
6
|
+
before :each do
|
7
|
+
ActionController::Routing::Routes.reload
|
8
|
+
login_as :designer
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should be an ResourceController" do
|
12
|
+
controller.should be_kind_of(Admin::ResourceController)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should handle Snippets" do
|
16
|
+
controller.class.model_class.should == Snippet
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
describe "show" do
|
21
|
+
it "should redirect to the edit action" do
|
22
|
+
get :show, :id => 1
|
23
|
+
response.should redirect_to(edit_admin_snippet_path(params[:id]))
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should show xml when format is xml" do
|
27
|
+
snippet = Snippet.first
|
28
|
+
get :show, :id => snippet.id, :format => "xml"
|
29
|
+
response.body.should == snippet.to_xml
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "with invalid snippet id" do
|
34
|
+
[:edit, :remove].each do |action|
|
35
|
+
before do
|
36
|
+
@parameters = {:id => 999}
|
37
|
+
end
|
38
|
+
it "should redirect the #{action} action to the index action" do
|
39
|
+
get action, @parameters
|
40
|
+
response.should redirect_to(admin_snippets_path)
|
41
|
+
end
|
42
|
+
it "should say that the 'Snippet could not be found.' after the #{action} action" do
|
43
|
+
get action, @parameters
|
44
|
+
flash[:notice].should == 'Snippet could not be found.'
|
45
|
+
end
|
46
|
+
end
|
47
|
+
it 'should redirect the update action to the index action' do
|
48
|
+
put :update, @parameters
|
49
|
+
response.should redirect_to(admin_snippets_path)
|
50
|
+
end
|
51
|
+
it "should say that the 'Snippet could not be found.' after the update action" do
|
52
|
+
put :update, @parameters
|
53
|
+
flash[:notice].should == 'Snippet could not be found.'
|
54
|
+
end
|
55
|
+
it 'should redirect the destroy action to the index action' do
|
56
|
+
delete :destroy, @parameters
|
57
|
+
response.should redirect_to(admin_snippets_path)
|
58
|
+
end
|
59
|
+
it "should say that the 'Snippet could not be found.' after the destroy action" do
|
60
|
+
delete :destroy, @parameters
|
61
|
+
flash[:notice].should == 'Snippet could not be found.'
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
{:get => [:index, :show, :new, :edit, :remove],
|
66
|
+
:post => [:create],
|
67
|
+
:put => [:update],
|
68
|
+
:delete => [:destroy]}.each do |method, actions|
|
69
|
+
actions.each do |action|
|
70
|
+
it "should require login to access the #{action} action" do
|
71
|
+
logout
|
72
|
+
lambda { send(method, action, :id => snippet_id(:first)) }.should require_login
|
73
|
+
end
|
74
|
+
|
75
|
+
if action == :show
|
76
|
+
it "should request authentication for API access on show" do
|
77
|
+
logout
|
78
|
+
send(method, action, :id => snippet_id(:first), :format => "xml")
|
79
|
+
response.response_code.should == 401
|
80
|
+
end
|
81
|
+
else
|
82
|
+
it "should allow access to designers for the #{action} action" do
|
83
|
+
lambda {
|
84
|
+
send(method, action, :id => snippet_id(:first))
|
85
|
+
}.should restrict_access(:allow => [users(:designer)],
|
86
|
+
:url => '/admin/pages')
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should allow access to admins for the #{action} action" do
|
90
|
+
lambda {
|
91
|
+
send(method, action, :id => snippet_id(:first))
|
92
|
+
}.should restrict_access(:allow => [users(:designer)],
|
93
|
+
:url => '/admin/pages')
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should deny non-designers and non-admins for the #{action} action" do
|
97
|
+
lambda {
|
98
|
+
send(method, action, :id => Snippet.first.id)
|
99
|
+
}.should restrict_access(:deny => [users(:non_admin), users(:existing)],
|
100
|
+
:url => '/admin/pages')
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
it "should clear the page cache when saved" do
|
107
|
+
Radiant::Cache.should_receive(:clear)
|
108
|
+
put :update, :id => snippet_id(:first), :snippet => {:content => "Foobar."}
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
class SnippetMarkdownFilter < TextFilter
|
2
|
+
def filter(text)
|
3
|
+
text + ' for Snippets!'
|
4
|
+
end
|
5
|
+
end
|
6
|
+
|
7
|
+
class SnippetsDataset < Dataset::Base
|
8
|
+
|
9
|
+
def load
|
10
|
+
create_snippet "first", :content => "test"
|
11
|
+
create_snippet "another", :content => "another test"
|
12
|
+
create_snippet "markdown", :filter_id => "Snippet Markdown", :content => "**markdown**"
|
13
|
+
create_snippet "radius", :content => "<r:title />"
|
14
|
+
create_snippet "global_page_cascade", :content => "<r:children:each><r:page:title /> </r:children:each>"
|
15
|
+
create_snippet "recursive", :content => "<r:children:each><r:snippet name='recursive' /></r:children:each><r:title />"
|
16
|
+
create_snippet "yielding", :content => "Before...<r:yield/>...and after"
|
17
|
+
create_snippet "div_wrap", :content => "<div><r:yield/></div>"
|
18
|
+
create_snippet "nested_yields", :content => '<snippet name="div_wrap">above <r:yield/> below</snippet>'
|
19
|
+
create_snippet "yielding_often", :content => '<r:yield/> is <r:yield/>er than <r:yield/>'
|
20
|
+
end
|
21
|
+
|
22
|
+
helpers do
|
23
|
+
def create_snippet(name, attributes={})
|
24
|
+
create_record :snippet, name.symbolize, snippet_params(attributes.reverse_merge(:name => name))
|
25
|
+
end
|
26
|
+
|
27
|
+
def snippet_params(attributes={})
|
28
|
+
name = attributes[:name] || unique_snippet_name
|
29
|
+
{
|
30
|
+
:name => name,
|
31
|
+
:content => "<r:content />"
|
32
|
+
}.merge(attributes)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def unique_snippet_name
|
38
|
+
@@unique_snippet_name_call_count ||= 0
|
39
|
+
@@unique_snippet_name_call_count += 1
|
40
|
+
"snippet-#{@@unique_snippet_name_call_count}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|