diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1c40133 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.sass-cache/ \ No newline at end of file diff --git a/app/controllers/admin/facets_controller.rb b/app/controllers/admin/facets_controller.rb index 46cff9f..2791b22 100644 --- a/app/controllers/admin/facets_controller.rb +++ b/app/controllers/admin/facets_controller.rb @@ -38,5 +38,11 @@ def destroy @page.destroy end + def tokens + @facets = Facet.with_globalize.where("title like ?", "%#{params[:q]}%") + respond_to do |format| + format.json { render :json => @facets } + end + end end end diff --git a/app/controllers/admin/products_controller.rb b/app/controllers/admin/products_controller.rb index ea74e58..0c71f1a 100644 --- a/app/controllers/admin/products_controller.rb +++ b/app/controllers/admin/products_controller.rb @@ -37,5 +37,12 @@ def destroy @page = Product.find params[:id] @page.destroy end + + def tokens + @products = Product.with_globalize.where("title like ? and parent_id is null", "%#{params[:q]}%") + respond_to do |format| + format.json { render :json => @products } + end + end end end diff --git a/app/controllers/pages_controller.rb b/app/controllers/pages_controller.rb new file mode 100644 index 0000000..25d38e2 --- /dev/null +++ b/app/controllers/pages_controller.rb @@ -0,0 +1,43 @@ +class PagesController < ApplicationController + + # This action is usually accessed with the root path, normally '/' + def home + error_404 unless (@page = Page.where(:link_url => '/').first).present? + end + + # This action can be accessed normally, or as nested pages. + # Assuming a page named "mission" that is a child of "about", + # you can access the pages with the following URLs: + # + # GET /pages/about + # GET /about + # + # GET /pages/mission + # GET /about/mission + # + def show + + @page = Page.find("#{params[:path]}/#{params[:id]}".split('/').last) + + if @page.type == "Facet" + @products = @page.products + @facets = @page.children.empty? ? @page.parent.children : @page.children + #@products.map{ |product| product.facets }.flatten.uniq.reject{ |f| facet_ids.include?(f.id) } + # if @facets.empty? + # @products = Sunspot.search(Product){ with(:facet_ids).all_of [facet_ids.first] }.results + # @facets = @products.map{ |product| product.facets }.flatten.uniq + # end + elsif @page.try(:live?) || (refinery_user? && current_user.authorized_plugins.include?("refinery_pages")) + # if the admin wants this to be a "placeholder" page which goes to its first child, go to that instead. + if @page.skip_to_first_child && (first_live_child = @page.children.order('lft ASC').live.first).present? + redirect_to first_live_child.url and return + elsif @page.link_url.present? + redirect_to @page.link_url and return + end + else + error_404 + end + + end + +end diff --git a/app/models/facet.rb b/app/models/facet.rb index 496da5d..3e2c40f 100644 --- a/app/models/facet.rb +++ b/app/models/facet.rb @@ -1,3 +1,16 @@ class Facet < Page has_and_belongs_to_many :products, :join_table => "facets_products", :foreign_key => "facet_id" + attr_accessible :product_tokens, :set + attr_reader :product_tokens + scope :age, where(:set => 'age') + scope :kind, where(:set => 'kind') + scope :interest, where(:set => 'interest') + def product_tokens=(ids) + self.product_ids = ids.split(",") + end + + def as_json(options={}) + { :id => self.id, :name => self.title } + end + end \ No newline at end of file diff --git a/app/models/product.rb b/app/models/product.rb index 432e53b..6dc7a2b 100644 --- a/app/models/product.rb +++ b/app/models/product.rb @@ -1,3 +1,11 @@ class Product < Page has_and_belongs_to_many :facets, :join_table => "facets_products", :foreign_key => "product_id" + attr_accessible :facet_tokens + attr_reader :facet_tokens + def facet_tokens=(ids) + self.facet_ids = ids.split(",") + end + def as_json(options={}) + { :id => self.id, :name => self.title } + end end diff --git a/app/views/admin/pages/_form.html.erb b/app/views/admin/pages/_form.html.erb new file mode 100644 index 0000000..75283e2 --- /dev/null +++ b/app/views/admin/pages/_form.html.erb @@ -0,0 +1,74 @@ +<% content_for :after_javascript_libraries do %> + <%= javascript_include_tag 'jquery.autoresize' %> + <%= javascript_include_tag 'jquery.textchange.min' %> + <%= javascript_include_tag "jquery.tokeninput.js" %> + <%= javascript_include_tag "products-admin" %> +<% end %> +<% content_for :stylesheets do %> + <%= stylesheet_link_tag "token-input" %> + <%= stylesheet_link_tag "catalog" %> +<% end %> + +<% url_opts = action_name == 'edit' ? {:url => admin_page_path(@page.id)} : {} %> +<%= form_for [:admin, @page], url_opts.merge({:as => :page}) do |f| %> + + <%= render :partial => "/shared/admin/error_messages", + :locals => { + :object => @page, + :include_object_name => true + } %> + + <%= render :partial => "locale_picker", + :locals => { + :current_locale => Thread.current[:globalize_locale] + } if ::Refinery.i18n_enabled? %> + +
+ +" + item[this.propertyToSearch] + "
"+settings.searchingText+"
"); + show_dropdown(); + } + } + + function show_dropdown_hint () { + if(settings.hintText) { + dropdown.html(""+settings.hintText+"
"); + show_dropdown(); + } + } + + // Highlight the query part of the search term + function highlight_term(value, term) { + return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "$1"); + } + + function find_value_and_highlight_term(template, value, term) { + return template.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + value + ")(?![^<>]*>)(?![^&;]+;)", "g"), highlight_term(value, term)); + } + + // Populate the results dropdown with some results + function populate_dropdown (query, results) { + if(results && results.length) { + dropdown.empty(); + var dropdown_ul = $(""+settings.noResultsText+"
"); + show_dropdown(); + } + } + } + + // Highlight an item in the results dropdown + function select_dropdown_item (item) { + if(item) { + if(selected_dropdown_item) { + deselect_dropdown_item($(selected_dropdown_item)); + } + + item.addClass(settings.classes.selectedDropdownItem); + selected_dropdown_item = item.get(0); + } + } + + // Remove highlighting from an item in the results dropdown + function deselect_dropdown_item (item) { + item.removeClass(settings.classes.selectedDropdownItem); + selected_dropdown_item = null; + } + + // Do a search and show the "searching" dropdown if the input is longer + // than settings.minChars + function do_search() { + var query = input_box.val().toLowerCase(); + + if(query && query.length) { + if(selected_token) { + deselect_token($(selected_token), POSITION.AFTER); + } + + if(query.length >= settings.minChars) { + show_dropdown_searching(); + clearTimeout(timeout); + + timeout = setTimeout(function(){ + run_search(query); + }, settings.searchDelay); + } else { + hide_dropdown(); + } + } + } + + // Do the actual search + function run_search(query) { + var cache_key = query + computeURL(); + var cached_results = cache.get(cache_key); + if(cached_results) { + populate_dropdown(query, cached_results); + } else { + // Are we doing an ajax search or local data search? + if(settings.url) { + var url = computeURL(); + // Extract exisiting get params + var ajax_params = {}; + ajax_params.data = {}; + if(url.indexOf("?") > -1) { + var parts = url.split("?"); + ajax_params.url = parts[0]; + + var param_array = parts[1].split("&"); + $.each(param_array, function (index, value) { + var kv = value.split("="); + ajax_params.data[kv[0]] = kv[1]; + }); + } else { + ajax_params.url = url; + } + + // Prepare the request + ajax_params.data[settings.queryParam] = query; + ajax_params.type = settings.method; + ajax_params.dataType = settings.contentType; + if(settings.crossDomain) { + ajax_params.dataType = "jsonp"; + } + + // Attach the success callback + ajax_params.success = function(results) { + if($.isFunction(settings.onResult)) { + results = settings.onResult.call(hidden_input, results); + } + cache.add(cache_key, settings.jsonContainer ? results[settings.jsonContainer] : results); + + // only populate the dropdown if the results are associated with the active search query + if(input_box.val().toLowerCase() === query) { + populate_dropdown(query, settings.jsonContainer ? results[settings.jsonContainer] : results); + } + }; + + // Make the request + $.ajax(ajax_params); + } else if(settings.local_data) { + // Do the search through local data + var results = $.grep(settings.local_data, function (row) { + return row[settings.propertyToSearch].toLowerCase().indexOf(query.toLowerCase()) > -1; + }); + + if($.isFunction(settings.onResult)) { + results = settings.onResult.call(hidden_input, results); + } + cache.add(cache_key, results); + populate_dropdown(query, results); + } + } + } + + // compute the dynamic URL + function computeURL() { + var url = settings.url; + if(typeof settings.url == 'function') { + url = settings.url.call(); + } + return url; + } +}; + +// Really basic cache for the results +$.TokenList.Cache = function (options) { + var settings = $.extend({ + max_size: 500 + }, options); + + var data = {}; + var size = 0; + + var flush = function () { + data = {}; + size = 0; + }; + + this.add = function (query, results) { + if(size > settings.max_size) { + flush(); + } + + if(!data[query]) { + size += 1; + } + + data[query] = results; + }; + + this.get = function (query) { + return data[query]; + }; +}; +}(jQuery)); diff --git a/public/javascripts/products-admin.js b/public/javascripts/products-admin.js index 80699ca..168932e 100644 --- a/public/javascripts/products-admin.js +++ b/public/javascripts/products-admin.js @@ -9,8 +9,16 @@ function display_new_page_dialog($elem, url, title) { } jQuery(document).ready(function($) { - - - -}); - + $("#page_facet_tokens").each(function() { + $(this).tokenInput("/refinery/facets/tokens.json", { + crossDomain: false, + prePopulate: $(this).data("pre") + }); + }); + $("#page_product_tokens").each(function() { + $(this).tokenInput("/refinery/products/tokens.json", { + crossDomain: false, + prePopulate: $(this).data("pre") + }); + }); +}); \ No newline at end of file diff --git a/public/stylesheets/catalog.css b/public/stylesheets/catalog.css new file mode 100644 index 0000000..9017d49 --- /dev/null +++ b/public/stylesheets/catalog.css @@ -0,0 +1,43 @@ +#switch_locale_picker { + float: right; } + #switch_locale_picker p { + float: left; + margin: 12px 5px 12px 0; } + #switch_locale_picker ul { + float: left; + border: 1px dashed #cccccc; + border-radius: 5px; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + padding: 10px; + margin: 0; } + #switch_locale_picker ul li { + display: block; + margin: 0 5px 0 0; } + #switch_locale_picker ul li a { + display: block; + padding: 2px 5px; + border-radius: 4px; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + background-color: #b3e1fb; + color: white; } + #switch_locale_picker ul li a:hover { + background-color: #22a7f2; + color: white; } + #switch_locale_picker ul li.selected a { + background-color: #f07235; + color: white; } + +.architecture { + padding-bottom: 20px; } + .architecture label { + margin-top: 0; } + +.record .title .preview span.set { + padding: 2px 5px; + border-radius: 4px; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + background-color: #b3ffb3; + color: green; } diff --git a/public/stylesheets/sass/catalog.sass b/public/stylesheets/sass/catalog.sass new file mode 100644 index 0000000..e985bda --- /dev/null +++ b/public/stylesheets/sass/catalog.sass @@ -0,0 +1,47 @@ +@import "shared/border-radius" +$facet_set_color: green +$locale_highlight_color: #22A7F2 + +@mixin border + border: 1px dashed #CCC + +border-radius(5px) + padding: 10px + +// Nicer locale picker in page forms +#switch_locale_picker + float: right + p + float: left + margin: 12px 5px 12px 0 + ul + float: left + +border + margin: 0 + li + display: block + margin: 0 5px 0 0 + a + display: block + padding: 2px 5px + +border-radius(4px) + background-color: lighten($locale_highlight_color, 30%) + color: #fff + &:hover + background-color: $locale_highlight_color + color: #fff + &.selected a + background-color: #f07235 + color: #fff + +// Architecture tabs for Facets and Products +.architecture + padding-bottom: 20px + label + margin-top: 0 + +// Facet sets in Facet index +.record .title .preview span.set + padding: 2px 5px + +border-radius(4px) + background-color: lighten($facet_set_color, 60%) + color: $facet_set_color \ No newline at end of file diff --git a/public/stylesheets/sass/shared/_border-radius.sass b/public/stylesheets/sass/shared/_border-radius.sass new file mode 100644 index 0000000..d26d635 --- /dev/null +++ b/public/stylesheets/sass/shared/_border-radius.sass @@ -0,0 +1,73 @@ +// Border-radius helps to make it easier to round the corners of your HTML elements +// Sample Usage: +// #container +// +border-radius("5px") + +// All corners +@mixin border-radius($radius) + border-radius: $radius + -moz-border-radius: $radius + -webkit-border-radius: $radius + +// Top Right +@mixin border-radius-top-right($radius) + +border-radius-top-right($radius) +// Bottom Right +@mixin border-radius-bottom-right($radius) + +border-radius-bottom-right($radius) +// Bottom Left +@mixin border-radius-bottom-left($radius) + +border-radius-bottom-left($radius) +// Top Left +@mixin border-radius-top-left($radius) + +border-radius-top-left($radius) +// Top +@mixin border-radius-top($radius) + +border-radius-top-left($radius) + +border-radius-top-right($radius) +// Right +@mixin border-radius-right($radius) + +border-radius-top-right($radius) + +border-radius-bottom-right($radius) +// Bottom +@mixin border-radius-bottom($radius) + +border-radius-bottom-right($radius) + +border-radius-bottom-left($radius) +// Left +@mixin border-radius-left($radius) + +border-radius-top-left($radius) + +border-radius-bottom-left($radius) + +// Let's setup the rules so we don't have to repeat ourselves +// These are mixins for this mixin and are re-used above +@mixin border-radius-top-right($radius) + border-top-right-radius: $radius + -moz-border-radius-topright: $radius + -webkit-border-top-right-radius: $radius + +@mixin border-radius-bottom-right($radius) + border-bottom-right-radius: $radius + -moz-border-radius-bottomright: $radius + -webkit-border-bottom-right-radius: $radius + +@mixin border-radius-bottom-left($radius) + border-bottom-left-radius: $radius + -moz-border-radius-bottomleft: $radius + -webkit-border-bottom-left-radius: $radius + +@mixin border-radius-top-left($radius) + border-top-left-radius: $radius + -moz-border-radius-topleft: $radius + -webkit-border-top-left-radius: $radius + +@mixin border-radius-table($radius) + tr:first-child + td:first-child + +border-radius-top-left($radius) + td:last-child + +border-radius-top-right($radius) + tr:last-child + td:first-child + +border-radius-bottom-left($radius) + td:last-child + +border-radius-bottom-right($radius) \ No newline at end of file diff --git a/public/stylesheets/token-input.css b/public/stylesheets/token-input.css new file mode 100755 index 0000000..03bb01c --- /dev/null +++ b/public/stylesheets/token-input.css @@ -0,0 +1,113 @@ +/* Example tokeninput style #1: Token vertical list*/ +ul.token-input-list { + overflow: hidden; + height: auto !important; + height: 1%; + width: 400px; + border: 1px solid #999; + cursor: text; + font-size: 12px; + font-family: Verdana; + z-index: 999; + margin: 0; + padding: 0; + background-color: #fff; + list-style-type: none; + clear: left; +} + +ul.token-input-list li { + list-style-type: none; +} + +ul.token-input-list li input { + border: 0; + width: 350px; + padding: 3px 8px; + background-color: white; + -webkit-appearance: caret; +} + +li.token-input-token { + overflow: hidden; + height: auto !important; + height: 1%; + margin: 3px; + padding: 3px 5px; + background-color: #d0efa0; + color: #000; + font-weight: bold; + cursor: default; + display: block; +} + +li.token-input-token p { + float: left; + padding: 0; + margin: 0; +} + +li.token-input-token span { + float: right; + color: #777; + cursor: pointer; +} + +li.token-input-selected-token { + background-color: #08844e; + color: #fff; +} + +li.token-input-selected-token span { + color: #bbb; +} + +div.token-input-dropdown { + position: absolute; + width: 400px; + background-color: #fff; + overflow: hidden; + border-left: 1px solid #ccc; + border-right: 1px solid #ccc; + border-bottom: 1px solid #ccc; + cursor: default; + font-size: 12px; + font-family: Verdana; + z-index: 1; +} + +div.token-input-dropdown p { + margin: 0; + padding: 5px; + font-weight: bold; + color: #777; +} + +div.token-input-dropdown ul { + margin: 0; + padding: 0; +} + +div.token-input-dropdown ul li { + background-color: #fff; + padding: 3px; + list-style-type: none; +} + +div.token-input-dropdown ul li.token-input-dropdown-item { + background-color: #fafafa; +} + +div.token-input-dropdown ul li.token-input-dropdown-item2 { + background-color: #fff; +} + +div.token-input-dropdown ul li em { + font-weight: bold; + font-style: normal; +} + +div.token-input-dropdown ul li.token-input-selected-dropdown-item { + background-color: #d0efa0; +} +