RESOURCE_TYPE_REGEXP = /^([a-zA-Z][\-+\.a-zA-Z\d]*):/ # The #link_to methods support smart formatting of links in multiple formats. format do # Creates a "link", the meaning of which depends upon the format. In this base # format, the link looks like [text][absolute path] # # @param text [String] optional string associated with link # @param opts [Hash] optional Hash. In simple formats, :path is usually the only key def link_to text=nil, opts={} path = path((opts.delete(:path) || {})) if text && path != text "#{text}[#{path}]" else path end end # link to a different view of the current card # @param view [Symbol,String] # @param text [String] # @param opts [Hash] def link_to_view view, text=nil, opts={} add_to_path opts, view: view unless view == :home link_to text, opts end # link to a card other than the current card. # @param cardish [Integer, Symbol, String, Card] a card identifier # @param text [String] # @param opts [Hash] def link_to_card cardish, text=nil, opts={} add_to_path opts, mark: Card::Name[cardish] link_to text, opts end # link to the "related" view (handles many card menu items) # @param field_cardish [Integer, Symbol, String, Card] identify the related field # @param text [String] # @param opts [Hash] def link_to_related field_cardish, text=nil, opts={} opts.bury :path, :slot, :items, :nest_name, "+#{Card::Name[field_cardish]}" link_to_view :related, text, opts end # a "resource" is essentially a reference to something that # decko doesn't recognize to be a card. Can be a remote url, # a local url (that decko hasn't parsed) or a local path. # @param resource [String] # @param text [String] # @param opts [Hash] def link_to_resource resource, text=nil, opts={} resource = clean_resource resource, resource_type(resource) link_to text, opts.merge(path: resource) end # smart_link_to is wrapper method for #link_to, #link_to_card, #link_to_view, # #link_to_resource, and #link_to_related. If the opts argument contains # :view, :related, :card, or :resource, it will use the respective method to # render a link. # # This is usually most useful when writing views that generate many different # kinds of links. def smart_link_to text, opts={} if (linktype = %i[view related card resource].find { |key| opts[key] }) send "link_to_#{linktype}", opts.delete(linktype), text, opts else send :link_to, text, opts end end private def resource_type resource case resource when /^https?\:/ then "external-link" when %r{^/} then "internal-link" when /^mailto\:/ then "email-link" when RESOURCE_TYPE_REGEXP then Regexp.last_match(1) + "-link" end end def clean_resource resource, resource_type if resource_type == "internal-link" contextualize_path resource[1..-1] else resource end end def add_to_path opts, new_hash opts[:path] = (opts[:path] || {}).merge new_hash end end public format :html do # in HTML, #link_to renders an anchor tag # it treats opts other than "path" as html opts for that tag, # and it adds special handling of "remote" and "method" opts # (changes them into data attributes) def link_to text=nil, opts={} opts[:href] ||= path opts.delete(:path) text = raw(text || opts[:href]) interpret_data_opts_to_link_to opts wrap_with :a, text, opts end # in HTML, #link_to_card adds special css classes indicated whether a # card is "known" (real or virtual) or "wanted" (unknown) # TODO: upgrade from (known/wanted)-card to (real/virtual/unknown)-card def link_to_card cardish, text=nil, opts={} name = Card::Name[cardish] add_known_or_wanted_class opts, name super name, (text || name), opts end # in HTML, #link_to_view defaults to a remote link with rel="nofollow". def link_to_view view, text=nil, opts={} opts.reverse_merge! remote: true, rel: "nofollow" super view, (text || view), opts end # in HTML, #link_to_resource automatically adds a target to external resources # so they will open in another tab. It also adds css classes indicating whether # the resource is internal or external def link_to_resource resource, text=nil, opts={} add_resource_opts opts, resource_type(resource) super end # in HTML, #link_to_related defaults to using the field name as text def link_to_related field_cardish, text=nil, opts={} name = Card::Name[field_cardish] super name, (text || name), opts end private def add_known_or_wanted_class opts, name known = opts.delete :known known = Card.known?(name) if known.nil? add_class opts, (known ? "known-card" : "wanted-card") end def interpret_data_opts_to_link_to opts %i[remote method].each do |key| next unless (val = opts.delete key) opts["data-#{key}"] = val end end def add_resource_opts opts, resource_type opts[:target] ||= "_blank" if resource_type == "external-link" add_class opts, resource_type end end