module Radius module LibraryTags include Radiant::Taggable include TaggableHelper class TagError < StandardError; end ############### tags for use on library pages # usually to build a faceted browser tag "library" do |tag| raise TagError, "library:* tags can only be used on a LibraryPage" unless tag.locals.page && tag.locals.page.is_a?(LibraryPage) tag.expand end desc %{ Displays a list of the tags that can be used to narrow the set of results displayed. This begins as a list of all available tags, and as they are chosen it shrinks to show only the coincident tags that will further reduce the set. This is normally used to display a list or cloud of facets that can be added to a search.

        
  • To show only those tags attached to a particular kind of object, supply a 'for' parameter. The parameter can be 'pages', 'assets' or the plural of any asset type. If you're displaying an image gallery, you may want to start with a cloud of all the tags that have been applied to images:
    
            
          
    You can still display pages associated with those tags, but the list will not include tags that only have pages. } tag "library:tags" do |tag| tag.locals.tags = _get_coincident_tags(tag).sort tag.expand end tag "library:tags:each" do |tag| tag.render('each_tag', tag.attr.dup, &tag.block) end tag "library:tags:list" do |tag| tag.render('tag_list', tag.attr.dup) end tag "library:tags:cloud" do |tag| tag.render('tag_cloud', tag.attr.dup) end desc %{ Expands if there are is more than one tag to show. *Usage:*
    
            
              Displaying items tagged with all of 
            
          
    } tag "library:if_tags" do |tag| tag.locals.tags = _get_coincident_tags(tag).sort tag.expand if tag.locals.tags.length > 1 end desc %{ Expands if are is one or no tag to show. *Usage:*
    
            That's your lot.
          
    } tag "library:unless_tags" do |tag| tag.locals.tags = _get_coincident_tags(tag).sort tag.expand if tag.locals.tags.length > 1 end desc %{ Displays a list of the tags requested by the user. To offer links that remove the tag from the current set, these will both work: *Usage:*
    
            
  • } tag "library:requested_tags" do |tag| tag.locals.tags = _get_requested_tags(tag).sort if tag.double? tag.expand else tag.render('tags:unlink_list', tag.attr.dup) end end tag "library:requested_tags:each" do |tag| tag.render('each_tag', tag.attr.dup, &tag.block) end desc %{ Expands if any tags have been specified: *Usage:*
    
            
              Displaying items tagged with all of 
            
          
    } tag "library:if_requested_tags" do |tag| tag.locals.tags = _get_requested_tags(tag).sort tag.expand if tag.locals.tags.any? end desc %{ Expands if no tags have been specified: *Usage:*
    
            
              Showing everything. Choose a tag to start narrowing down the list.
            
          
    } tag "library:unless_requested_tags" do |tag| tag.expand unless _get_requested_tags(tag).any? end desc %{ Displays a list of the pages associated with the current tag set. If no tags are specified, this will show all pages. You can use all the usual r:page tags within the list. :by, :order, :limit, :offset, :status and all the usual list-control attributes are obeyed. To paginate the list, set paginated="true" and, optionally, per_page="xx". *Usage:*
    
            

  • } tag "library:pages" do |tag| tag.locals.pages = _get_pages(tag) tag.expand end tag "library:pages:each" do |tag| tag.render('page_list', tag.attr.dup, &tag.block) # r:page_list is defined in taggable end desc %{ Expands if there are any pages associated with all of the current tag set. *Usage:*
    
            

    Pages

    ...
    } tag "library:if_pages" do |tag| tag.expand if _get_pages(tag).any? end desc %{ Displays a list of the assets associated with the current tag set. If no tags are specified, this will show all assets. You can use all the usual r:assets tags within the list. By, order, limit and offset attributes are obeyed. The default is to sort by creation date, descending. To paginate the list, set paginated="true" and, optionally, per_page="xx". *Usage:*
    
            
  • } tag "library:assets" do |tag| tag.expand end tag "library:assets:each" do |tag| tag.locals.assets = _get_assets(tag) tag.render('asset_list', tag.attr.dup, &tag.block) end desc %{ Expands if there are any assets associated with all of the current tag set. *Usage:*
    
            

    Assets

    ...
    } tag "library:if_assets" do |tag| tag.expand if _get_assets(tag).any? end Asset.known_types.each do |type| these = type.to_s.pluralize desc %{ Displays a list of the all the #{these} associated with the current tag set. If no tags are specified, this will show all such assets. You can use all the usual r:assets tags within the list. *Usage:*
    
              
  • } tag "library:#{these}" do |tag| tag.expand end tag "library:#{these}:each" do |tag| tag.locals.assets = _get_assets(tag).send(these.to_sym) tag.render('asset_list', tag.attr.dup, &tag.block) end desc %{ Expands if there are any #{these} associated with all of the current tag set. *Usage:*
    
              

    #{these.titlecase}

    ...
    } tag "library:if_#{these}" do |tag| tag.locals.assets = _get_assets(tag).send(these.to_sym) tag.expand if tag.locals.assets.any? end end ############### extra tags:* tags that only make sense on library or other faceting pages desc %{ Makes a link that adds the current tag to the active set. Other options are passed through as usual. If the present page is not a library page, or similar, the link will be directed to the first available library page. You can override this behavior by specifying a 'base' parameter, which will force that path prefix regardless of what page it designates, if any. Remote urls are also possible. *Usage:*
    } tag 'tag:link' do |tag| options = tag.attr.dup options['class'] ||= 'facet' anchor = options['anchor'] ? "##{options.delete('anchor')}" : '' attributes = options.inject(' ') { |s, (k, v)| s << %{#{k.downcase}="#{v}" } }.strip text = tag.double? ? tag.expand : tag.render('tag:name') if tag.locals.page.respond_to?(:requested_tags) href = tag.locals.page.path(tag.locals.page.requested_tags + [tag.locals.tag]) elsif base_path = options.delete('base') href = clean_path(base_path + '/-' + tag.locals.tag.clean_title) elsif library = LibraryPage.first href = library.path([tag.locals.tag]) else raise TagError "cannot find a LibraryPage to link to." end %{#{text}} end desc %{ Summarises in a sentence the list of attached, with each one presented as a faceting link. } tag 'tags:link_list' do |tag| _get_requested_tags(tag).map { |t| tag.locals.tag = t tag.render('tag:link', tag.attr.dup) }.join(' | ') end desc %{ Makes a link that removes the current tag from the active set. Other options as for tag:link. This only really makes sense in the context of a requested_tags list, since no other tag can be unlinked. *Usage:*
    } tag 'tag:unlink' do |tag| raise TagError "unlinking a tag requires a library page or similar" unless tag.locals.page.respond_to?(:requested_tags) options = tag.attr.dup options['class'] ||= 'defacet' anchor = options['anchor'] ? "##{options.delete('anchor')}" : '' attributes = options.inject(' ') { |s, (k, v)| s << %{#{k.downcase}="#{v}" } }.strip text = tag.double? ? tag.expand : tag.render('tag:name') href = tag.locals.page.path(tag.locals.page.requested_tags - [tag.locals.tag]) %{#{text}} end desc %{ Summarises in a sentence the list of tags currently active, with each one presented as a defaceting link. } tag 'tags:unlink_list' do |tag| requested = _get_requested_tags(tag) if requested.any? requested.map { |t| tag.locals.tag = t tag.render('tag:unlink', tag.attr.dup) }.to_sentence else "" end end private def _get_requested_tags(tag) tag.locals.page.requested_tags end def _get_coincident_tags(tag) requested = _get_requested_tags(tag) limit = tag.attr['limit'] || 50 if requested.any? Tag.coincident_with(requested) else Tag.most_popular(limit) end end # a bit of extra logic so that in the absence of any requested tags we default to all, not none def _default_library_find_options { :by => 'created_at', :order => 'desc' } end def _get_pages(tag) options = children_find_options(tag) requested = _get_requested_tags(tag) pages = Page.scoped(options) pages = pages.tagged_with(requested) if requested.any? pages end def _get_assets(tag) options = asset_find_options(tag) requested = _get_requested_tags(tag) assets = Asset.scoped(options) assets = assets.tagged_with(requested) if requested.any? assets end # duplicate of children_find_options except: # no virtual or status options # defaults to chronological descending def asset_find_options(tag) attr = tag.attr.symbolize_keys options = {} [:limit, :offset].each do |symbol| if number = attr[symbol] if number =~ /^\d{1,4}$/ options[symbol] = number.to_i else raise TagError.new("`#{symbol}' attribute of `each' tag must be a positive number between 1 and 4 digits") end end end by = (attr[:by] || 'created_at').strip order = (attr[:order] || 'desc').strip order_string = '' if self.attributes.keys.include?(by) order_string << by else raise TagError.new("`by' attribute of `each' tag must be set to a valid field name") end if order =~ /^(asc|desc)$/i order_string << " #{$1.upcase}" else raise TagError.new(%{`order' attribute of `each' tag must be set to either "asc" or "desc"}) end options[:order] = order_string options end end end