radiant-library-extension 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,112 @@
1
+ # Library
2
+
3
+ This is a gallery/library displayer. It combines [paperclipped](http://github.com/spanner/paperclipped) (for images and documents) and [taggable](http://github.com/spanner/radiant-taggable-extension) to offer faceted tag-based retrieval of everything through a single page. I use it for image libraries, document downloads, and on big sites just for page-retrieval. Anything that is tagged can be offered through this interface.
4
+
5
+ ## Status
6
+
7
+ Fairly mature and in use in the world. I've just rationalised the radius tags to support what has emerged as the likely uses, so the interface is now reliable.
8
+
9
+ We're now using built-in pagination and gem-based configuration, so radiant 0.9 is required.
10
+
11
+ The requirement for our fork of paperclipped should soon disappear.
12
+
13
+ ## Requirements
14
+
15
+ * Radiant 0.9
16
+ * [paperclipped](http://github.com/spanner/paperclipped) (currently, requires our fork) and [taggable](http://github.com/spanner/radiant-taggable-extension) extensions
17
+
18
+ ## Installation
19
+
20
+ As usual:
21
+
22
+ ./script/extension install library
23
+
24
+ or some variation on:
25
+
26
+ git submodule add git://github.com/spanner/radiant-library-extension.git vendor/extensions/library
27
+ rake radiant:extensions:library:update
28
+ rake radiant:extensions:library:migrate
29
+
30
+ ## Configuration
31
+
32
+ You need to make sure that paperclipped and taggable load before this does, and anything that adds content types to paperclipped. This is the most awkward sequence I've had to use:
33
+
34
+ config.extensions = [ :share_layouts, :sites, :taggable, :reader, :reader_group, :paperclipped, :paperclipped_gps, :all, :library ]
35
+
36
+ ## Library pages
37
+
38
+ The **LibraryPage** page type is a handy cache-friendly way of catching tag parameters and displaying lists of related items: any path following the address of the page is taken as a slash-separated list of tags, so with a tag page at /archive you can call addresses like:
39
+
40
+ /archive/lasagne/chips/pudding
41
+
42
+ and the right tags will be retrieved, if they exist.
43
+
44
+ ## Examples
45
+
46
+ This will display a faceted image browser on a library page:
47
+
48
+ <r:library:if_requested_tags>
49
+ <p>Displaying pictures tagged with <r:library:requested_tags /></p>
50
+ </r:library:if_requested_tags>
51
+
52
+ <r:library:images:each paginated="true" per_page="20">
53
+ <r:assets:link size="full"><r:assets:image size="small" /></r:assets:link>
54
+ </r:library:images:each>
55
+
56
+ <r:library:tags for="images" />
57
+
58
+ And this will automate the illustration of any page based on tag-overlap:
59
+
60
+ <r:related_images:each limit="3">
61
+ <r:assets:image size="standard" />
62
+ <p class="caption"><r:assets:caption /></p>
63
+ </r:related_images:each>
64
+
65
+ ## Radius tags
66
+
67
+ This extension used to define a ridiculous confusion of tags, so I have slimmed it ruthlessly to make likely uses easy rather than to make all uses possible.
68
+
69
+ ### Library page tags
70
+
71
+ The library tags now focus on two tasks: choosing a set of tags and displaying a set of matching objects.
72
+
73
+ <r:library:tags />
74
+ <r:library:tags:each>...</r:library:tags:each>
75
+
76
+ Displays a list of the tags available. If any tags have been requested, this will show the list of coincident tags (that can be used to limit the result set further). If not it shows all the available tags. If a `for` attribute is set:
77
+
78
+ <r:library:tags for="images" />
79
+ <r:library:tags for="pages" />
80
+
81
+ Then we show only the set of tags attached to any object of that kind.
82
+
83
+ <r:library:requested_tags />
84
+ <r:library:requested_tags:each>...</r:library:requested_tags:each>
85
+
86
+ Displays the currently-limiting set of tags.
87
+
88
+ <r:library:pages:each>...</r:library:pages:each>
89
+ <r:library:assets:each>...</r:library:assets:each>
90
+ <r:library:images:each>...</r:library:images:each>
91
+ <r:library:videos:each>...</r:library:videos:each>
92
+
93
+ Display the list of (that kind of) objects associated with the current tag set.
94
+
95
+ ### Tag assets and asset tags
96
+
97
+ All the page tags have asset equivalents:
98
+
99
+ <r:tags:assets:each tags="foo, bar">...</r:tags:assets:each>
100
+ <r:related_assets:each>...</r:related_assets:each>
101
+
102
+ and for any `*asset*` tag you can substitute an asset type, so this also works:
103
+
104
+ <r:related_images:each>...</r:related_images:each>
105
+
106
+ Within these lists you can use all the usual page and asset tags.
107
+
108
+ ## Author and copyright
109
+
110
+ * William Ross, for spanner. will at spanner.org
111
+ * Copyright 2007-2010 spanner ltd
112
+ * released under the same terms as Rails and/or Radiant
data/Rakefile ADDED
@@ -0,0 +1,137 @@
1
+ begin
2
+ require 'jeweler'
3
+ Jeweler::Tasks.new do |gem|
4
+ gem.name = "radiant-library-extension"
5
+ gem.summary = %Q{Library Extension for Radiant CMS}
6
+ gem.description = %Q{Combines paperclipped and taggable to create a general purpose faceted library}
7
+ gem.email = "will@spanner.org"
8
+ gem.homepage = "http://github.com/spanner/radiant-library-extension"
9
+ gem.authors = ["spanner"]
10
+ gem.add_dependency "radiant", ">= 0.9.0"
11
+ gem.add_dependency "radiant-taggable-extension", ">= 1.2.0"
12
+ end
13
+ rescue LoadError
14
+ puts "Jeweler (or a dependency) not available. This is only required if you plan to package library as a gem."
15
+ end
16
+
17
+ # In rails 1.2, plugins aren't available in the path until they're loaded.
18
+ # Check to see if the rspec plugin is installed first and require
19
+ # it if it is. If not, use the gem version.
20
+
21
+ # Determine where the RSpec plugin is by loading the boot
22
+ unless defined? RADIANT_ROOT
23
+ ENV["RAILS_ENV"] = "test"
24
+ case
25
+ when ENV["RADIANT_ENV_FILE"]
26
+ require File.dirname(ENV["RADIANT_ENV_FILE"]) + "/boot"
27
+ when File.dirname(__FILE__) =~ %r{vendor/radiant/vendor/extensions}
28
+ require "#{File.expand_path(File.dirname(__FILE__) + "/../../../../../")}/config/boot"
29
+ else
30
+ require "#{File.expand_path(File.dirname(__FILE__) + "/../../../")}/config/boot"
31
+ end
32
+ end
33
+
34
+ require 'rake'
35
+ require 'rake/rdoctask'
36
+ require 'rake/testtask'
37
+
38
+ rspec_base = File.expand_path(RADIANT_ROOT + '/vendor/plugins/rspec/lib')
39
+ $LOAD_PATH.unshift(rspec_base) if File.exist?(rspec_base)
40
+ require 'spec/rake/spectask'
41
+ require 'cucumber'
42
+ require 'cucumber/rake/task'
43
+
44
+ # Cleanup the RADIANT_ROOT constant so specs will load the environment
45
+ Object.send(:remove_const, :RADIANT_ROOT)
46
+
47
+ extension_root = File.expand_path(File.dirname(__FILE__))
48
+
49
+ task :default => :spec
50
+ task :stats => "spec:statsetup"
51
+
52
+ desc "Run all specs in spec directory"
53
+ Spec::Rake::SpecTask.new(:spec) do |t|
54
+ t.spec_opts = ['--options', "\"#{extension_root}/spec/spec.opts\""]
55
+ t.spec_files = FileList['spec/**/*_spec.rb']
56
+ end
57
+
58
+ task :features => 'spec:integration'
59
+
60
+ namespace :spec do
61
+ desc "Run all specs in spec directory with RCov"
62
+ Spec::Rake::SpecTask.new(:rcov) do |t|
63
+ t.spec_opts = ['--options', "\"#{extension_root}/spec/spec.opts\""]
64
+ t.spec_files = FileList['spec/**/*_spec.rb']
65
+ t.rcov = true
66
+ t.rcov_opts = ['--exclude', 'spec', '--rails']
67
+ end
68
+
69
+ desc "Print Specdoc for all specs"
70
+ Spec::Rake::SpecTask.new(:doc) do |t|
71
+ t.spec_opts = ["--format", "specdoc", "--dry-run"]
72
+ t.spec_files = FileList['spec/**/*_spec.rb']
73
+ end
74
+
75
+ [:models, :controllers, :views, :helpers].each do |sub|
76
+ desc "Run the specs under spec/#{sub}"
77
+ Spec::Rake::SpecTask.new(sub) do |t|
78
+ t.spec_opts = ['--options', "\"#{extension_root}/spec/spec.opts\""]
79
+ t.spec_files = FileList["spec/#{sub}/**/*_spec.rb"]
80
+ end
81
+ end
82
+
83
+ desc "Run the Cucumber features"
84
+ Cucumber::Rake::Task.new(:integration) do |t|
85
+ t.fork = true
86
+ t.cucumber_opts = ['--format', (ENV['CUCUMBER_FORMAT'] || 'pretty')]
87
+ # t.feature_pattern = "#{extension_root}/features/**/*.feature"
88
+ t.profile = "default"
89
+ end
90
+
91
+ # Setup specs for stats
92
+ task :statsetup do
93
+ require 'code_statistics'
94
+ ::STATS_DIRECTORIES << %w(Model\ specs spec/models)
95
+ ::STATS_DIRECTORIES << %w(View\ specs spec/views)
96
+ ::STATS_DIRECTORIES << %w(Controller\ specs spec/controllers)
97
+ ::STATS_DIRECTORIES << %w(Helper\ specs spec/views)
98
+ ::CodeStatistics::TEST_TYPES << "Model specs"
99
+ ::CodeStatistics::TEST_TYPES << "View specs"
100
+ ::CodeStatistics::TEST_TYPES << "Controller specs"
101
+ ::CodeStatistics::TEST_TYPES << "Helper specs"
102
+ ::STATS_DIRECTORIES.delete_if {|a| a[0] =~ /test/}
103
+ end
104
+
105
+ namespace :db do
106
+ namespace :fixtures do
107
+ desc "Load fixtures (from spec/fixtures) into the current environment's database. Load specific fixtures using FIXTURES=x,y"
108
+ task :load => :environment do
109
+ require 'active_record/fixtures'
110
+ ActiveRecord::Base.establish_connection(RAILS_ENV.to_sym)
111
+ (ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/) : Dir.glob(File.join(RAILS_ROOT, 'spec', 'fixtures', '*.{yml,csv}'))).each do |fixture_file|
112
+ Fixtures.create_fixtures('spec/fixtures', File.basename(fixture_file, '.*'))
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
118
+
119
+ desc 'Generate documentation for the library extension.'
120
+ Rake::RDocTask.new(:rdoc) do |rdoc|
121
+ rdoc.rdoc_dir = 'rdoc'
122
+ rdoc.title = 'LibraryExtension'
123
+ rdoc.options << '--line-numbers' << '--inline-source'
124
+ rdoc.rdoc_files.include('README')
125
+ rdoc.rdoc_files.include('lib/**/*.rb')
126
+ end
127
+
128
+ # For extensions that are in transition
129
+ desc 'Test the library extension.'
130
+ Rake::TestTask.new(:test) do |t|
131
+ t.libs << 'lib'
132
+ t.pattern = 'test/**/*_test.rb'
133
+ t.verbose = true
134
+ end
135
+
136
+ # Load any custom rakefiles for extension
137
+ Dir[File.dirname(__FILE__) + '/tasks/*.rake'].sort.each { |f| require f }
@@ -0,0 +1,61 @@
1
+ class LibraryPage < Page
2
+ include Library::LibraryTags
3
+ include WillPaginate::ViewHelpers
4
+
5
+ class RedirectRequired < StandardError
6
+ def initialize(message = nil); super end
7
+ end
8
+
9
+ description %{ Takes tag names in child position or as paramaters so that tagged items can be listed. }
10
+
11
+ attr_accessor :requested_tags, :strict_match
12
+
13
+ def cache?
14
+ true
15
+ end
16
+
17
+ def find_by_url(url, live = true, clean = false)
18
+ url = clean_url(url) if clean
19
+ my_url = self.url
20
+ return false unless url =~ /^#{Regexp.quote(my_url)}(.*)/
21
+ tags = $1.split('/')
22
+ if slug_child = children.find_by_slug(tags[0])
23
+ found = slug_child.find_by_url(url, live, clean)
24
+ return found if found
25
+ end
26
+ remove_tags, add_tags = tags.partition{|t| t.first == '-'}
27
+ add_request_tags(add_tags)
28
+ remove_request_tags(remove_tags)
29
+ self
30
+ end
31
+
32
+ def add_request_tags(tags=[])
33
+ if tags.any?
34
+ tags.collect! { |tag| Tag.find_by_title(Rack::Utils::unescape(tag)) }
35
+ self.requested_tags = (self.requested_tags + tags.select{|t| !t.nil?}).uniq
36
+ end
37
+ end
38
+
39
+ def remove_request_tags(tags=[])
40
+ if tags.any?
41
+ tags.collect! { |tag|
42
+ tag.slice!(0) if tag.first == '-'
43
+ Tag.find_by_title(Rack::Utils::unescape(tag))
44
+ }
45
+ self.requested_tags = (self.requested_tags - tags.select{|t| !t.nil?}).uniq
46
+ end
47
+ end
48
+
49
+ def requested_tags
50
+ @requested_tags ||= []
51
+ end
52
+
53
+ # this isn't very pleasing but it's the best way to let the controller know
54
+ # of our real address once tags have been added and removed.
55
+
56
+ def url_with_tags(tags = requested_tags)
57
+ clean_url( url_without_tags + '/' + tags.uniq.map(&:clean_title).to_param )
58
+ end
59
+ alias_method_chain :url, :tags
60
+
61
+ end
@@ -0,0 +1,20 @@
1
+ - fields_for :asset, @asset do |fields|
2
+ #extended-metadata{ :class => "row", :style => "display: none" }
3
+ %table.fieldset{ :cellpadding => "0", :cellspacing => "0", :border => "0" }
4
+ %tr
5
+ %td.label
6
+ = fields.label :caption
7
+ %td.field
8
+ = fields.text_field :caption, :class => 'textbox', :maxlength => 255
9
+ -if @asset.new_record? || @asset.image?
10
+ %tr
11
+ %td.label
12
+ Display
13
+ %td.field{:style => 'text-align: left;'}
14
+ = fields.check_box :furniture
15
+ = fields.label :furniture, "Site furniture? (not shown in lists and galleries)"
16
+ = render_region :extended_metadata
17
+ %p
18
+ %small
19
+ %a{ :id => "more-extended-metadata", :href => "#", :onclick => "#{toggle_javascript_for('extended-metadata')}; return false;" } More
20
+ %a{ :id => "less-extended-metadata", :href => "#", :onclick => "#{toggle_javascript_for('extended-metadata')}; return false;", :style => "display: none" } Less
@@ -0,0 +1,31 @@
1
+ - content_for :page_css do
2
+ :sass
3
+ div#a_header
4
+ :width 99%
5
+ div#a_title
6
+ :width 70%
7
+ :float left
8
+ div#a_keywords
9
+ :width 28%
10
+ :float right
11
+ div#content
12
+ .form-area
13
+ .keywords
14
+ .textbox
15
+ :font-family Georgia, Palatino, "Times New Roman", Times, serif
16
+ :font-size 200%
17
+ :width 100%
18
+ :color #c00
19
+
20
+ - fields_for :asset, @asset do |fields|
21
+ #a_header
22
+ #a_title
23
+ %p.title
24
+ %label{:for=>"asset_title"}
25
+ Title
26
+ = fields.text_field :title, :class => 'textbox', :maxlength => 100
27
+ #a_keywords
28
+ %p.keywords
29
+ %label{:for=>"asset_keywords"}
30
+ Tags
31
+ = fields.text_field :keywords, :class => 'textbox'
@@ -0,0 +1,22 @@
1
+ - include_stylesheet 'admin/assets'
2
+
3
+ - asset_taggings = @tag.taggings.of_a :asset
4
+ - if asset_taggings.any?
5
+ %h2
6
+ Tagged assets
7
+ %table.index
8
+ - asset_taggings.each do |tagging|
9
+ - asset = tagging.tagged
10
+ - if asset
11
+ - dom_id = "tagging_#{tagging.id}"
12
+ %tr{:id => dom_id}
13
+ %td.asset{:style => 'width: 48px'}
14
+ = link_to image_tag(asset.thumbnail(:icon)), edit_admin_asset_path(asset), :class => 'icon'
15
+ %td.asset-title
16
+ = link_to asset.title, edit_admin_asset_path(asset)
17
+ %td.actions
18
+ = link_to_remote image('minus') + ' detach', |
19
+ :html => { :class => "action", :title => "Detach tag from asset" }, |
20
+ :url => admin_tagging_url(tagging), :method => :delete, |
21
+ :after => "Effect.Fade('#{dom_id}', { duration: 0.5 })", |
22
+ :complete => "Element.remove('#{dom_id}');"
data/cucumber.yml ADDED
@@ -0,0 +1 @@
1
+ default: --format progress features --tags ~@proposed,~@in_progress
@@ -0,0 +1,9 @@
1
+ class Furniture < ActiveRecord::Migration
2
+ def self.up
3
+ add_column :assets, :furniture, :boolean, :default => false
4
+ end
5
+
6
+ def self.down
7
+ remove_column :assets, :furniture
8
+ end
9
+ end
@@ -0,0 +1,16 @@
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}
8
+
9
+ Cucumber::Rails::World.class_eval do
10
+ include Dataset
11
+ datasets_directory "#{RADIANT_ROOT}/spec/datasets"
12
+ Dataset::Resolver.default = Dataset::DirectoryResolver.new("#{RADIANT_ROOT}/spec/datasets", File.dirname(__FILE__) + '/../../spec/datasets', File.dirname(__FILE__) + '/../datasets')
13
+ self.datasets_database_dump_path = "#{Rails.root}/tmp/dataset"
14
+
15
+ # dataset :library
16
+ end
@@ -0,0 +1,14 @@
1
+ def path_to(page_name)
2
+ case page_name
3
+
4
+ when /the homepage/i
5
+ root_path
6
+
7
+ when /login/i
8
+ login_path
9
+ # Add more page name => path mappings here
10
+
11
+ else
12
+ raise "Can't find mapping from \"#{page_name}\" to a path."
13
+ end
14
+ end
@@ -0,0 +1,15 @@
1
+ module Library
2
+ module LibraryTag
3
+
4
+ def assets
5
+ Asset.from_tags([self])
6
+ end
7
+
8
+ Asset.known_types.each do |type|
9
+ define_method type.to_s.pluralize.intern do
10
+ Asset.send("#{type.to_s.pluralize}".intern).from_tags([self])
11
+ end
12
+ end
13
+
14
+ end
15
+ end