1
0
Fork 0
forked from OpenNeo/impress

Merge branch 'closet'

This commit is contained in:
Emi Matchu 2011-07-31 23:05:26 -04:00
commit f6ed50a62f
101 changed files with 4685 additions and 341 deletions

View file

@ -33,6 +33,10 @@ gem 'right_aws', '~> 2.1.0'
gem "character-encodings", "~> 0.4.1", :platforms => :ruby_18
gem "nokogiri", "~> 1.5.0"
gem 'sanitize', '~> 2.0.3'
group :development_async do
# async wrappers
gem 'eventmachine', :git => 'git://github.com/eventmachine/eventmachine.git'

View file

@ -107,6 +107,7 @@ GEM
mime-types (1.16)
msgpack (0.4.4)
mysql2 (0.2.6)
nokogiri (1.5.0)
openneo-auth-signatory (0.1.0)
ruby-hmac
polyglot (0.3.1)
@ -164,9 +165,11 @@ GEM
ruby-hmac (0.4.0)
rufus-scheduler (2.0.9)
tzinfo (>= 0.3.23)
sanitize (2.0.3)
nokogiri (< 1.6, >= 1.4.4)
sinatra (1.2.6)
rack (~> 1.1)
tilt (>= 1.2.2, < 2.0)
tilt (< 2.0, >= 1.2.2)
swf_converter (0.0.3)
thor (0.14.6)
tilt (1.3.2)
@ -204,6 +207,7 @@ DEPENDENCIES
msgpack (~> 0.4.3)
mysql2
mysqlplus!
nokogiri (~> 1.5.0)
openneo-auth-signatory (~> 0.1.0)
rack-fiber_pool
rails (= 3.0.4)
@ -213,6 +217,7 @@ DEPENDENCIES
resque-scheduler (~> 2.0.0.d)
right_aws (~> 2.1.0)
rspec-rails (~> 2.0.0.beta.22)
sanitize (~> 2.0.3)
swf_converter (~> 0.0.3)
whenever (~> 0.6.2)
will_paginate (~> 3.0.pre2)

View file

@ -1,10 +1,34 @@
class ApplicationController < ActionController::Base
protect_from_forgery
helper_method :can_use_image_mode?
helper_method :can_use_image_mode?, :user_is?
def authenticate_user! # too lazy to change references to login_path
redirect_to(login_path) unless user_signed_in?
end
def authorize_user!
raise AccessDenied unless user_signed_in? && current_user.id == params[:user_id].to_i
end
def can_use_image_mode?
user_signed_in? && current_user.image_mode_tester?
end
class AccessDenied < StandardError;end
rescue_from AccessDenied, :with => :on_access_denied
def on_access_denied
render :file => 'public/403.html', :layout => false, :status => :forbidden
end
def redirect_back!(default=:back)
redirect_to(params[:return_to] || default)
end
def user_is?(user)
user_signed_in? && user == current_user
end
end

View file

@ -0,0 +1,146 @@
class ClosetHangersController < ApplicationController
before_filter :authorize_user!, :only => [:destroy, :create, :update, :petpage]
before_filter :find_item, :only => [:destroy, :create, :update]
before_filter :find_user, :only => [:index, :petpage]
def destroy
raise ActiveRecord::RecordNotFound unless params[:closet_hanger]
@closet_hanger = current_user.closet_hangers.find_by_item_id_and_owned!(@item.id, owned)
@closet_hanger.destroy
respond_to do |format|
format.html { redirect_after_destroy! }
format.json { render :json => true }
end
end
def index
@public_perspective = params.has_key?(:public) || !user_is?(@user)
find_closet_hangers!
if @public_perspective && user_signed_in?
items = []
@closet_lists_by_owned.each do |owned, lists|
lists.each do |list|
list.hangers.each { |hanger| items << hanger.item }
end
end
@unlisted_closet_hangers_by_owned.each do |owned, hangers|
hangers.each { |hanger| items << hanger.item }
end
current_user.assign_closeted_to_items!(items)
end
end
def petpage
@public_perspective = true
find_closet_hangers!
end
# Since the user does not care about the idea of a hanger, but rather the
# quantity of an item they own, the user would expect a create form to work
# even after the record already exists, and an update form to work even after
# the record is deleted. So, create and update are aliased, and both find
# the record if it exists or create a new one if it does not. They will even
# delete the record if quantity is zero.
#
# This is kinda a violation of REST. It's not worth breaking user
# expectations, though, and I can't really think of a genuinely RESTful way
# to pull this off.
def update
@closet_hanger = current_user.closet_hangers.find_or_initialize_by_item_id_and_owned(@item.id, owned)
@closet_hanger.attributes = params[:closet_hanger]
unless @closet_hanger.quantity == 0 # save the hanger, new record or not
if @closet_hanger.save
respond_to do |format|
format.html {
message = "Success! You #{@closet_hanger.verb(:you)} #{@closet_hanger.quantity} "
message << ((@closet_hanger.quantity > 1) ? @item.name.pluralize : @item.name)
message << " in the \"#{@closet_hanger.list.name}\" list" if @closet_hanger.list
flash[:success] = "#{message}."
redirect_back!(@item)
}
format.json { render :json => true }
end
else
respond_to do |format|
format.html {
flash[:alert] = "We couldn't save how many of this item you #{@closet_hanger.verb(:you)}: #{@closet_hanger.errors.full_messages.to_sentence}"
redirect_back!(@item)
}
format.json { render :json => {:errors => @closet_hanger.errors.full_messages}, :status => :unprocessable_entity }
end
end
else # delete the hanger since the user doesn't want it
@closet_hanger.destroy
respond_to do |format|
format.html { redirect_after_destroy! }
format.json { render :json => true }
end
end
end
alias_method :create, :update
protected
def find_item
@item = Item.find params[:item_id]
end
def find_user
if params[:user_id]
@user = User.find params[:user_id]
elsif user_signed_in?
redirect_to user_closet_hangers_path(current_user)
else
redirect_to login_path(:return_to => request.fullpath)
end
end
def find_closet_hangers!
@perspective_user = current_user unless @public_perspective
@closet_lists_by_owned = @user.closet_lists.
alphabetical.includes(:hangers => :item)
unless @perspective_user == @user
# If we run this when the user matches, we'll end up with effectively:
# WHERE belongs_to_user AND (is_public OR belongs_to_user)
# and it's a bit silly to put the SQL server through a condition that's
# always true.
@closet_lists_by_owned = @closet_lists_by_owned.visible_to(@perspective_user)
end
@closet_lists_by_owned = @closet_lists_by_owned.group_by(&:hangers_owned)
visible_groups = @user.closet_hangers_groups_visible_to(@perspective_user)
unless visible_groups.empty?
@unlisted_closet_hangers_by_owned = @user.closet_hangers.unlisted.
owned_before_wanted.alphabetical_by_item_name.includes(:item).
where(:owned => [visible_groups]).group_by(&:owned)
else
@unlisted_closet_hangers_by_owned = {}
end
end
def owned
owned = true
if params[:closet_hanger]
owned = case params[:closet_hanger][:owned]
when 'true', '1' then true
when 'false', '0' then false
end
end
end
def redirect_after_destroy!
flash[:success] = "Success! You do not #{@closet_hanger.verb(:you)} #{@item.name}."
redirect_back!(@item)
end
end

View file

@ -0,0 +1,49 @@
class ClosetListsController < ApplicationController
before_filter :authorize_user!
before_filter :find_closet_list, :only => [:edit, :update, :destroy]
def create
@closet_list = current_user.closet_lists.build params[:closet_list]
if @closet_list.save
save_successful!
else
save_failed!
render :action => :new
end
end
def destroy
@closet_list.destroy
flash[:success] = "Successfully deleted \"#{@closet_list.name}\""
redirect_to user_closet_hangers_path(current_user)
end
def new
@closet_list = current_user.closet_lists.build params[:closet_list]
end
def update
if @closet_list.update_attributes(params[:closet_list])
save_successful!
else
save_failed!
render :action => :edit
end
end
protected
def find_closet_list
@closet_list = current_user.closet_lists.find params[:id]
end
def save_failed!
flash.now[:alert] = "We can't save this list because: #{@closet_list.errors.full_messages.to_sentence}"
end
def save_successful!
flash[:success] = "Successfully saved \"#{@closet_list.name}\""
redirect_to user_closet_hangers_path(current_user)
end
end

View file

@ -0,0 +1,67 @@
class ClosetPagesController < ApplicationController
include ActionView::Helpers::TextHelper
before_filter :authenticate_user!, :build_closet_page
rescue_from ClosetPage::ParseError, :with => :on_parse_error
def create
if params[:closet_page] && params[:closet_page][:source]
@closet_page.index = params[:closet_page][:index]
@closet_page.source = params[:closet_page][:source]
saved_counts = @closet_page.save_hangers!
any_created = saved_counts[:created] > 0
any_updated = saved_counts[:updated] > 0
if any_created || any_updated
message = "Page #{@closet_page.index} saved! We "
message << "added " + pluralize(saved_counts[:created], 'item') + " to your closet" if any_created
message << " and " if any_created && any_updated
message << "updated the count on " + pluralize(saved_counts[:updated], 'item') if any_updated
message << ". "
else
message = "Success! We checked that page, and we already had all this data recorded. "
end
unless @closet_page.unknown_item_names.empty?
message << "We also found " +
pluralize(@closet_page.unknown_item_names.size, 'item') +
" we didn't recognize: " +
@closet_page.unknown_item_names.to_sentence +
". Please put each item on your pet and type its name in on the " +
"home page so we can have a record of it. Thanks! "
end
if @closet_page.last?
message << "That was the last page of your Neopets closet."
destination = user_closet_hangers_path(current_user)
else
message << "Now the frame should contain page #{@closet_page.index + 1}. Paste that source code over, too."
destination = {:action => :new, :index => (@closet_page.index + 1)}
end
flash[:success] = message
redirect_to destination
else
redirect_to :action => :new
end
end
def new
@closet_page.index ||= 1
end
protected
def build_closet_page
@closet_page = ClosetPage.new(current_user)
@closet_page.index = params[:index]
end
def on_parse_error
flash[:alert] = "We had trouble reading your source code. Is it a valid HTML document? Make sure you pasted the computery-looking result of clicking View Frame Source, and not the pretty page itself."
render :action => :new
end
end

View file

@ -10,7 +10,8 @@ class ItemsController < ApplicationController
else
per_page = nil
end
@items = Item.search(@query).alphabetize.paginate :page => params[:page], :per_page => per_page
@items = Item.search(@query, current_user).alphabetize.paginate :page => params[:page], :per_page => per_page
assign_closeted!
respond_to do |format|
format.html { render }
format.json { render :json => {:items => @items, :total_pages => @items.total_pages} }
@ -24,6 +25,7 @@ class ItemsController < ApplicationController
end
elsif params.has_key?(:ids) && params[:ids].is_a?(Array)
@items = Item.find(params[:ids])
assign_closeted!
respond_to do |format|
format.json { render :json => @items }
end
@ -37,6 +39,19 @@ class ItemsController < ApplicationController
def show
@item = Item.find params[:id]
@trading_closet_hangers_by_owned = {
true => @item.closet_hangers.owned_trading.newest.includes(:user),
false => @item.closet_hangers.wanted_trading.newest.includes(:user)
}
if user_signed_in?
@current_user_hangers = [true, false].map do |owned|
hanger = current_user.closet_hangers.find_or_initialize_by_item_id_and_owned(@item.id, owned)
hanger.quantity ||= 1
hanger
end
end
end
def needed
@ -50,11 +65,16 @@ class ItemsController < ApplicationController
raise ActiveRecord::RecordNotFound, 'Pet type not found'
end
@items = @pet_type.needed_items.alphabetize
assign_closeted!
@pet_name = params[:name]
render :layout => 'application'
end
private
protected
def assign_closeted!
current_user.assign_closeted_to_items!(@items) if user_signed_in?
end
def set_query
@query = params[:q]

View file

@ -1,5 +1,40 @@
class UsersController < ApplicationController
before_filter :find_and_authorize_user!, :only => [:update]
def top_contributors
@users = User.top_contributors.paginate :page => params[:page], :per_page => 20
end
def update
success = @user.update_attributes params[:user]
respond_to do |format|
format.html {
if success
flash[:success] = "Settings successfully saved"
redirect_back! user_closet_hangers_path(@user)
else
flash[:alert] = "Error saving user settings: #{@user.errors.full_messages.to_sentence}"
end
}
format.json {
if success
render :json => true
else
render :json => {:errors => @user.errors.full_messages}, :status => :unprocessable_entity
end
}
end
end
protected
def find_and_authorize_user!
if current_user.id == params[:id].to_i
@user = current_user
else
raise AccessDenied
end
end
end

View file

@ -29,10 +29,22 @@ module ApplicationHelper
content_tag(:div, html, :class => 'campaign-progress-wrapper')
end
def canonical_path(resource)
content_for :meta, tag(:link, :rel => 'canonical', :href => url_for(resource))
end
def contact_email
"webmaster@openneo.net"
end
def feedback_url
"http://openneo.uservoice.com/forums/40720-dress-to-impress"
end
def flashes
raw(flash.inject('') do |html, pair|
key, value = pair
html + content_tag('p', value, :class => key)
html + content_tag('p', value, :class => "flash #{key}")
end)
end
@ -86,6 +98,15 @@ module ApplicationHelper
hidden_field_tag 'origin', value, :id => nil
end
def return_to_field_tag
hidden_field_tag :return_to, request.fullpath
end
def secondary_nav(&block)
content_for :before_flashes,
content_tag(:nav, :id => 'secondary-nav', &block)
end
def show_title_header?
params[:controller] != 'items'
end

View file

@ -0,0 +1,110 @@
require 'cgi'
module ClosetHangersHelper
def closet_hangers_help_class
'hidden' unless @user.closet_hangers.empty?
end
def closet_hanger_verb(owned, positive=true)
ClosetHanger.verb(closet_hanger_subject, owned, positive)
end
def send_neomail_url(user)
"http://www.neopets.com/neomessages.phtml?type=send&recipient=#{CGI.escape @user.neopets_username}"
end
def closet_hanger_subject
public_perspective? ? @user.name : :you
end
def hangers_group_visibility_field_name(owned)
owned ? :owned_closet_hangers_visibility : :wanted_closet_hangers_visibility
end
def closet_visibility_choices(*args)
ClosetVisibility.levels.map do |level|
[level.send(*args), level.id]
end
end
def closet_visibility_descriptions(subject='these items')
content = ''
ClosetVisibility.levels.each do |level|
content << content_tag(:li, level.description(subject), 'data-id' => level.id)
end
content_tag :ul, content.html_safe, :class => 'visibility-descriptions'
end
# Do we have either unlisted hangers that are owned/wanted, or non-empty
# owned/wanted lists?
def has_hangers?(owned)
# If we have unlisted hangers of this type, pass.
return true if @unlisted_closet_hangers_by_owned.has_key?(owned)
# Additionally, if we have no lists of this type, fail.
lists = @closet_lists_by_owned[owned]
return false unless lists
# If any of those lists are non-empty, pass.
lists.each do |list|
return true unless list.hangers.empty?
end
# Otherwise, all of the lists are empty. Fail.
return false
end
def has_lists?(owned)
@closet_lists_by_owned.has_key?(owned)
end
def link_to_add_closet_list(content, options)
owned = options.delete(:owned)
path = new_user_closet_list_path current_user,
:closet_list => {:hangers_owned => owned}
link_to(content, path, options)
end
def nc_icon_url
"http://#{request.host}#{image_path 'nc.png'}"
end
def petpage_item_name(item)
item.name.gsub(/ on/i, ' o<b></b>n')
end
def public_perspective?
@public_perspective
end
PETPAGE_HANGER_BATCH_SIZE = 5
def render_batched_petpage_hangers(hangers)
output do |html|
hangers.in_groups_of(PETPAGE_HANGER_BATCH_SIZE) do |batch|
content = batch.map do |hanger|
render 'petpage_hanger', :hanger => hanger if hanger
end.join.html_safe
html << content_tag(:div, content, :class => 'dti-item-row')
end
end
end
def render_closet_lists(lists)
if lists
render :partial => 'closet_lists/closet_list', :collection => lists,
:locals => {:show_controls => !public_perspective?}
end
end
def render_unlisted_closet_hangers(owned)
hangers_content = render :partial => 'closet_hanger',
:collection => @unlisted_closet_hangers_by_owned[owned],
:locals => {:show_controls => !public_perspective?}
end
def unlisted_hangers_count(owned)
hangers = @unlisted_closet_hangers_by_owned[owned]
hangers ? hangers.size : 0
end
end

View file

@ -0,0 +1,30 @@
module ClosetListsHelper
def closet_list_delete_confirmation(closet_list)
"Are you sure you want to delete \"#{closet_list.name}\"?".tap do |msg|
unless closet_list.hangers.empty?
msg << " Even if you do, we'll remember that you " +
ClosetHanger.verb(:you, closet_list.hangers_owned) +
" these items."
end
end
end
def closet_list_description_format(list)
md = RDiscount.new(list.description)
Sanitize.clean(md.to_html, Sanitize::Config::BASIC).html_safe
end
def hangers_owned_options
@hangers_owned_options ||= [true, false].map do |owned|
verb = ClosetHanger.verb(:i, owned)
["items I #{verb}", owned]
end
end
def render_sorted_hangers(list, show_controls)
render :partial => 'closet_hanger',
:collection => list.hangers.sort { |x,y| x.item.name <=> y.item.name },
:locals => {:show_controls => show_controls}
end
end

View file

@ -0,0 +1,10 @@
module ClosetPagesHelper
def link_to_neopets_login(content)
link_to content, neopets_login_url, :target => "_blank"
end
def neopets_login_url
"http://www.neopets.com/loginpage.phtml"
end
end

View file

@ -48,6 +48,28 @@ module ItemsHelper
end
end
def closeted_icons_for(item)
content = ''.html_safe
if item.owned?
content << image_tag(
'owned.png',
:title => 'You own this',
:alt => 'Own'
)
end
if item.wanted?
content << image_tag(
'wanted.png',
:title => 'You want this',
:alt => 'Want'
)
end
content_tag :div, content, :class => 'closeted-icons'
end
def list_zones(zones, method=:label)
zones.sort { |x,y| x.label <=> y.label }.map(&method).join(', ')
end
@ -60,6 +82,12 @@ module ItemsHelper
sprintf(NeoitemsURLFormat, CGI::escape(item.name))
end
def render_trading_closet_hangers(owned)
@trading_closet_hangers_by_owned[owned].map do |hanger|
link_to hanger.user.name, user_closet_hangers_path(hanger.user)
end.to_sentence.html_safe
end
private
def build_on_pet_types(species, special_color=nil, &block)

View file

@ -0,0 +1,58 @@
class ClosetHanger < ActiveRecord::Base
belongs_to :item
belongs_to :list, :class_name => 'ClosetList'
belongs_to :user
attr_accessible :list_id, :owned, :quantity
validates :item_id, :uniqueness => {:scope => [:user_id, :owned]}
validates :quantity, :numericality => {:greater_than => 0}
validates_presence_of :item, :user
validate :list_belongs_to_user
scope :alphabetical_by_item_name, joins(:item).order(Item.arel_table[:name])
scope :newest, order(arel_table[:created_at].desc)
scope :owned_before_wanted, order(arel_table[:owned].desc)
scope :unlisted, where(:list_id => nil)
{:owned => true, :wanted => false}.each do |name, owned|
scope "#{name}_trading", joins(:user).includes(:list).
where(:owned => owned).
where((
User.arel_table["#{name}_closet_hangers_visibility"].gteq(ClosetVisibility[:trading].id)
).or(
ClosetList.arel_table[:visibility].gteq(ClosetVisibility[:trading].id)
))
end
before_validation :set_owned_by_list
def verb(subject=:someone)
self.class.verb(subject, owned?)
end
def self.verb(subject, owned, positive=true)
base = (owned) ? 'own' : 'want'
base << 's' if positive && subject != :you && subject != :i
base
end
protected
def list_belongs_to_user
if list_id?
if list
errors.add(:list_id, "must belong to you") unless list.user_id == user_id
else
errors.add(:list, "must exist")
end
end
end
def set_owned_by_list
self.owned = list.hangers_owned if list
true
end
end

31
app/models/closet_list.rb Normal file
View file

@ -0,0 +1,31 @@
class ClosetList < ActiveRecord::Base
belongs_to :user
has_many :hangers, :class_name => 'ClosetHanger', :foreign_key => 'list_id',
:dependent => :nullify
attr_accessible :description, :hangers_owned, :name, :visibility
validates :name, :presence => true, :uniqueness => {:scope => :user_id}
validates :user, :presence => true
validates :hangers_owned, :inclusion => {:in => [true, false], :message => "can't be blank"}
scope :alphabetical, order(:name)
scope :public, where(arel_table[:visibility].gteq(ClosetVisibility[:public].id))
scope :visible_to, lambda { |user|
condition = arel_table[:visibility].gteq(ClosetVisibility[:public].id)
condition = condition.or(arel_table[:user_id].eq(user.id)) if user
where(condition)
}
after_save :sync_hangers_owned!
def sync_hangers_owned!
if hangers_owned_changed?
hangers.each do |hanger|
hanger.owned = hangers_owned
hanger.save!
end
end
end
end

138
app/models/closet_page.rb Normal file
View file

@ -0,0 +1,138 @@
require 'yaml'
class ClosetPage
include ActiveModel::Conversion
extend ActiveModel::Naming
SELECTORS = {
:items => "form[action=\"process_closet.phtml\"] tr[bgcolor!=silver][bgcolor!=\"#E4E4E4\"]",
:item_thumbnail => "img",
:item_name => "td:nth-child(2)",
:item_quantity => "td:nth-child(5)",
:item_remove => "input",
:page_select => "select[name=page]",
:selected => "option[selected]"
}
attr_accessor :index
attr_reader :hangers, :source, :total_pages, :unknown_item_names, :user
def initialize(user)
raise ArgumentError, "Expected #{user.inspect} to be a User", caller unless user.is_a?(User)
@user = user
end
def last?
@index == @total_pages
end
def persisted?
false
end
def save_hangers!
counts = {:created => 0, :updated => 0}
ClosetHanger.transaction do
@hangers.each do |hanger|
if hanger.new_record?
counts[:created] += 1
hanger.save!
elsif hanger.changed?
counts[:updated] += 1
hanger.save!
end
end
end
counts
end
def source=(source)
@source = source
parse_source!(source)
end
def url
"http://www.neopets.com/closet.phtml?per_page=50&page=#{@index}"
end
protected
def element(selector_name, parent)
parent.at_css(SELECTORS[selector_name]) ||
raise(ParseError, "Closet #{selector_name} element not found in #{parent.inspect}")
end
def elements(selector_name, parent)
parent.css(SELECTORS[selector_name])
end
def parse_source!(source)
doc = Nokogiri::HTML(source)
page_selector = element(:page_select, doc)
@total_pages = page_selector.children.size
@index = element(:selected, page_selector)['value'].to_i
items_data = {
:id => {},
:thumbnail_url => {}
}
# Go through the items, and find the ID/thumbnail for each and data with it
elements(:items, doc).each do |row|
# For normal items, the td contains essentially:
# <b>NAME<br/><span>OPTIONAL ADJECTIVE</span></b>
# For PB items, the td contains:
# NAME<br/><span>OPTIONAL ADJECTIVE</span>
# So, we want the first text node. If it's a PB item, that's the first
# child. If it's a normal item, it's the first child <b>'s child.
name_el = element(:item_name, row).children[0]
name_el = name_el.children[0] if name_el.name == 'b'
data = {
:name => name_el.text,
:quantity => element(:item_quantity, row).text.to_i
}
if id = element(:item_remove, row)['name']
id = id.to_i
items_data[:id][id] = data
else # if this is a pb item, which does not give ID, go by thumbnail
thumbnail_url = element(:item_thumbnail, row)['src']
items_data[:thumbnail_url][thumbnail_url] = data
end
end
# Find items with either a matching ID or matching thumbnail URL
# Check out that single-query beauty :)
i = Item.arel_table
items = Item.where(
i[:id].in(items_data[:id].keys).
or(
i[:thumbnail_url].in(items_data[:thumbnail_url].keys)
)
)
# Create closet hanger from each item, and remove them from the reference
# lists
@hangers = items.map do |item|
data = items_data[:id].delete(item.id) ||
items_data[:thumbnail_url].delete(item.thumbnail_url)
hanger = @user.closet_hangers.find_or_initialize_by_item_id(item.id)
hanger.quantity = data[:quantity]
hanger
end
# Take the names of the items remaining in the reference lists, meaning
# that they weren't found
@unknown_item_names = []
items_data.each do |type, data_by_key|
data_by_key.each do |key, data|
@unknown_item_names << data[:name]
end
end
end
class ParseError < RuntimeError;end
end

View file

@ -0,0 +1,58 @@
module ClosetVisibility
class Level
attr_accessor :id, :name
attr_writer :description
def initialize(data)
data.each do |key, value|
send("#{key}=", value)
end
end
def description(subject=nil)
if subject
@description.sub('$SUBJECT', subject).capitalize
else
@description
end
end
def human_name
name.to_s.humanize
end
end
LEVELS = [
Level.new(
:id => 0,
:name => :private,
:description => "Only you can see $SUBJECT"
),
Level.new(
:id => 1,
:name => :public,
:description => "Anyone who visits this page can see $SUBJECT"
),
Level.new(
:id => 2,
:name => :trading,
:description => "$SUBJECT will be publicly listed for trades"
)
]
LEVELS_BY_NAME = {}.tap do |levels_by_name|
LEVELS.each do |level|
levels_by_name[level.id] = level
levels_by_name[level.name] = level
end
end
def self.[](id)
LEVELS_BY_NAME[id]
end
def self.levels
LEVELS
end
end

View file

@ -1,15 +1,18 @@
# requires item sweeper at bottom
class Item < ActiveRecord::Base
include PrettyParam
SwfAssetType = 'object'
has_many :closet_hangers
has_one :contribution, :as => :contributed
has_many :parent_swf_asset_relationships, :foreign_key => 'parent_id',
:conditions => {:swf_asset_type => SwfAssetType}
has_many :swf_assets, :through => :parent_swf_asset_relationships, :source => :object_asset,
:conditions => {:type => SwfAssetType}
attr_writer :current_body_id
attr_writer :current_body_id, :owned, :wanted
NCRarities = [0, 500]
PAINTBRUSH_SET_DESCRIPTION = 'This item is part of a deluxe paint brush set!'
@ -42,12 +45,24 @@ class Item < ActiveRecord::Base
scope :sitemap, select([:id, :name]).order(:id).limit(49999)
# Not defining validations, since this app is currently read-only
scope :with_closet_hangers, joins(:closet_hangers)
def closeted?
@owned || @wanted
end
def nc?
NCRarities.include?(rarity_index)
end
def owned?
@owned
end
def wanted?
@wanted
end
def restricted_zones
unless @restricted_zones
@restricted_zones = []
@ -115,7 +130,7 @@ class Item < ActiveRecord::Base
@supported_species ||= species_support_ids.blank? ? Species.all : species_support_ids.sort.map { |id| Species.find(id) }
end
def self.search(query)
def self.search(query, user=nil)
raise SearchError, "Please provide a search query" unless query
query = query.strip
raise SearchError, "Search queries should be at least 3 characters" if query.length < 3
@ -143,7 +158,7 @@ class Item < ActiveRecord::Base
limited_filters_used << condition.filter
end
end
condition.narrow(scope)
condition.narrow(scope, user)
end
end
@ -154,19 +169,12 @@ class Item < ActiveRecord::Base
:name => name,
:thumbnail_url => thumbnail_url,
:zones_restrict => zones_restrict,
:rarity_index => rarity_index
:rarity_index => rarity_index,
:owned => owned?,
:wanted => wanted?
}
end
URL_CHAR_BLACKLIST = /[^a-z0-9\-]/i
def name_for_url
name.downcase.gsub(' ', '-').gsub(URL_CHAR_BLACKLIST, '')
end
def to_param
"#{id}-#{name_for_url}"
end
before_create do
self.sold_in_mall ||= false
true
@ -631,6 +639,7 @@ class Item < ActiveRecord::Base
name = name.to_s
SearchFilterScopes << name
LimitedSearchFilters << name if options[:limit]
(class << self; self; end).instance_eval do
if options[:full]
define_method "search_filter_#{name}", &options[:full]
@ -648,10 +657,13 @@ class Item < ActiveRecord::Base
search_filter name, options, &block
end
def self.search_filter_block(options, positive)
Proc.new { |str, scope|
condition = yield(str)
condition = "!(#{condition.to_sql})" unless positive
def self.search_filter_block(options, positive, &block)
Proc.new { |str, user, scope|
condition = block.arity == 1 ? block.call(str) : block.call(str, user)
unless positive
condition = condition.to_sql if condition.respond_to?(:to_sql)
condition = "!(#{condition})"
end
scope = scope.send(options[:scope]) if options[:scope]
scope.where(condition)
}
@ -679,6 +691,47 @@ class Item < ActiveRecord::Base
filter
end
USER_ADJECTIVES = {
'own' => true,
'owns' => true,
'owned' => true,
'want' => false,
'wants' => false,
'wanted' => false,
'all' => nil,
'items' => nil
}
def self.parse_user_adjective(adjective, user)
unless USER_ADJECTIVES.has_key?(adjective)
raise SearchError, "We don't understand user:#{adjective}. " +
"Find items you own with user:owns, items you want with user:wants, or " +
"both with user:all"
end
unless user
raise SearchError, "It looks like you're not logged in, so you don't own any items."
end
USER_ADJECTIVES[adjective]
end
search_filter :user do |adjective, user|
# Though joins may seem more efficient here for the positive case, we need
# to be able to handle cases like "user:owns user:wants", which breaks on
# the JOIN approach. Just have to look up the IDs in advance.
owned_value = parse_user_adjective(adjective, user)
hangers = ClosetHanger.arel_table
items = user.closeted_items
items = items.where(ClosetHanger.arel_table[:owned].eq(owned_value)) unless owned_value.nil?
item_ids = items.map(&:id)
# Though it's best to do arel_table[:id].in(item_ids), it breaks in this
# version of Arel, and other conditions will overwrite this one. Since IDs
# are guaranteed to be integers, let's just build our own string condition
# and be done with it.
"id IN (#{item_ids.join(',')})"
end
search_filter :only do |species_name|
begin
id = Species.require_by_name(species_name).id
@ -709,7 +762,7 @@ class Item < ActiveRecord::Base
SwfAsset.arel_table[:zone_id].in(zone_set.map(&:id))
end
single_search_filter :not_type, :full => lambda { |zone_set_name, scope|
single_search_filter :not_type, :full => lambda { |zone_set_name, user, scope|
zone_set = Zone::ItemZoneSets[zone_set_name]
raise SearchError, "Type \"#{zone_set_name}\" does not exist" unless zone_set
psa = ParentSwfAssetRelationship.arel_table.alias
@ -757,10 +810,10 @@ class Item < ActiveRecord::Base
@positive = !@positive
end
def narrow(scope)
def narrow(scope, user)
if SearchFilterScopes.include?(filter)
polarized_filter = @positive ? filter : "not_#{filter}"
Item.send("search_filter_#{polarized_filter}", self, scope)
Item.send("search_filter_#{polarized_filter}", self, user, scope)
else
raise SearchError, "Filter #{filter} does not exist"
end

View file

@ -0,0 +1,11 @@
module PrettyParam
BLACKLIST = /[^a-z0-9]/i
def name_for_param
name.split(BLACKLIST).select { |word| !word.blank? }.join('-')
end
def to_param
"#{id}-#{name_for_param}"
end
end

View file

@ -1,7 +1,12 @@
class User < ActiveRecord::Base
include PrettyParam
DefaultAuthServerId = 1
PreviewTopContributorsCount = 3
has_many :closet_hangers
has_many :closet_lists
has_many :closeted_items, :through => :closet_hangers, :source => :item
has_many :contributions
has_many :outfits
@ -9,6 +14,9 @@ class User < ActiveRecord::Base
devise :rememberable
attr_accessible :neopets_username, :owned_closet_hangers_visibility,
:wanted_closet_hangers_visibility
def contribute!(pet)
new_contributions = []
new_points = 0
@ -39,6 +47,30 @@ class User < ActiveRecord::Base
new_points
end
def assign_closeted_to_items!(items)
# Assigning these items to a hash by ID means that we don't have to go
# N^2 searching the items list for items that match the given IDs or vice
# versa, and everything stays a lovely O(n)
items_by_id = {}
items.each { |item| items_by_id[item.id] = item }
closet_hangers.where(:item_id => items_by_id.keys).each do |hanger|
item = items_by_id[hanger.item_id]
if hanger.owned?
item.owned = true
else
item.wanted = true
end
end
end
def closet_hangers_groups_visible_to(user)
return [true, false] if user == self
[].tap do |groups|
groups << true if owned_closet_hangers_visibility >= ClosetVisibility[:public].id
groups << false if wanted_closet_hangers_visibility >= ClosetVisibility[:public].id
end
end
def self.find_or_create_from_remote_auth_data(user_data)
user = find_or_initialize_by_remote_id_and_auth_server_id(
user_data['id'],
@ -56,3 +88,4 @@ class User < ActiveRecord::Base
user ? user.points : 0
end
end

View file

@ -76,7 +76,7 @@ $container_width: 800px
input, button, select, label
cursor: pointer
input[type=text], input[type=password], input[type=search], select
input[type=text], input[type=password], input[type=search], input[type=number], select, textarea
+border-radius(3px)
background: #fff
border: 1px solid $input-border-color
@ -85,14 +85,14 @@ input[type=text], input[type=password], input[type=search], select
&:focus, &:active
color: inherit
textarea
font: inherit
a.button, input[type=submit], button
+awesome-button
&.loud
+loud-awesome-button
a.button
+arrowed-awesome-button
ul.buttons
margin-bottom: 1em
li
@ -155,9 +155,21 @@ ul.buttons
&:hover span
text-decoration: none
#userbar-items-link
+hover-link
background: $module-bg-color
padding: .25em .5em
&:after
color: red
content: "new!"
font-size: 85%
margin-left: .5em
.object
+inline-block
padding: .5em
margin: $object-padding 0
padding: 0 $object-padding
position: relative
text-align: center
vertical-align: top
@ -166,13 +178,38 @@ ul.buttons
text-decoration: none
img
+opacity(0.75)
&:hover img
+opacity(1)
img
display: block
height: $object-img-size
margin: 0 auto
width: $object-img-size
&:hover img, a:hover img
// behave in browsers that only respond to a:hover, but also be in the
// hover state more often for browsers who support div:hover
// (quantity form in user items)
+opacity(1)
.nc-icon, .closeted-icons
+opacity(1)
background: rgba(255, 255, 255, 0.75)
line-height: 1
position: absolute
top: $object-img-size - $nc-icon-size
&:hover
+opacity(0.5)
background: transparent
.nc-icon, .closeted-icons img
display: inline
height: $nc-icon-size
width: $nc-icon-size
.nc-icon
right: ($object-width - $object-img-size) / 2 + $object-padding
$closeted-icons-left: ($object-width - $object-img-size) / 2 + $object-padding
.closeted-icons
left: $closeted-icons-left
dt
font-weight: bold
@ -202,15 +239,6 @@ dd
.current
font-weight: bold
.object .nc-icon
height: 16px
position: absolute
right: ($object-width - $object-img-size) / 2 + $object-padding
top: $object-img-size - $nc-icon-size
width: 16px
&:hover
+opacity(0.5)
/* Fonts
/* A font by Jos Buivenga (exljbris) -> www.exljbris.nl

View file

@ -0,0 +1,388 @@
@import "partials/context_button"
@import "partials/icon"
@import "partials/secondary_nav"
body.closet_hangers-index
+secondary-nav
#title
margin-bottom: 0
#import-link
+awesome-button
+loud-awesome-button-color
#closet-hangers-items-search
float: right
input[name=q]
&.loading
background:
image: url(/images/loading.gif)
position: 2px center
repeat: no-repeat
padding-left: $icon-width + 4px
#closet-hangers-contact
clear: both
color: $soft-text-color
margin-bottom: 1em
margin-left: 2em
min-height: image-height("neomail.png")
a, > span
+hover-link
background:
image: image-url("neomail.png")
position: left center
repeat: no-repeat
color: inherit
float: left
height: 100%
padding-left: image-width("neomail.png") + 4px
> span
background-image: image-url("neomail_edit.png")
input[type=text]
width: 10em
label
font-weight: bold
margin-right: .5em
&:after
content: ":"
#edit-contact-link-to-replace-form, #cancel-contact-link
display: none
.edit-contact-link, #cancel-contact-link
cursor: pointer
text-decoration: underline
&:hover
text-decoration: none
#edit-contact-link-to-replace-form
#contact-link-has-value
display: none
#contact-link-no-value
display: inline
&.has-value
#contact-link-has-value
display: inline
#contact-link-no-value
display: none
#cancel-contact-link
margin-left: 1em
#toggle-help
+awesome-button
cursor: pointer
display: none
#closet-hangers-help.hidden
display: none
#closet-hangers-extras
margin:
bottom: 2em
top: 2em
text-align: center
a
+awesome-button
margin: 0 0.5em
#closet-hangers-share
font-size: 85%
margin-bottom: 1em
label
font-weight: bold
margin-right: .5em
input
width: 30em
#closet-hangers
clear: both
text-align: center
.object
.quantity
+opacity(.75)
background: white
padding: 6px 4px 4px
position: absolute
left: ($object-width - $object-img-size) / 2 + $object-padding
line-height: 1
text-align: left
top: 0
span, input[type=number]
font-size: 16px
font-weight: bold
form
display: none
&[data-quantity="1"]
.quantity
display: none
.closet-hangers-group
border-top: 1px solid $module-border-color
margin-bottom: 2em
padding-bottom: 1em
> header
border-bottom: 1px solid $soft-border-color
display: block
margin-bottom: .25em
padding: .25em 0
position: relative
h3
font-size: 250%
margin: 0
.add-closet-list
+awesome-button
bottom: 50%
margin-bottom: -1em
position: absolute
right: 1em
&:active
margin-bottom: -1.1em
top: auto
span.show, span.hide
color: $soft-text-color
display: none
font-size: 85%
left: 1em
position: absolute
top: 1em
&:hover
color: inherit
text-decoration: underline
.closet-list
border-bottom: 1px solid $soft-border-color
padding: .5em 0
position: relative
.visibility-form
font-size: 85%
left: .5em
position: absolute
text-align: left
top: .25em
z-index: 10
input, select
font-size: inherit
margin:
bottom: 0
top: 0
select
border-color: $background-color
input[type=submit]
+context-button
font-size: inherit
visibility: hidden
&:active
top: 1px
.visibility-descriptions
+opacity(.75)
background: $background-color
font-style: italic
list-style: none
padding: 0 .5em
li
display: none
&:hover
.visibility-descriptions li.current
display: block
header
display: block
position: relative
h4
+header-text
font-size: 150%
line-height: 1
margin: 0 auto .67em
width: 50%
.empty-list
display: none
font-style: italic
.closet-list-controls
display: none
position: absolute
right: 1em
top: 0
a, input[type=submit]
+context-button
form
display: inline
&[data-hangers-count="0"]
.empty-list
display: block
&.unlisted
h4
font:
size: 125%
style: italic
&:hover
.closet-list-controls
display: block
.visibility-form
input[type=submit]
visibility: visible
select
border-color: $soft-border-color
&:last-child
border-bottom: 0
&.droppable-active
+border-radius(1em)
+module
border-bottom-width: 1px
border-style: dotted
margin: 1em 0
.object
// totally hiding these elements causes the original element to change
// position, throwing off the drag
+opacity(.25)
&.ui-draggable-dragging
+opacity(1)
.closet-list-controls
display: none
.closet-list-hangers
overflow: hidden
.visibility-form
display: none
.closet-hangers-group-autocomplete-item, .closet-list-autocomplete-item
span
font-style: italic
padding: .2em .4em
.closet-list-autocomplete-item
a, span
font-size: 85%
padding-left: 2em
&.current-user
#closet-hangers
.object:hover
form
display: inline
.closet-hanger-destroy
position: absolute
right: ($object-width - $object-img-size) / 2 + $object-padding
top: $object-img-size - 28px
input
+context-button
.quantity
+opacity(1)
background: transparent
top: 0
padding: 0
span
display: none
input[type=number]
padding: 2px
width: 2em
input[type=submit]
font-size: 85%
&.js
#closet-hangers
.object:hover .quantity
display: block
input[type=number]
width: 2.5em
input[type=submit]
display: none
.object.loading
background: $module-bg-color
outline: 1px solid $module-border-color
.quantity
display: block
span:after
content: ""
#closet-hangers-contact
form
display: none
.edit-contact-link, #cancel-contact-link
display: inline
&.editing
form
display: block
.edit-contact-link
display: none
.closet-hangers-group
header
.show, .hide
cursor: pointer
.hide
display: block
&.hidden
header .hide, .closet-hangers-group-content
display: none
header .show
display: block
#toggle-help
display: inline

View file

@ -0,0 +1,12 @@
body.closet_hangers-petpage
+secondary-nav
#intro
clear: both
#petpage-output
display: block
height: 30em
margin: 0 auto
width: 50%

View file

@ -0,0 +1,30 @@
body.closet_lists-new, body.closet_lists-create, body.closet_lists-edit, body.closet_lists-update
+secondary-nav
form ul.fields
clear: both
list-style: none
label
float: left
font-weight: bold
margin-right: 1em
li
padding: 0.75em 0
width: 35em
input, textarea, select
clear: both
display: block
margin-top: .25em
width: 80%
textarea
height: 12em
.hint
display: block
font:
size: 85%

View file

@ -0,0 +1,55 @@
body.closet_pages-new, body.closet_pages-create
#title
float: left
.flash
clear: both
#back-to-items
+awesome-button
margin:
left: 1em
top: .75em
#closet-page-form
+clearfix
clear: both
margin-bottom: 1em
#closet-page-frame-wrapper
float: left
margin-right: 2%
width: 48%
#closet-page-frame
height: 19em
width: 100%
#closet-page-source
float: left
width: 50%
label
font-weight: bold
textarea
height: 19em
ol
padding-left: 1em
> li
margin-bottom: 1em
ul
font-size: 85%
margin:
bottom: 1em
top: 0
padding-left: 1em
p
margin: 0

View file

@ -1,5 +1,5 @@
body.items-show
header
#item-header
border-bottom: 1px solid $module-border-color
display: block
margin-bottom: 1em
@ -50,8 +50,51 @@ body.items-show
font:
family: $text-font
size: 85%
p:first-child
margin-bottom: .25em
margin-bottom: 1em
p
display: inline
&:first-child
margin-right: 1em
#trade-hangers
font-size: 85%
text-align: left
p
position: relative
&:first-child
margin-bottom: .5em
&.overflows
.toggle
display: block
&.showing-more
.toggle
.less
display: block
.more
display: none
.toggle
background: white
bottom: 0
cursor: pointer
display: none
font-family: $main-font
padding: 0 1em
position: absolute
right: 0
&:hover
text-decoration: underline
.less
display: none
#item-preview-header
margin-top: 3em
h3, a
@ -62,3 +105,37 @@ body.items-show
.nc-icon
height: 16px
width: 16px
#closet-hangers
border: 1px solid $module-border-color
float: right
font-size: 85%
margin-left: 1em
padding: 1em
width: 21em
label, header
display: block
font-weight: bold
header
font-size: 125%
form
padding: .5em 0
select
width: 9em
input[type=number]
width: 4em
&.js
#trade-hangers
p
max-height: 3em
overflow: hidden
&.showing-more
max-height: none

View file

@ -1,6 +1,7 @@
@import ../shared/jquery.jgrowl
@import partials/wardrobe
@import "partials/context_button"
@import "partials/icon"
@import star
@ -229,9 +230,10 @@ body.outfits-edit
display: none
font-size: 85%
margin: 0 auto
#preview-mode-image-access-denied
#preview-mode-note
display: block
font-size: 75%
margin-top: .5em
text-align: center
text-decoration: none
width: 100%
@ -312,9 +314,14 @@ body.outfits-edit
margin: 0 1em 0 0
input
+inline-block
&[type=submit]
margin-right: 2em
.preview-search-form-your-items
display: none
font-size: 85%
margin-right: 1em
#preview-search-form-pagination
+inline-block
margin-left: 2em
a, span
margin: 0 .25em
.current
@ -400,10 +407,7 @@ body.outfits-edit
li
margin-bottom: .25em
a
+awesome-button
+awesome-button-color(#aaaaaa)
+opacity(0.9)
font-size: 80%
+context-button
&:hover
ul, .object-info
display: block
@ -560,6 +564,8 @@ body.outfits-edit
display: block
#save-outfit, #save-current-outfit, #save-outfit-copy, #current-outfit-permalink, #shared-outfit-permalink, #share-outfit, #shared-outfit-url
display: none
.preview-search-form-your-items
+inline-block
&.user-not-signed-in
#save-outfit-not-signed-in

View file

@ -137,3 +137,12 @@ body.outfits-new
#read-more
float: right
#your-items-module
h3
&:after
color: red
content: "new!"
font-size: 85%
font-weight: bold
margin-left: .5em

View file

@ -0,0 +1,6 @@
=context-button
+awesome-button
+awesome-button-color(#aaaaaa)
+opacity(0.9)
font-size: 80%

View file

@ -0,0 +1,12 @@
=secondary-nav
#title
float: left
margin-right: .5em
.flash
clear: both
#secondary-nav
display: block
margin-top: .75em

View file

@ -24,7 +24,8 @@ $text-font: "Droid Serif", Georgia, "Times New Roman", Times, serif
$object-img-size: 80px
$object-width: 100px
$object-padding: 6px
$object-padding: 8px
$nc-icon-size: 16px
$container-top-padding: 3em

View file

@ -6,6 +6,10 @@
@import partials/jquery.jgrowl
@import closet_hangers/index
@import closet_hangers/petpage
@import closet_lists/form
@import closet_pages/new
@import contributions/index
@import items
@import items/index

View file

@ -0,0 +1,18 @@
- show_controls ||= false # we could do user check here, but may as well do it once
.object{'data-item-id' => closet_hanger.item_id, 'data-quantity' => closet_hanger.quantity}
= render :partial => 'items/item_link', :locals => {:item => closet_hanger.item}
.quantity{:class => "quantity-#{closet_hanger.quantity}"}
%span= closet_hanger.quantity
- if show_controls
= form_for closet_hanger, :url => user_item_closet_hanger_path(current_user, closet_hanger.item), :html => {:class => 'closet-hanger-update'} do |f|
= return_to_field_tag
= f.hidden_field :list_id
= f.hidden_field :owned
= f.number_field :quantity, :min => 0, :required => true, :title => "You own #{pluralize closet_hanger.quantity, closet_hanger.item.name}"
= f.submit "Save"
- if show_controls
= form_tag user_item_closet_hanger_path(current_user, closet_hanger.item), :method => :delete, :class => 'closet-hanger-destroy' do
= return_to_field_tag
= hidden_field_tag 'closet_hanger[owned]', closet_hanger.owned
= submit_tag "Remove"

View file

@ -0,0 +1,99 @@
%style{:type => 'text/css'}
:plain
.dti-item-group, .dti-item-list, .dti-unlisted-items {
margin: 0;
padding: 0;
}
.dti-item-group-header, .dti-item-group, #dti-item-footer {
color: #040;
font-family: Helvetica, Arial, Verdana, sans-serif;
line-height: 1.5;
margin: 0 auto;
text-align: center;
width: 560px;
}
.dti-item-group-header {
border-top: 1px solid #060;
font-size: 175%;
margin-top: 1em;
}
.dti-item-group-header, .dti-item-list {
border-bottom: 1px solid #ADA;
}
.dti-item-group {
list-style: none;
}
.dti-item-list h3 {
font-size: 150%;
}
.dti-items {
border-spacing: 10px;
display: table;
}
.dti-item-row {
display: table-row;
}
.dti-item {
display: table-cell;
margin: 0 10px;
padding: 8px;
text-align: right;
width: 84px;
}
.dti-item-thumbnail {
height: 80px;
width: 80px;
}
.dti-item span {
display: block;
text-align: center;
}
.dti-item-nc {
margin-top: -16px;
}
.dti-unlisted-items h3 {
font-style: italic;
}
#dti-item-footer {
font-size: 85%;
margin-top: 2em;
}
- [true, false].each do |owned|
- lists = lists_by_owned[owned]
- if lists || unlisted_hangers_by_owned[owned]
%h2.dti-item-group-header Items #OWNER #{ClosetHanger.verb(:someone, owned)}
%ul.dti-item-group
- if lists
- lists.each do |list|
- unless list.hangers.empty?
%li.dti-item-list
%h3= list.name
- if list.description?
.dti-item-list-desc
= closet_list_description_format list
%div.dti-items
= render_batched_petpage_hangers(list.hangers.sort { |a,b| a.item.name <=> b.item.name })
- if unlisted_hangers_by_owned[owned]
%li.dti-unlisted-items
- unless lists.blank?
%h3 (Not in a list)
%div
= render_batched_petpage_hangers(unlisted_hangers_by_owned[owned])
#dti-item-footer
I made this list on Dress to Impress. You can, too!

View file

@ -0,0 +1,6 @@
%div.dti-item
= image_tag hanger.item.thumbnail_url, :alt => nil, :class => 'dti-item-thumbnail'
- if hanger.item.nc?
= image_tag nc_icon_url, :alt => 'NC', :class => 'dti-item-nc', :title => 'This is an NC Mall item'
%span= petpage_item_name hanger.item

View file

@ -0,0 +1,119 @@
- unless public_perspective?
- title 'Your Items'
- add_body_class 'current-user'
- secondary_nav do
%span#toggle-help Need help?
= form_tag items_path, :method => :get, :id => 'closet-hangers-items-search', 'data-current-user-id' => current_user.id do
= text_field_tag :q, nil, :placeholder => "Find items to add"
= submit_tag 'Search', :name => nil
- else
- title "#{@user.name}'s Items"
- canonical_path user_closet_hangers_path(@user)
- content_for :before_flashes do
#closet-hangers-contact
- if public_perspective?
- if @user.neopets_username?
= link_to "Neomail #{@user.neopets_username}", send_neomail_url(@user)
- else
%span#edit-contact-link-to-replace-form.edit-contact-link{:class => @user.neopets_username? ? 'has-value' : nil}
%span#contact-link-no-value
Add your Neopets username
%span#contact-link-has-value
Edit
= surround '"' do
Neomail
%span= @user.neopets_username
= form_for @user do |f|
= f.label :neopets_username
= f.text_field :neopets_username
= f.submit "Save"
%span#cancel-contact-link cancel
- unless public_perspective?
#closet-hangers-help{:class => closet_hangers_help_class}
:markdown
**These are your items! You can track what items you want and own, and
share [this page](#{request.fullpath}) with the world**. Just look up an
item in the search form above to get started.
**You can also sort your items into lists.**
[Building an Up For Trade list is a good place to start][uft]. You can
make lists for trade items with different market values, a private list of
what you need for that next outfit, or whatever you like. You can also
drag-and-drop items in and out of lists. It's pretty fun.
**Your items also have privacy settings.**
Items can be **private**, so only you can see them. They can be **public**,
so you can share this page with friends. They can even be **trading**,
meaning that we'll mention on the item's [Infinite Closet][ic] page that
you own or want that item.
**We try to make trading easy.** If there's some item you want, you can
pull up that item's [Infinite Closet][ic] page to see if anyone is offering
it, and see what *that* user wants
in exchange. It's all pretty spiffy. Also, if you plan to trade, your should
<span class="edit-contact-link">add your Neopets username</span> so that
when other users come here they know how to contact you.
**Have fun!** If you have any [neat ideas][suggestions] or [general praise and
bug reports][mail], we love to hear them. And, if you enjoy this feature,
[please consider donating to keep Dress to Impress running and improving][donate].
Thanks!
[donate]: #{donate_path}
[ic]: #{items_path}
[mail]: mailto:#{contact_email}
[suggestions]: #{feedback_url}
[uft]: #{new_user_closet_list_path(@user, :closet_list => {:hangers_owned => true, :name => 'Up For Trade', :visibility => ClosetVisibility[:trading].id})}
- unless public_perspective?
#closet-hangers-extras
#closet-hangers-share
%label{:for => 'closet-hangers-share-box'} Public URL:
%input#closet-hangers-share-box{:type => 'text', :value => user_closet_hangers_url(@user), :readonly => true}
= link_to "Import from Neopets closet", new_closet_page_path
= link_to "Export to Neopets petpage", petpage_user_closet_hangers_path(@user)
#closet-hangers{:class => public_perspective? ? nil : 'current-user'}
- [true, false].each do |owned|
.closet-hangers-group{'data-owned' => owned.to_s, :id => "closet-hangers-group-#{owned}"}
%header
%h3
Items #{closet_hanger_subject} #{closet_hanger_verb(owned)}
%span.toggle.show show
%span.toggle.hide hide
- unless public_perspective?
= link_to_add_closet_list 'Add new list', :owned => owned, :class => 'add-closet-list'
.closet-hangers-group-content
= render_closet_lists(@closet_lists_by_owned[owned])
.closet-list.unlisted{'data-hangers-count' => unlisted_hangers_count(owned)}
%header
- unless public_perspective?
= form_for @user, :html => {:class => 'visibility-form'} do |f|
= f.select hangers_group_visibility_field_name(owned),
closet_visibility_choices(:human_name)
= f.submit "Save"
= closet_visibility_descriptions
- if has_lists?(owned)
%h4 (Not in a list)
.closet-list-content
.closet-list-hangers
= render_unlisted_closet_hangers(owned)
%span.empty-list
There aren't any items here.
- if public_perspective?
- unless has_hangers?(owned)
%p #{@user.name} doesn't seem to #{closet_hanger_verb(owned, false)} anything.
- content_for :stylesheets do
= stylesheet_link_tag 'south-street/jquery-ui'
- content_for :javascripts do
= include_javascript_libraries :jquery
= javascript_include_tag 'jquery.ui', 'jquery.jgrowl', 'placeholder', 'closet_hangers/index'

View file

@ -0,0 +1,22 @@
- title 'Export to petpage'
- secondary_nav do
= link_to 'Back to Your Items', user_closet_hangers_path(current_user), :class => 'button'
#intro
%p
We took your public lists and created a nice, simple HTML file for your
Neopet's petpage. By default it's styled as a table, but it doesn't have to
be. The HTML is flexible, so, if you're the artsy type, you're free to mess
with the styles all you want!
%p
Copy the HTML from the box below, then paste it into
= succeed '.' do
= link_to "your pet's page", 'http://www.neopets.com/edithomepage.phtml'
Then head to the Neoboards to show off! Have fun!
%textarea#petpage-output
= '' + render('petpage_content',
:lists_by_owned => @closet_lists_by_owned,
:unlisted_hangers_by_owned => @unlisted_closet_hangers_by_owned)

View file

@ -0,0 +1,22 @@
.closet-list{'data-id' => closet_list.id, 'data-hangers-count' => closet_list.hangers.count, :id => "closet-list-#{closet_list.id}"}
%header
- if show_controls
= form_for [closet_list.user, closet_list], :html => {:class => 'visibility-form'} do |f|
= f.select :visibility, closet_visibility_choices(:human_name)
= f.submit "Save"
= closet_visibility_descriptions('items in this list')
.closet-list-controls
= link_to 'Edit', edit_user_closet_list_path(closet_list.user, closet_list)
= form_tag user_closet_list_path(closet_list.user, closet_list), :method => 'delete' do
= submit_tag 'Delete', :confirm => closet_list_delete_confirmation(closet_list)
%h4= closet_list.name
.closet-list-content
- if closet_list.description?
= closet_list_description_format closet_list
.closet-list-hangers
- unless closet_list.hangers.empty?
= render_sorted_hangers(closet_list, show_controls)
%span.empty-list This list is empty.

View file

@ -0,0 +1,31 @@
- secondary_nav do
= link_to 'Back to Your Items', user_closet_hangers_path(current_user), :class => 'button'
= form_for [@closet_list.user, @closet_list] do |f|
%ul.fields
%li
= f.label :name
%span.hint Like "up for trade" or "NC wishlist"
= f.text_field :name, :required => true
%li
= f.label :hangers_owned, 'This is a list for&hellip;'.html_safe
= f.select :hangers_owned, hangers_owned_options
%li
= f.label :visibility, 'Who can see this list?'
= f.select :visibility, closet_visibility_choices(:description, 'Items in this list')
%li
= f.label :description
%span.hint
Why are these items in a list? What are your terms for trading?
Or you can leave this blank.
= f.text_area :description
%span.hint
We
= surround '_' do
%em support
= surround '**' do
%strong Markdown
and
some HTML.
= f.submit 'Save list'

View file

@ -0,0 +1,3 @@
- title "Editing list \"#{@closet_list.name}\""
= render 'form'

View file

@ -0,0 +1,3 @@
- title 'Create an items list'
= render 'form'

View file

@ -0,0 +1,57 @@
- title "Import from closet, Page #{@closet_page.index}"
- content_for :before_flashes do
= link_to 'Back to Your Items', user_closet_hangers_path(current_user), :id => 'back-to-items'
= form_for @closet_page, :html => {:id => 'closet-page-form'} do |f|
= f.hidden_field :index
#closet-page-frame-wrapper
%span
%strong Page #{@closet_page.index}
of your closet
%iframe#closet-page-frame{:src => @closet_page.url}
#closet-page-source
= f.label :source, "Paste source code below"
= f.text_area :source
= f.submit 'Add items to closet'
:markdown
**Welcome to the bulk closet importer!** We're going to make it as
easy as possible to import your Neopets.com closet data into your Dress to
Impress closet. Here's how it works.
1. Check the framed Neopets.com window on the left, pointing to
[page #{@closet_page.index} of your closet][cp].
* **Confirm that you're logged in.** If you're logged into
Neopets, but the above frame says that you're not, try enabling
"third-party cookies" in your browser. (Most have that on by default.)
* **If you haven't logged in, #{link_to_neopets_login "do so in another window"}</a>.**
It's never a good idea to log in inside of a frame, unless you're a
web programmer pro who can check that the frame does, in fact, point
to Neopets.com. To be safe,
#{link_to_neopets_login "pull up another window, check the URL, and log in safely"}.
* **Confirm that the page is, in fact, your closet.** Similarly, don't
just trust a website when they tell you to copy-paste the source code
of another site. Instead, check that the page is what it is supposed to
be and does not contain any information you didn't mean to give out.
2. View the frame's source code.
* **In Google Chrome,** right-click the frame and choose **View Frame Source**.
* **In Firefox,** right-click the frame, choose **This Frame**, then **View Frame Source**.
* In other browsers, right-click, and look for something similar. If you're
still having trouble, try
#{link_to "viewing the page in a new window", @closet_page.url, :target => "_blank"},
right-clicking, and choosing View Source.
3. Highlight the entire source code, and copy-paste it into the box on the right.
* Some nifty shortcuts: Ctrl-A to select all the text, Ctrl-C to copy it,
Ctrl-V to paste it in.
4. Submit!
* We'll analyze the code you sent us, grab exclusively the identity and
quantity of items in your closet, and add that to your Dress to Impress
closet. I promise it's all safe, but, if you're concerned, find a
programmer buddy and [check out the source code to be sure][source].
[cp]: #{@closet_page.url}
[source]: http://github.com/matchu/openneo-impress-rails

View file

@ -1,4 +1,7 @@
- title 'Recent Contributions'
- if @user
- canonical_path user_contributions_path(@user)
%ul.buttons
- if @user
%li= link_to 'Recent Contributions', contributions_path, :class => 'button'
@ -15,3 +18,4 @@
= will_paginate @contributions
%ul.contributions= render @contributions
= will_paginate @contributions

View file

@ -1,5 +1,3 @@
.object
= link_to item_path(item, :q => @query) do
= image_tag item.thumbnail_url, :alt => item.description, :title => item.description
= item.name
= nc_icon_for(item)
= render :partial => 'item_link', :locals => {:item => item}

View file

@ -0,0 +1,6 @@
= link_to item_path(item, :q => @query) do
= image_tag item.thumbnail_url, :alt => item.description, :title => item.description
%span.name= item.name
= nc_icon_for(item)
= closeted_icons_for(item)

View file

@ -20,6 +20,11 @@
%dd
returns any item with the word "kreludor" and the phrase "altador cup"
in it, but not the word "background"
%dt hat user:owns
%dd
returns
= link_to 'items that you own', your_items_path
with the word "hat"
%dt blue is:nc
%dd returns any NC Mall item with the word "blue" in it
%dt collar -is:pb

View file

@ -1,17 +1,35 @@
- title @item.name
- content_for :meta do
%link{:rel => 'canonical', :href => url_for(@item)}
- canonical_path @item
- cache "items_show_#{@item.id}_main_content" do
%header
%header#item-header
= image_tag @item.thumbnail_url, :id => 'item-thumbnail'
%div
%h2#item-name= @item.name
= nc_icon_for(@item)
- unless @item.rarity.blank?
== Rarity: #{@item.rarity_index} (#{@item.rarity})
%a.button{:href => neoitems_url_for(@item)} NeoItems
= link_to 'NeoItems', neoitems_url_for(@item), :class => 'button'
- if @current_user_hangers
#closet-hangers
%header
Track this in
= link_to 'Your Items', user_closet_hangers_path(current_user)
- @current_user_hangers.each do |hanger|
= form_for(hanger, :url => user_item_closet_hanger_path(current_user, @item)) do |f|
- if hanger.new_record?
= f.hidden_field :quantity
= f.hidden_field :owned
= f.submit "I #{hanger.verb(:you)} this item!"
- else
= f.hidden_field :owned
= f.label :quantity, "How many of these do you #{hanger.verb(:you)}?"
= f.number_field :quantity, :min => 0, :required => true
- lists = current_user.closet_lists.where(:hangers_owned => hanger.owned).all
- unless lists.empty?
= f.collection_select :list_id, lists, :id, :name, :include_blank => 'Not in a list'
= f.submit "Save"
%p= @item.description
#item-zones
@ -25,6 +43,37 @@
- else
= list_zones @item.restricted_zones
#trade-hangers
- [true, false].each do |owned|
%p
- unless @trading_closet_hangers_by_owned[owned].empty?
%strong
= pluralize @trading_closet_hangers_by_owned[owned].size, 'user'
- if owned
- if @trading_closet_hangers_by_owned[owned].size == 1
has
- else
have
this item up for trade:
- else
- if @trading_closet_hangers_by_owned[owned].size == 1
wants
- else
want
this item:
= render_trading_closet_hangers(owned)
- else
%strong
We don't know anyone who
- if owned
has this item up for trade.
- else
wants this item.
%span.toggle
%span.more more
%span.less less
#item-preview-header
%h3 Preview
%a#customize-more.button{:href => '/'} Customize more

View file

@ -11,6 +11,7 @@
Dress to Impress: Preview customized Neopets' clothing and wearables
/[if IE]
= include_javascript_libraries :html5
= yield :stylesheets
= stylesheet_link_tag "compiled/screen"
= yield :meta
= csrf_meta_tag
@ -36,15 +37,12 @@
#userbar
- if user_signed_in?
- if can_use_image_mode?
= link_to image_mode_path, :id => 'userbar-image-mode' do
= image_tag 'image_mode_icon.png', :alt => 'Image Mode'
Welcome to Image Mode!
%span
Hey,
= succeed '!' do
= link_to current_user.name, user_contributions_path(current_user)
== You have #{current_user.points} points.
Hey, #{current_user.name}!
You have
= succeed '.' do
= link_to "#{current_user.points} points", user_contributions_path(current_user)
= link_to 'Items', user_closet_hangers_path(current_user), :id => 'userbar-items-link'
= link_to 'Outfits', current_user_outfits_path
= link_to 'Settings', Openneo::Auth.remote_settings_url
= link_to 'Log out', logout_path_with_return_to
@ -69,9 +67,9 @@
Contact:
%ul
%li
%a{:href => "http://openneo.uservoice.com/forums/40720-dress-to-impress"} Suggestions
%a{:href => feedback_url} Suggestions
%li
%a{:href => "mailto:webmaster@openneo.net"} Questions, comments, bugs
%a{:href => "mailto:#{contact_email}"} Questions, comments, bugs
%p
Images &copy; 2000-2010 Neopets, Inc. All Rights Reserved.
Used With Permission

View file

@ -41,14 +41,16 @@
%li#preview-mode-flash.active Flash
- if can_use_image_mode?
%li#preview-mode-image Image
- unless can_use_image_mode?
= link_to(donate_path, :id => 'preview-mode-image-access-denied', :target => '_blank') do
- if can_use_image_mode?
%button#preview-download-image Download
= link_to 'Image mode FAQ', image_mode_path,
:id => 'preview-mode-note', :target => '_blank'
- else
= link_to(donate_path, :id => 'preview-mode-note', :target => '_blank') do
%strong Image mode
is available for early beta testing to users who
%em donate
at least $5 to help upgrade the server. Thanks!
- if can_use_image_mode?
%button#preview-download-image Download
#preview-sidebar
- unless can_use_image_mode?
#preview-sidebar-donation-request
@ -91,6 +93,8 @@
%h2 Add an item
%input{:name => "query", :placeholder => "Search items...", :type => "search"}/
%input{:type => "submit", :value => "Go"}/
%a.preview-search-form-your-items{:href => '#', 'data-search-value' => 'owns'} Items you own
%a.preview-search-form-your-items{:href => '#', 'data-search-value' => 'wants'} Items you want
#preview-search-form-pagination
%a#preview-search-form-clear{:href => "#"} clear
%dl#preview-search-form-help

View file

@ -28,16 +28,16 @@
- cache :action_suffix => 'sections_and_description' do
%ul#sections
%li
%a{:href => "http://forum.openneo.net"}
= image_tag 'forum.png'
%li#your-items-module
= link_to image_tag('your_items.png'), your_items_path
%h3
%a{:href => "http://forum.openneo.net/"} Forum
= link_to 'Your Items', your_items_path
%div
%h4 Join our community!
%h4 Track and trade!
%p
Show off your designs, ask for advice, or play silly forum games
here.
Make lists of the items you own and want, and share them with the
world.
%li
%a{:href => items_path}
= image_tag 'items.png'

View file

@ -1,4 +1,10 @@
OpenneoImpressItems::Application.routes.draw do |map|
get "petpages/new"
get "closet_lists/new"
get "closet_lists/create"
root :to => 'outfits#new'
devise_for :users
@ -24,6 +30,8 @@ OpenneoImpressItems::Application.routes.draw do |map|
resources :pet_attributes, :only => [:index]
resources :swf_assets, :only => [:index, :show]
resources :closet_pages, :only => [:new, :create], :path => 'closet/pages'
match '/users/current-user/outfits' => 'outfits#index', :as => :current_user_outfits
match '/pets/load' => 'pets#load', :method => :post, :as => :load_pet
@ -33,9 +41,22 @@ OpenneoImpressItems::Application.routes.draw do |map|
match '/logout' => 'sessions#destroy', :as => :logout
match '/users/authorize' => 'sessions#create'
resources :user, :only => [] do
resources :users, :path => 'user', :only => [:update] do
resources :contributions, :only => [:index]
resources :closet_hangers, :only => [:index], :path => 'closet' do
collection do
get :petpage
end
end
resources :closet_lists, :only => [:new, :create, :edit, :update, :destroy], :path => 'closet/lists'
resources :items, :only => [] do
resource :closet_hanger, :only => [:create, :update, :destroy]
end
end
match 'users/current-user/closet' => 'closet_hangers#index', :as => :your_items
match 'users/top-contributors' => 'users#top_contributors', :as => :top_contributors
match 'users/top_contributors' => redirect('/users/top-contributors')

View file

@ -0,0 +1,15 @@
class CreateClosetHangers < ActiveRecord::Migration
def self.up
create_table :closet_hangers do |t|
t.integer :item_id
t.integer :user_id
t.integer :quantity
t.timestamps
end
end
def self.down
drop_table :closet_hangers
end
end

View file

@ -0,0 +1,7 @@
class SetClosetHangersQuantityDefaultToZero < ActiveRecord::Migration
def self.up
end
def self.down
end
end

View file

@ -0,0 +1,9 @@
class AddNeopetsUsernameToUsers < ActiveRecord::Migration
def self.up
add_column :users, :neopets_username, :string
end
def self.down
remove_column :users, :neopets_username
end
end

View file

@ -0,0 +1,10 @@
class AddOwnedToClosetHangers < ActiveRecord::Migration
def self.up
add_column :closet_hangers, :owned, :boolean, :null => false, :default => true
end
def self.down
remove_column :closet_hangers, :owned
end
end

View file

@ -0,0 +1,21 @@
class CreateClosetLists < ActiveRecord::Migration
def self.up
create_table :closet_lists do |t|
t.string :name
t.text :description
t.integer :user_id
t.boolean :hangers_owned, :null => false
t.timestamps
end
add_column :closet_hangers, :list_id, :integer
end
def self.down
drop_table :closet_lists
remove_column :closet_hangers, :list_id
end
end

View file

@ -0,0 +1,12 @@
class AddClosetHangersVisibilityToUsers < ActiveRecord::Migration
def self.up
add_column :users, :owned_closet_hangers_visibility, :integer, :null => false, :default => 1
add_column :users, :wanted_closet_hangers_visibility, :integer, :null => false, :default => 1
end
def self.down
remove_column :users, :wanted_closet_hangers_visibility
remove_column :users, :owned_closet_hangers_visibility
end
end

View file

@ -0,0 +1,10 @@
class AddVisibilityToClosetLists < ActiveRecord::Migration
def self.up
add_column :closet_lists, :visibility, :integer, :null => false, :default => 1
end
def self.down
remove_column :closet_lists, :visibility
end
end

View file

@ -10,7 +10,7 @@
#
# It's strongly recommended to check this file into your version control system.
ActiveRecord::Schema.define(:version => 20110626202605) do
ActiveRecord::Schema.define(:version => 20110731021808) do
create_table "auth_servers", :force => true do |t|
t.string "short_name", :limit => 10, :null => false
@ -20,6 +20,26 @@ ActiveRecord::Schema.define(:version => 20110626202605) do
t.string "secret", :limit => 64, :null => false
end
create_table "closet_hangers", :force => true do |t|
t.integer "item_id"
t.integer "user_id"
t.integer "quantity"
t.datetime "created_at"
t.datetime "updated_at"
t.boolean "owned", :default => true, :null => false
t.integer "list_id"
end
create_table "closet_lists", :force => true do |t|
t.string "name"
t.text "description"
t.integer "user_id"
t.boolean "hangers_owned", :null => false
t.datetime "created_at"
t.datetime "updated_at"
t.integer "visibility", :default => 1, :null => false
end
create_table "contributions", :force => true do |t|
t.string "contributed_type", :limit => 8, :null => false
t.integer "contributed_id", :null => false
@ -169,6 +189,10 @@ ActiveRecord::Schema.define(:version => 20110626202605) do
t.boolean "forum_admin", :default => false, :null => false
t.boolean "forum_moderator"
t.boolean "image_mode_tester", :default => false, :null => false
t.text "closet_description", :null => false
t.string "neopets_username"
t.integer "owned_closet_hangers_visibility", :default => 1, :null => false
t.integer "wanted_closet_hangers_visibility", :default => 1, :null => false
end
create_table "zones", :force => true do |t|

28
public/403.html Normal file
View file

@ -0,0 +1,28 @@
<!DOCTYPE html>
<html>
<head>
<title>You do not have permission to access this page (403)</title>
<style type="text/css">
body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
div.dialog {
width: 25em;
padding: 0 4em;
margin: 4em auto 0 auto;
border: 1px solid #ccc;
border-right-color: #999;
border-bottom-color: #999;
}
h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
</style>
</head>
<body>
<!-- This file lives in public/403.html -->
<div class="dialog">
<h1>You do not have permission to access this page.</h1>
<p>This resource might belong to another user, or your session may have expired.</p>
<p><a href="/login">Try logging in again.</a></p>
</div>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 738 B

After

Width:  |  Height:  |  Size: 732 B

BIN
public/images/neomail.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 754 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 756 B

BIN
public/images/owned.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 537 B

BIN
public/images/wanted.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 671 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

View file

@ -0,0 +1,510 @@
(function () {
var hangersInitCallbacks = [];
function onHangersInit(callback) {
hangersInitCallbacks[hangersInitCallbacks.length] = callback;
}
function hangersInit() {
for(var i = 0; i < hangersInitCallbacks.length; i++) {
hangersInitCallbacks[i]();
}
}
/*
Hanger groups
*/
var hangerGroups = [];
$('div.closet-hangers-group').each(function () {
var el = $(this);
var lists = [];
el.find('div.closet-list').each(function () {
var el = $(this);
var id = el.attr('data-id');
if(id) {
lists[lists.length] = {
id: parseInt(id, 10),
label: el.find('h4').text()
}
}
});
hangerGroups[hangerGroups.length] = {
label: el.find('h3').text(),
lists: lists,
owned: (el.attr('data-owned') == 'true')
};
});
$('div.closet-hangers-group span.toggle').live('click', function () {
$(this).closest('.closet-hangers-group').toggleClass('hidden');
});
/*
Hanger forms
*/
$.fn.liveDraggable = function (opts) {
this.live("mouseover", function() {
if (!$(this).data("init")) {
$(this).data("init", true).draggable(opts);
}
});
};
var body = $(document.body).addClass("js");
if(!body.hasClass("current-user")) return false;
var hangersElQuery = '#closet-hangers';
var hangersEl = $(hangersElQuery);
$.fn.disableForms = function () {
return this.data("formsDisabled", true).find("input").attr("disabled", "disabled").end();
}
$.fn.enableForms = function () {
return this.data("formsDisabled", false).find("input").removeAttr("disabled").end();
}
$.fn.hasChanged = function () {
return this.attr('data-previous-value') != this.val();
}
$.fn.revertValue = function () {
return this.each(function () {
var el = $(this);
el.val(el.attr('data-previous-value'));
});
}
$.fn.storeValue = function () {
return this.each(function () {
var el = $(this);
el.attr('data-previous-value', el.val());
});
}
$.fn.insertIntoSortedList = function (list, compare) {
var newChild = this, inserted = false;
list.children().each(function () {
if(compare(newChild, $(this)) < 1) {
newChild.insertBefore(this);
inserted = true;
return false;
}
});
if(!inserted) newChild.appendTo(list);
return this;
}
function handleSaveError(xhr, action) {
try {
var data = $.parseJSON(xhr.responseText);
} catch(e) {
var data = {};
}
if(typeof data.errors != 'undefined') {
$.jGrowl("Error " + action + ": " + data.errors.join(", "));
} else {
$.jGrowl("We had trouble " + action + " just now. Try again?");
}
}
function objectRemoved(objectWrapper) {
objectWrapper.hide(250, $.proxy(objectWrapper, 'remove'));
}
function compareItemsByName(a, b) {
return a.find('span.name').text().localeCompare(b.find('span.name').text());
}
function findList(id, item) {
if(id) {
return $('#closet-list-' + id);
} else {
return item.closest('.closet-hangers-group').find('div.closet-list.unlisted');
}
}
function updateListHangersCount(el) {
el.attr('data-hangers-count', el.find('div.object').length);
}
function moveItemToList(item, listId) {
var newList = findList(listId, item);
var oldList = item.closest('div.closet-list');
var hangersWrapper = newList.find('div.closet-list-hangers');
item.insertIntoSortedList(hangersWrapper, compareItemsByName);
updateListHangersCount(oldList);
updateListHangersCount(newList);
}
function submitUpdateForm(form) {
if(form.data('loading')) return false;
var quantityEl = form.children("input[name=closet_hanger\[quantity\]]");
var listEl = form.children("input[name=closet_hanger\[list_id\]]");
var listChanged = listEl.hasChanged();
if(listChanged || quantityEl.hasChanged()) {
var objectWrapper = form.closest(".object").addClass("loading");
var newQuantity = quantityEl.val();
var quantitySpan = objectWrapper.find(".quantity span").text(newQuantity);
objectWrapper.attr('data-quantity', newQuantity);
var data = form.serialize(); // get data before disabling inputs
objectWrapper.disableForms();
form.data('loading', true);
if(listChanged) moveItemToList(objectWrapper, listEl.val());
$.ajax({
url: form.attr("action") + ".json",
type: "post",
data: data,
dataType: "json",
complete: function (data) {
if(quantityEl.val() == 0) {
objectRemoved(objectWrapper);
} else {
objectWrapper.removeClass("loading").enableForms();
}
form.data('loading', false);
},
success: function () {
quantityEl.storeValue();
listEl.storeValue();
},
error: function (xhr) {
quantityEl.revertValue();
listEl.revertValue();
if(listChanged) moveItemToList(objectWrapper, listEl.val());
quantitySpan.text(quantityEl.val());
handleSaveError(xhr, "updating the quantity");
}
});
}
}
$(hangersElQuery + ' form.closet-hanger-update').live('submit', function (e) {
e.preventDefault();
submitUpdateForm($(this));
});
function editableInputs() { return $(hangersElQuery).find('input[name=closet_hanger\[quantity\]], input[name=closet_hanger\[list_id\]]') }
$(hangersElQuery + 'input[name=closet_hanger\[quantity\]]').live('change', function () {
submitUpdateForm($(this).parent());
}).storeValue();
onHangersInit(function () {
editableInputs().storeValue();
});
$(hangersElQuery + ' div.object').live('mouseleave', function () {
submitUpdateForm($(this).find('form.closet-hanger-update'));
}).liveDraggable({
appendTo: '#closet-hangers',
distance: 20,
helper: "clone",
revert: "invalid"
});
$(hangersElQuery + " form.closet-hanger-destroy").live("submit", function (e) {
e.preventDefault();
var form = $(this);
var button = form.children("input[type=submit]").val("Removing…");
var objectWrapper = form.closest(".object").addClass("loading");
var data = form.serialize(); // get data before disabling inputs
objectWrapper.addClass("loading").disableForms();
$.ajax({
url: form.attr("action") + ".json",
type: "post",
data: data,
dataType: "json",
complete: function () {
button.val("Remove");
},
success: function () {
objectRemoved(objectWrapper);
},
error: function () {
objectWrapper.removeClass("loading").enableForms();
$.jGrowl("Error removing item. Try again?");
}
});
});
/*
Search, autocomplete
*/
$('input, textarea').placeholder();
var itemsSearchForm = $("#closet-hangers-items-search[data-current-user-id]");
var itemsSearchField = itemsSearchForm.children("input[name=q]");
itemsSearchField.autocomplete({
select: function (e, ui) {
if(ui.item.is_item) {
// Let the autocompleter finish up this search before starting a new one
setTimeout(function () { itemsSearchField.autocomplete("search", ui.item) }, 0);
} else {
var item = ui.item.item;
var group = ui.item.group;
itemsSearchField.addClass("loading");
var closetHanger = {
owned: group.owned,
list_id: ui.item.list ? ui.item.list.id : ''
};
if(!item.hangerInGroup) closetHanger.quantity = 1;
$.ajax({
url: "/user/" + itemsSearchForm.data("current-user-id") + "/items/" + item.id + "/closet_hanger",
type: "post",
data: {closet_hanger: closetHanger, return_to: window.location.pathname + window.location.search},
complete: function () {
itemsSearchField.removeClass("loading");
},
success: function (html) {
var doc = $(html);
hangersEl.html( doc.find('#closet-hangers').html() );
hangersInit();
doc.find('.flash').hide().insertBefore(hangersEl).show(500).delay(5000).hide(250);
itemsSearchField.val("");
},
error: function (xhr) {
handleSaveError(xhr, "adding the item");
}
});
}
},
source: function (input, callback) {
if(typeof input.term == 'string') { // user-typed query
$.getJSON("/items.json?q=" + input.term, function (data) {
var output = [];
var items = data.items;
for(var i in items) {
items[i].label = items[i].name;
items[i].is_item = true;
output[output.length] = items[i];
}
callback(output);
});
} else { // item was chosen, now choose a group to insert
var groupInserts = [], group;
var item = input.term, itemEl, hangerInGroup, currentListId;
for(var i in hangerGroups) {
group = hangerGroups[i];
itemEl = $('div.closet-hangers-group[data-owned=' + group.owned + '] div.object[data-item-id=' + item.id + ']');
hangerInGroup = itemEl.length > 0;
currentListId = itemEl.closest('.closet-list').attr('data-id');
groupInserts[groupInserts.length] = {
group: group,
item: item,
label: item.label,
hangerInGroup: hangerInGroup,
hangerInList: !!currentListId
}
for(var i = 0; i < group.lists.length; i++) {
groupInserts[groupInserts.length] = {
group: group,
item: item,
label: item.label,
list: group.lists[i],
hangerInGroup: hangerInGroup,
currentListId: currentListId
}
}
}
callback(groupInserts);
}
}
});
var autocompleter = itemsSearchField.data("autocomplete");
autocompleter._renderItem = function( ul, item ) {
var li = $("<li></li>").data("item.autocomplete", item);
if(item.is_item) { // these are items from the server
li.append("<a>Add <strong>" + item.label + "</strong>");
} else if(item.list) { // these are list inserts
if(item.hangerInGroup) {
if(item.currentListId == item.list.id) {
li.append("<span>It's in <strong>" + item.list.label + "</strong> now");
} else {
li.append("<a>Move to <strong>" + item.list.label + "</strong>");
}
} else {
li.append("<a>Add to <strong>" + item.list.label + "</strong>");
}
li.addClass("closet-list-autocomplete-item");
} else { // these are group inserts
if(item.hangerInGroup) {
var groupName = item.group.label;
if(item.hangerInList) {
li.append("<a>Move to <strong>" + groupName.replace(/\s+$/, '') + "</strong>, no list");
} else {
li.append("<span>It's in <strong>" + groupName + "</strong> now");
}
} else {
li.append("<a>Add to <strong>" + item.group.label + "</strong>");
}
li.addClass('closet-hangers-group-autocomplete-item');
}
return li.appendTo(ul);
}
/*
Contact Neopets username form
*/
var contactEl = $('#closet-hangers-contact');
var editContactLink = $('.edit-contact-link');
var contactForm = contactEl.children('form');
var cancelContactLink = $('#cancel-contact-link');
var contactFormUsername = contactForm.children('input[type=text]');
var editContactLinkUsername = $('#contact-link-has-value span');
function closeContactForm() {
contactEl.removeClass('editing');
}
editContactLink.click(function () {
contactEl.addClass('editing');
contactFormUsername.focus();
});
cancelContactLink.click(closeContactForm);
contactForm.submit(function (e) {
var data = contactForm.serialize();
contactForm.disableForms();
$.ajax({
url: contactForm.attr('action') + '.json',
type: 'post',
data: data,
dataType: 'json',
complete: function () {
contactForm.enableForms();
},
success: function () {
var newName = contactFormUsername.val();
if(newName.length > 0) {
editContactLink.addClass('has-value');
editContactLinkUsername.text(newName);
} else {
editContactLink.removeClass('has-value');
}
closeContactForm();
},
error: function (xhr) {
handleSaveError(xhr, 'saving Neopets username');
}
});
e.preventDefault();
});
/*
Hanger list controls
*/
$('input[type=submit][data-confirm]').live('click', function (e) {
if(!confirm(this.getAttribute('data-confirm'))) e.preventDefault();
});
/*
Closet list droppable
*/
onHangersInit(function () {
$('.closet-hangers-group').each(function () {
var group = $(this);
group.find('div.closet-list').droppable({
accept: '#' + group.attr('id') + ' div.object',
activate: function () {
$(this).find('.closet-list-content').animate({opacity: 0, height: 100}, 250);
},
activeClass: 'droppable-active',
deactivate: function () {
$(this).find('.closet-list-content').css('height', 'auto').animate({opacity: 1}, 250);
},
drop: function (e, ui) {
var form = ui.draggable.find('form.closet-hanger-update');
form.find('input[name=closet_hanger\[list_id\]]').val(this.getAttribute('data-id'));
submitUpdateForm(form);
}
});
});
});
/*
Visibility Descriptions
*/
function updateVisibilityDescription() {
var descriptions = $(this).closest('.visibility-form').
find('ul.visibility-descriptions');
descriptions.children('li.current').removeClass('current');
descriptions.children('li[data-id=' + $(this).val() + ']').addClass('current');
}
function visibilitySelects() { return $('form.visibility-form select') }
visibilitySelects().live('change', updateVisibilityDescription);
onHangersInit(function () {
visibilitySelects().each(updateVisibilityDescription);
});
/*
Help
*/
$('#toggle-help').click(function () {
$('#closet-hangers-help').toggleClass('hidden');
});
/*
Share URL
*/
$('#closet-hangers-share-box').mouseover(function () {
$(this).focus();
}).mouseout(function () {
$(this).blur();
});
/*
Initialize
*/
hangersInit();
})();

View file

@ -260,3 +260,23 @@ window.MainWardrobe = {View: {Outfit: {setFlashIsReady: previewSWFIsReady}}}
var SWFLog = $.noop;
/*
Trade hangers
*/
$(document.body).addClass('js');
$('#trade-hangers p').wrapInner('<div/>').each(function () {
var el = $(this);
if(el.height() < el.children().height()) {
el.addClass('overflows');
}
});
$('#trade-hangers .toggle').click(function () {
$(this).closest('p').toggleClass('showing-more');
});

View file

@ -0,0 +1,175 @@
/*!
* jQuery UI 1.8.14
*
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI
*/
(function(c,j){function k(a,b){var d=a.nodeName.toLowerCase();if("area"===d){b=a.parentNode;d=b.name;if(!a.href||!d||b.nodeName.toLowerCase()!=="map")return false;a=c("img[usemap=#"+d+"]")[0];return!!a&&l(a)}return(/input|select|textarea|button|object/.test(d)?!a.disabled:"a"==d?a.href||b:b)&&l(a)}function l(a){return!c(a).parents().andSelf().filter(function(){return c.curCSS(this,"visibility")==="hidden"||c.expr.filters.hidden(this)}).length}c.ui=c.ui||{};if(!c.ui.version){c.extend(c.ui,{version:"1.8.14",
keyCode:{ALT:18,BACKSPACE:8,CAPS_LOCK:20,COMMA:188,COMMAND:91,COMMAND_LEFT:91,COMMAND_RIGHT:93,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,MENU:93,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38,WINDOWS:91}});c.fn.extend({_focus:c.fn.focus,focus:function(a,b){return typeof a==="number"?this.each(function(){var d=this;setTimeout(function(){c(d).focus();
b&&b.call(d)},a)}):this._focus.apply(this,arguments)},scrollParent:function(){var a;a=c.browser.msie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?this.parents().filter(function(){return/(relative|absolute|fixed)/.test(c.curCSS(this,"position",1))&&/(auto|scroll)/.test(c.curCSS(this,"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0):this.parents().filter(function(){return/(auto|scroll)/.test(c.curCSS(this,"overflow",1)+c.curCSS(this,
"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0);return/fixed/.test(this.css("position"))||!a.length?c(document):a},zIndex:function(a){if(a!==j)return this.css("zIndex",a);if(this.length){a=c(this[0]);for(var b;a.length&&a[0]!==document;){b=a.css("position");if(b==="absolute"||b==="relative"||b==="fixed"){b=parseInt(a.css("zIndex"),10);if(!isNaN(b)&&b!==0)return b}a=a.parent()}}return 0},disableSelection:function(){return this.bind((c.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection",
function(a){a.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}});c.each(["Width","Height"],function(a,b){function d(f,g,m,n){c.each(e,function(){g-=parseFloat(c.curCSS(f,"padding"+this,true))||0;if(m)g-=parseFloat(c.curCSS(f,"border"+this+"Width",true))||0;if(n)g-=parseFloat(c.curCSS(f,"margin"+this,true))||0});return g}var e=b==="Width"?["Left","Right"]:["Top","Bottom"],h=b.toLowerCase(),i={innerWidth:c.fn.innerWidth,innerHeight:c.fn.innerHeight,outerWidth:c.fn.outerWidth,
outerHeight:c.fn.outerHeight};c.fn["inner"+b]=function(f){if(f===j)return i["inner"+b].call(this);return this.each(function(){c(this).css(h,d(this,f)+"px")})};c.fn["outer"+b]=function(f,g){if(typeof f!=="number")return i["outer"+b].call(this,f);return this.each(function(){c(this).css(h,d(this,f,true,g)+"px")})}});c.extend(c.expr[":"],{data:function(a,b,d){return!!c.data(a,d[3])},focusable:function(a){return k(a,!isNaN(c.attr(a,"tabindex")))},tabbable:function(a){var b=c.attr(a,"tabindex"),d=isNaN(b);
return(d||b>=0)&&k(a,!d)}});c(function(){var a=document.body,b=a.appendChild(b=document.createElement("div"));c.extend(b.style,{minHeight:"100px",height:"auto",padding:0,borderWidth:0});c.support.minHeight=b.offsetHeight===100;c.support.selectstart="onselectstart"in b;a.removeChild(b).style.display="none"});c.extend(c.ui,{plugin:{add:function(a,b,d){a=c.ui[a].prototype;for(var e in d){a.plugins[e]=a.plugins[e]||[];a.plugins[e].push([b,d[e]])}},call:function(a,b,d){if((b=a.plugins[b])&&a.element[0].parentNode)for(var e=
0;e<b.length;e++)a.options[b[e][0]]&&b[e][1].apply(a.element,d)}},contains:function(a,b){return document.compareDocumentPosition?a.compareDocumentPosition(b)&16:a!==b&&a.contains(b)},hasScroll:function(a,b){if(c(a).css("overflow")==="hidden")return false;b=b&&b==="left"?"scrollLeft":"scrollTop";var d=false;if(a[b]>0)return true;a[b]=1;d=a[b]>0;a[b]=0;return d},isOverAxis:function(a,b,d){return a>b&&a<b+d},isOver:function(a,b,d,e,h,i){return c.ui.isOverAxis(a,d,h)&&c.ui.isOverAxis(b,e,i)}})}})(jQuery);
;/*!
* jQuery UI Widget 1.8.14
*
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Widget
*/
(function(b,j){if(b.cleanData){var k=b.cleanData;b.cleanData=function(a){for(var c=0,d;(d=a[c])!=null;c++)b(d).triggerHandler("remove");k(a)}}else{var l=b.fn.remove;b.fn.remove=function(a,c){return this.each(function(){if(!c)if(!a||b.filter(a,[this]).length)b("*",this).add([this]).each(function(){b(this).triggerHandler("remove")});return l.call(b(this),a,c)})}}b.widget=function(a,c,d){var e=a.split(".")[0],f;a=a.split(".")[1];f=e+"-"+a;if(!d){d=c;c=b.Widget}b.expr[":"][f]=function(h){return!!b.data(h,
a)};b[e]=b[e]||{};b[e][a]=function(h,g){arguments.length&&this._createWidget(h,g)};c=new c;c.options=b.extend(true,{},c.options);b[e][a].prototype=b.extend(true,c,{namespace:e,widgetName:a,widgetEventPrefix:b[e][a].prototype.widgetEventPrefix||a,widgetBaseClass:f},d);b.widget.bridge(a,b[e][a])};b.widget.bridge=function(a,c){b.fn[a]=function(d){var e=typeof d==="string",f=Array.prototype.slice.call(arguments,1),h=this;d=!e&&f.length?b.extend.apply(null,[true,d].concat(f)):d;if(e&&d.charAt(0)==="_")return h;
e?this.each(function(){var g=b.data(this,a),i=g&&b.isFunction(g[d])?g[d].apply(g,f):g;if(i!==g&&i!==j){h=i;return false}}):this.each(function(){var g=b.data(this,a);g?g.option(d||{})._init():b.data(this,a,new c(d,this))});return h}};b.Widget=function(a,c){arguments.length&&this._createWidget(a,c)};b.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",options:{disabled:false},_createWidget:function(a,c){b.data(c,this.widgetName,this);this.element=b(c);this.options=b.extend(true,{},this.options,
this._getCreateOptions(),a);var d=this;this.element.bind("remove."+this.widgetName,function(){d.destroy()});this._create();this._trigger("create");this._init()},_getCreateOptions:function(){return b.metadata&&b.metadata.get(this.element[0])[this.widgetName]},_create:function(){},_init:function(){},destroy:function(){this.element.unbind("."+this.widgetName).removeData(this.widgetName);this.widget().unbind("."+this.widgetName).removeAttr("aria-disabled").removeClass(this.widgetBaseClass+"-disabled ui-state-disabled")},
widget:function(){return this.element},option:function(a,c){var d=a;if(arguments.length===0)return b.extend({},this.options);if(typeof a==="string"){if(c===j)return this.options[a];d={};d[a]=c}this._setOptions(d);return this},_setOptions:function(a){var c=this;b.each(a,function(d,e){c._setOption(d,e)});return this},_setOption:function(a,c){this.options[a]=c;if(a==="disabled")this.widget()[c?"addClass":"removeClass"](this.widgetBaseClass+"-disabled ui-state-disabled").attr("aria-disabled",c);return this},
enable:function(){return this._setOption("disabled",false)},disable:function(){return this._setOption("disabled",true)},_trigger:function(a,c,d){var e=this.options[a];c=b.Event(c);c.type=(a===this.widgetEventPrefix?a:this.widgetEventPrefix+a).toLowerCase();d=d||{};if(c.originalEvent){a=b.event.props.length;for(var f;a;){f=b.event.props[--a];c[f]=c.originalEvent[f]}}this.element.trigger(c,d);return!(b.isFunction(e)&&e.call(this.element[0],c,d)===false||c.isDefaultPrevented())}}})(jQuery);
;/*!
* jQuery UI Mouse 1.8.14
*
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Mouse
*
* Depends:
* jquery.ui.widget.js
*/
(function(b){var d=false;b(document).mousedown(function(){d=false});b.widget("ui.mouse",{options:{cancel:":input,option",distance:1,delay:0},_mouseInit:function(){var a=this;this.element.bind("mousedown."+this.widgetName,function(c){return a._mouseDown(c)}).bind("click."+this.widgetName,function(c){if(true===b.data(c.target,a.widgetName+".preventClickEvent")){b.removeData(c.target,a.widgetName+".preventClickEvent");c.stopImmediatePropagation();return false}});this.started=false},_mouseDestroy:function(){this.element.unbind("."+
this.widgetName)},_mouseDown:function(a){if(!d){this._mouseStarted&&this._mouseUp(a);this._mouseDownEvent=a;var c=this,f=a.which==1,g=typeof this.options.cancel=="string"?b(a.target).closest(this.options.cancel).length:false;if(!f||g||!this._mouseCapture(a))return true;this.mouseDelayMet=!this.options.delay;if(!this.mouseDelayMet)this._mouseDelayTimer=setTimeout(function(){c.mouseDelayMet=true},this.options.delay);if(this._mouseDistanceMet(a)&&this._mouseDelayMet(a)){this._mouseStarted=this._mouseStart(a)!==
false;if(!this._mouseStarted){a.preventDefault();return true}}true===b.data(a.target,this.widgetName+".preventClickEvent")&&b.removeData(a.target,this.widgetName+".preventClickEvent");this._mouseMoveDelegate=function(e){return c._mouseMove(e)};this._mouseUpDelegate=function(e){return c._mouseUp(e)};b(document).bind("mousemove."+this.widgetName,this._mouseMoveDelegate).bind("mouseup."+this.widgetName,this._mouseUpDelegate);a.preventDefault();return d=true}},_mouseMove:function(a){if(b.browser.msie&&
!(document.documentMode>=9)&&!a.button)return this._mouseUp(a);if(this._mouseStarted){this._mouseDrag(a);return a.preventDefault()}if(this._mouseDistanceMet(a)&&this._mouseDelayMet(a))(this._mouseStarted=this._mouseStart(this._mouseDownEvent,a)!==false)?this._mouseDrag(a):this._mouseUp(a);return!this._mouseStarted},_mouseUp:function(a){b(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate);if(this._mouseStarted){this._mouseStarted=
false;a.target==this._mouseDownEvent.target&&b.data(a.target,this.widgetName+".preventClickEvent",true);this._mouseStop(a)}return false},_mouseDistanceMet:function(a){return Math.max(Math.abs(this._mouseDownEvent.pageX-a.pageX),Math.abs(this._mouseDownEvent.pageY-a.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return true}})})(jQuery);
;/*
* jQuery UI Position 1.8.14
*
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Position
*/
(function(c){c.ui=c.ui||{};var n=/left|center|right/,o=/top|center|bottom/,t=c.fn.position,u=c.fn.offset;c.fn.position=function(b){if(!b||!b.of)return t.apply(this,arguments);b=c.extend({},b);var a=c(b.of),d=a[0],g=(b.collision||"flip").split(" "),e=b.offset?b.offset.split(" "):[0,0],h,k,j;if(d.nodeType===9){h=a.width();k=a.height();j={top:0,left:0}}else if(d.setTimeout){h=a.width();k=a.height();j={top:a.scrollTop(),left:a.scrollLeft()}}else if(d.preventDefault){b.at="left top";h=k=0;j={top:b.of.pageY,
left:b.of.pageX}}else{h=a.outerWidth();k=a.outerHeight();j=a.offset()}c.each(["my","at"],function(){var f=(b[this]||"").split(" ");if(f.length===1)f=n.test(f[0])?f.concat(["center"]):o.test(f[0])?["center"].concat(f):["center","center"];f[0]=n.test(f[0])?f[0]:"center";f[1]=o.test(f[1])?f[1]:"center";b[this]=f});if(g.length===1)g[1]=g[0];e[0]=parseInt(e[0],10)||0;if(e.length===1)e[1]=e[0];e[1]=parseInt(e[1],10)||0;if(b.at[0]==="right")j.left+=h;else if(b.at[0]==="center")j.left+=h/2;if(b.at[1]==="bottom")j.top+=
k;else if(b.at[1]==="center")j.top+=k/2;j.left+=e[0];j.top+=e[1];return this.each(function(){var f=c(this),l=f.outerWidth(),m=f.outerHeight(),p=parseInt(c.curCSS(this,"marginLeft",true))||0,q=parseInt(c.curCSS(this,"marginTop",true))||0,v=l+p+(parseInt(c.curCSS(this,"marginRight",true))||0),w=m+q+(parseInt(c.curCSS(this,"marginBottom",true))||0),i=c.extend({},j),r;if(b.my[0]==="right")i.left-=l;else if(b.my[0]==="center")i.left-=l/2;if(b.my[1]==="bottom")i.top-=m;else if(b.my[1]==="center")i.top-=
m/2;i.left=Math.round(i.left);i.top=Math.round(i.top);r={left:i.left-p,top:i.top-q};c.each(["left","top"],function(s,x){c.ui.position[g[s]]&&c.ui.position[g[s]][x](i,{targetWidth:h,targetHeight:k,elemWidth:l,elemHeight:m,collisionPosition:r,collisionWidth:v,collisionHeight:w,offset:e,my:b.my,at:b.at})});c.fn.bgiframe&&f.bgiframe();f.offset(c.extend(i,{using:b.using}))})};c.ui.position={fit:{left:function(b,a){var d=c(window);d=a.collisionPosition.left+a.collisionWidth-d.width()-d.scrollLeft();b.left=
d>0?b.left-d:Math.max(b.left-a.collisionPosition.left,b.left)},top:function(b,a){var d=c(window);d=a.collisionPosition.top+a.collisionHeight-d.height()-d.scrollTop();b.top=d>0?b.top-d:Math.max(b.top-a.collisionPosition.top,b.top)}},flip:{left:function(b,a){if(a.at[0]!=="center"){var d=c(window);d=a.collisionPosition.left+a.collisionWidth-d.width()-d.scrollLeft();var g=a.my[0]==="left"?-a.elemWidth:a.my[0]==="right"?a.elemWidth:0,e=a.at[0]==="left"?a.targetWidth:-a.targetWidth,h=-2*a.offset[0];b.left+=
a.collisionPosition.left<0?g+e+h:d>0?g+e+h:0}},top:function(b,a){if(a.at[1]!=="center"){var d=c(window);d=a.collisionPosition.top+a.collisionHeight-d.height()-d.scrollTop();var g=a.my[1]==="top"?-a.elemHeight:a.my[1]==="bottom"?a.elemHeight:0,e=a.at[1]==="top"?a.targetHeight:-a.targetHeight,h=-2*a.offset[1];b.top+=a.collisionPosition.top<0?g+e+h:d>0?g+e+h:0}}}};if(!c.offset.setOffset){c.offset.setOffset=function(b,a){if(/static/.test(c.curCSS(b,"position")))b.style.position="relative";var d=c(b),
g=d.offset(),e=parseInt(c.curCSS(b,"top",true),10)||0,h=parseInt(c.curCSS(b,"left",true),10)||0;g={top:a.top-g.top+e,left:a.left-g.left+h};"using"in a?a.using.call(b,g):d.css(g)};c.fn.offset=function(b){var a=this[0];if(!a||!a.ownerDocument)return null;if(b)return this.each(function(){c.offset.setOffset(this,b)});return u.call(this)}}})(jQuery);
;/*
* jQuery UI Draggable 1.8.14
*
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Draggables
*
* Depends:
* jquery.ui.core.js
* jquery.ui.mouse.js
* jquery.ui.widget.js
*/
(function(d){d.widget("ui.draggable",d.ui.mouse,{widgetEventPrefix:"drag",options:{addClasses:true,appendTo:"parent",axis:false,connectToSortable:false,containment:false,cursor:"auto",cursorAt:false,grid:false,handle:false,helper:"original",iframeFix:false,opacity:false,refreshPositions:false,revert:false,revertDuration:500,scope:"default",scroll:true,scrollSensitivity:20,scrollSpeed:20,snap:false,snapMode:"both",snapTolerance:20,stack:false,zIndex:false},_create:function(){if(this.options.helper==
"original"&&!/^(?:r|a|f)/.test(this.element.css("position")))this.element[0].style.position="relative";this.options.addClasses&&this.element.addClass("ui-draggable");this.options.disabled&&this.element.addClass("ui-draggable-disabled");this._mouseInit()},destroy:function(){if(this.element.data("draggable")){this.element.removeData("draggable").unbind(".draggable").removeClass("ui-draggable ui-draggable-dragging ui-draggable-disabled");this._mouseDestroy();return this}},_mouseCapture:function(a){var b=
this.options;if(this.helper||b.disabled||d(a.target).is(".ui-resizable-handle"))return false;this.handle=this._getHandle(a);if(!this.handle)return false;d(b.iframeFix===true?"iframe":b.iframeFix).each(function(){d('<div class="ui-draggable-iframeFix" style="background: #fff;"></div>').css({width:this.offsetWidth+"px",height:this.offsetHeight+"px",position:"absolute",opacity:"0.001",zIndex:1E3}).css(d(this).offset()).appendTo("body")});return true},_mouseStart:function(a){var b=this.options;this.helper=
this._createHelper(a);this._cacheHelperProportions();if(d.ui.ddmanager)d.ui.ddmanager.current=this;this._cacheMargins();this.cssPosition=this.helper.css("position");this.scrollParent=this.helper.scrollParent();this.offset=this.positionAbs=this.element.offset();this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left};d.extend(this.offset,{click:{left:a.pageX-this.offset.left,top:a.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()});
this.originalPosition=this.position=this._generatePosition(a);this.originalPageX=a.pageX;this.originalPageY=a.pageY;b.cursorAt&&this._adjustOffsetFromHelper(b.cursorAt);b.containment&&this._setContainment();if(this._trigger("start",a)===false){this._clear();return false}this._cacheHelperProportions();d.ui.ddmanager&&!b.dropBehaviour&&d.ui.ddmanager.prepareOffsets(this,a);this.helper.addClass("ui-draggable-dragging");this._mouseDrag(a,true);d.ui.ddmanager&&d.ui.ddmanager.dragStart(this,a);return true},
_mouseDrag:function(a,b){this.position=this._generatePosition(a);this.positionAbs=this._convertPositionTo("absolute");if(!b){b=this._uiHash();if(this._trigger("drag",a,b)===false){this._mouseUp({});return false}this.position=b.position}if(!this.options.axis||this.options.axis!="y")this.helper[0].style.left=this.position.left+"px";if(!this.options.axis||this.options.axis!="x")this.helper[0].style.top=this.position.top+"px";d.ui.ddmanager&&d.ui.ddmanager.drag(this,a);return false},_mouseStop:function(a){var b=
false;if(d.ui.ddmanager&&!this.options.dropBehaviour)b=d.ui.ddmanager.drop(this,a);if(this.dropped){b=this.dropped;this.dropped=false}if((!this.element[0]||!this.element[0].parentNode)&&this.options.helper=="original")return false;if(this.options.revert=="invalid"&&!b||this.options.revert=="valid"&&b||this.options.revert===true||d.isFunction(this.options.revert)&&this.options.revert.call(this.element,b)){var c=this;d(this.helper).animate(this.originalPosition,parseInt(this.options.revertDuration,
10),function(){c._trigger("stop",a)!==false&&c._clear()})}else this._trigger("stop",a)!==false&&this._clear();return false},_mouseUp:function(a){this.options.iframeFix===true&&d("div.ui-draggable-iframeFix").each(function(){this.parentNode.removeChild(this)});d.ui.ddmanager&&d.ui.ddmanager.dragStop(this,a);return d.ui.mouse.prototype._mouseUp.call(this,a)},cancel:function(){this.helper.is(".ui-draggable-dragging")?this._mouseUp({}):this._clear();return this},_getHandle:function(a){var b=!this.options.handle||
!d(this.options.handle,this.element).length?true:false;d(this.options.handle,this.element).find("*").andSelf().each(function(){if(this==a.target)b=true});return b},_createHelper:function(a){var b=this.options;a=d.isFunction(b.helper)?d(b.helper.apply(this.element[0],[a])):b.helper=="clone"?this.element.clone().removeAttr("id"):this.element;a.parents("body").length||a.appendTo(b.appendTo=="parent"?this.element[0].parentNode:b.appendTo);a[0]!=this.element[0]&&!/(fixed|absolute)/.test(a.css("position"))&&
a.css("position","absolute");return a},_adjustOffsetFromHelper:function(a){if(typeof a=="string")a=a.split(" ");if(d.isArray(a))a={left:+a[0],top:+a[1]||0};if("left"in a)this.offset.click.left=a.left+this.margins.left;if("right"in a)this.offset.click.left=this.helperProportions.width-a.right+this.margins.left;if("top"in a)this.offset.click.top=a.top+this.margins.top;if("bottom"in a)this.offset.click.top=this.helperProportions.height-a.bottom+this.margins.top},_getParentOffset:function(){this.offsetParent=
this.helper.offsetParent();var a=this.offsetParent.offset();if(this.cssPosition=="absolute"&&this.scrollParent[0]!=document&&d.ui.contains(this.scrollParent[0],this.offsetParent[0])){a.left+=this.scrollParent.scrollLeft();a.top+=this.scrollParent.scrollTop()}if(this.offsetParent[0]==document.body||this.offsetParent[0].tagName&&this.offsetParent[0].tagName.toLowerCase()=="html"&&d.browser.msie)a={top:0,left:0};return{top:a.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:a.left+(parseInt(this.offsetParent.css("borderLeftWidth"),
10)||0)}},_getRelativeOffset:function(){if(this.cssPosition=="relative"){var a=this.element.position();return{top:a.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:a.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}else return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.element.css("marginLeft"),10)||0,top:parseInt(this.element.css("marginTop"),10)||0,right:parseInt(this.element.css("marginRight"),10)||0,bottom:parseInt(this.element.css("marginBottom"),
10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var a=this.options;if(a.containment=="parent")a.containment=this.helper[0].parentNode;if(a.containment=="document"||a.containment=="window")this.containment=[a.containment=="document"?0:d(window).scrollLeft()-this.offset.relative.left-this.offset.parent.left,a.containment=="document"?0:d(window).scrollTop()-this.offset.relative.top-this.offset.parent.top,
(a.containment=="document"?0:d(window).scrollLeft())+d(a.containment=="document"?document:window).width()-this.helperProportions.width-this.margins.left,(a.containment=="document"?0:d(window).scrollTop())+(d(a.containment=="document"?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top];if(!/^(document|window|parent)$/.test(a.containment)&&a.containment.constructor!=Array){a=d(a.containment);var b=a[0];if(b){a.offset();var c=d(b).css("overflow")!=
"hidden";this.containment=[(parseInt(d(b).css("borderLeftWidth"),10)||0)+(parseInt(d(b).css("paddingLeft"),10)||0),(parseInt(d(b).css("borderTopWidth"),10)||0)+(parseInt(d(b).css("paddingTop"),10)||0),(c?Math.max(b.scrollWidth,b.offsetWidth):b.offsetWidth)-(parseInt(d(b).css("borderLeftWidth"),10)||0)-(parseInt(d(b).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left-this.margins.right,(c?Math.max(b.scrollHeight,b.offsetHeight):b.offsetHeight)-(parseInt(d(b).css("borderTopWidth"),
10)||0)-(parseInt(d(b).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top-this.margins.bottom];this.relative_container=a}}else if(a.containment.constructor==Array)this.containment=a.containment},_convertPositionTo:function(a,b){if(!b)b=this.position;a=a=="absolute"?1:-1;var c=this.cssPosition=="absolute"&&!(this.scrollParent[0]!=document&&d.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,f=/(html|body)/i.test(c[0].tagName);return{top:b.top+
this.offset.relative.top*a+this.offset.parent.top*a-(d.browser.safari&&d.browser.version<526&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():f?0:c.scrollTop())*a),left:b.left+this.offset.relative.left*a+this.offset.parent.left*a-(d.browser.safari&&d.browser.version<526&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():f?0:c.scrollLeft())*a)}},_generatePosition:function(a){var b=this.options,c=this.cssPosition=="absolute"&&
!(this.scrollParent[0]!=document&&d.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,f=/(html|body)/i.test(c[0].tagName),e=a.pageX,h=a.pageY;if(this.originalPosition){var g;if(this.containment){if(this.relative_container){g=this.relative_container.offset();g=[this.containment[0]+g.left,this.containment[1]+g.top,this.containment[2]+g.left,this.containment[3]+g.top]}else g=this.containment;if(a.pageX-this.offset.click.left<g[0])e=g[0]+this.offset.click.left;
if(a.pageY-this.offset.click.top<g[1])h=g[1]+this.offset.click.top;if(a.pageX-this.offset.click.left>g[2])e=g[2]+this.offset.click.left;if(a.pageY-this.offset.click.top>g[3])h=g[3]+this.offset.click.top}if(b.grid){h=b.grid[1]?this.originalPageY+Math.round((h-this.originalPageY)/b.grid[1])*b.grid[1]:this.originalPageY;h=g?!(h-this.offset.click.top<g[1]||h-this.offset.click.top>g[3])?h:!(h-this.offset.click.top<g[1])?h-b.grid[1]:h+b.grid[1]:h;e=b.grid[0]?this.originalPageX+Math.round((e-this.originalPageX)/
b.grid[0])*b.grid[0]:this.originalPageX;e=g?!(e-this.offset.click.left<g[0]||e-this.offset.click.left>g[2])?e:!(e-this.offset.click.left<g[0])?e-b.grid[0]:e+b.grid[0]:e}}return{top:h-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+(d.browser.safari&&d.browser.version<526&&this.cssPosition=="fixed"?0:this.cssPosition=="fixed"?-this.scrollParent.scrollTop():f?0:c.scrollTop()),left:e-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+(d.browser.safari&&d.browser.version<
526&&this.cssPosition=="fixed"?0:this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():f?0:c.scrollLeft())}},_clear:function(){this.helper.removeClass("ui-draggable-dragging");this.helper[0]!=this.element[0]&&!this.cancelHelperRemoval&&this.helper.remove();this.helper=null;this.cancelHelperRemoval=false},_trigger:function(a,b,c){c=c||this._uiHash();d.ui.plugin.call(this,a,[b,c]);if(a=="drag")this.positionAbs=this._convertPositionTo("absolute");return d.Widget.prototype._trigger.call(this,a,b,
c)},plugins:{},_uiHash:function(){return{helper:this.helper,position:this.position,originalPosition:this.originalPosition,offset:this.positionAbs}}});d.extend(d.ui.draggable,{version:"1.8.14"});d.ui.plugin.add("draggable","connectToSortable",{start:function(a,b){var c=d(this).data("draggable"),f=c.options,e=d.extend({},b,{item:c.element});c.sortables=[];d(f.connectToSortable).each(function(){var h=d.data(this,"sortable");if(h&&!h.options.disabled){c.sortables.push({instance:h,shouldRevert:h.options.revert});
h.refreshPositions();h._trigger("activate",a,e)}})},stop:function(a,b){var c=d(this).data("draggable"),f=d.extend({},b,{item:c.element});d.each(c.sortables,function(){if(this.instance.isOver){this.instance.isOver=0;c.cancelHelperRemoval=true;this.instance.cancelHelperRemoval=false;if(this.shouldRevert)this.instance.options.revert=true;this.instance._mouseStop(a);this.instance.options.helper=this.instance.options._helper;c.options.helper=="original"&&this.instance.currentItem.css({top:"auto",left:"auto"})}else{this.instance.cancelHelperRemoval=
false;this.instance._trigger("deactivate",a,f)}})},drag:function(a,b){var c=d(this).data("draggable"),f=this;d.each(c.sortables,function(){this.instance.positionAbs=c.positionAbs;this.instance.helperProportions=c.helperProportions;this.instance.offset.click=c.offset.click;if(this.instance._intersectsWith(this.instance.containerCache)){if(!this.instance.isOver){this.instance.isOver=1;this.instance.currentItem=d(f).clone().removeAttr("id").appendTo(this.instance.element).data("sortable-item",true);
this.instance.options._helper=this.instance.options.helper;this.instance.options.helper=function(){return b.helper[0]};a.target=this.instance.currentItem[0];this.instance._mouseCapture(a,true);this.instance._mouseStart(a,true,true);this.instance.offset.click.top=c.offset.click.top;this.instance.offset.click.left=c.offset.click.left;this.instance.offset.parent.left-=c.offset.parent.left-this.instance.offset.parent.left;this.instance.offset.parent.top-=c.offset.parent.top-this.instance.offset.parent.top;
c._trigger("toSortable",a);c.dropped=this.instance.element;c.currentItem=c.element;this.instance.fromOutside=c}this.instance.currentItem&&this.instance._mouseDrag(a)}else if(this.instance.isOver){this.instance.isOver=0;this.instance.cancelHelperRemoval=true;this.instance.options.revert=false;this.instance._trigger("out",a,this.instance._uiHash(this.instance));this.instance._mouseStop(a,true);this.instance.options.helper=this.instance.options._helper;this.instance.currentItem.remove();this.instance.placeholder&&
this.instance.placeholder.remove();c._trigger("fromSortable",a);c.dropped=false}})}});d.ui.plugin.add("draggable","cursor",{start:function(){var a=d("body"),b=d(this).data("draggable").options;if(a.css("cursor"))b._cursor=a.css("cursor");a.css("cursor",b.cursor)},stop:function(){var a=d(this).data("draggable").options;a._cursor&&d("body").css("cursor",a._cursor)}});d.ui.plugin.add("draggable","opacity",{start:function(a,b){a=d(b.helper);b=d(this).data("draggable").options;if(a.css("opacity"))b._opacity=
a.css("opacity");a.css("opacity",b.opacity)},stop:function(a,b){a=d(this).data("draggable").options;a._opacity&&d(b.helper).css("opacity",a._opacity)}});d.ui.plugin.add("draggable","scroll",{start:function(){var a=d(this).data("draggable");if(a.scrollParent[0]!=document&&a.scrollParent[0].tagName!="HTML")a.overflowOffset=a.scrollParent.offset()},drag:function(a){var b=d(this).data("draggable"),c=b.options,f=false;if(b.scrollParent[0]!=document&&b.scrollParent[0].tagName!="HTML"){if(!c.axis||c.axis!=
"x")if(b.overflowOffset.top+b.scrollParent[0].offsetHeight-a.pageY<c.scrollSensitivity)b.scrollParent[0].scrollTop=f=b.scrollParent[0].scrollTop+c.scrollSpeed;else if(a.pageY-b.overflowOffset.top<c.scrollSensitivity)b.scrollParent[0].scrollTop=f=b.scrollParent[0].scrollTop-c.scrollSpeed;if(!c.axis||c.axis!="y")if(b.overflowOffset.left+b.scrollParent[0].offsetWidth-a.pageX<c.scrollSensitivity)b.scrollParent[0].scrollLeft=f=b.scrollParent[0].scrollLeft+c.scrollSpeed;else if(a.pageX-b.overflowOffset.left<
c.scrollSensitivity)b.scrollParent[0].scrollLeft=f=b.scrollParent[0].scrollLeft-c.scrollSpeed}else{if(!c.axis||c.axis!="x")if(a.pageY-d(document).scrollTop()<c.scrollSensitivity)f=d(document).scrollTop(d(document).scrollTop()-c.scrollSpeed);else if(d(window).height()-(a.pageY-d(document).scrollTop())<c.scrollSensitivity)f=d(document).scrollTop(d(document).scrollTop()+c.scrollSpeed);if(!c.axis||c.axis!="y")if(a.pageX-d(document).scrollLeft()<c.scrollSensitivity)f=d(document).scrollLeft(d(document).scrollLeft()-
c.scrollSpeed);else if(d(window).width()-(a.pageX-d(document).scrollLeft())<c.scrollSensitivity)f=d(document).scrollLeft(d(document).scrollLeft()+c.scrollSpeed)}f!==false&&d.ui.ddmanager&&!c.dropBehaviour&&d.ui.ddmanager.prepareOffsets(b,a)}});d.ui.plugin.add("draggable","snap",{start:function(){var a=d(this).data("draggable"),b=a.options;a.snapElements=[];d(b.snap.constructor!=String?b.snap.items||":data(draggable)":b.snap).each(function(){var c=d(this),f=c.offset();this!=a.element[0]&&a.snapElements.push({item:this,
width:c.outerWidth(),height:c.outerHeight(),top:f.top,left:f.left})})},drag:function(a,b){for(var c=d(this).data("draggable"),f=c.options,e=f.snapTolerance,h=b.offset.left,g=h+c.helperProportions.width,n=b.offset.top,o=n+c.helperProportions.height,i=c.snapElements.length-1;i>=0;i--){var j=c.snapElements[i].left,l=j+c.snapElements[i].width,k=c.snapElements[i].top,m=k+c.snapElements[i].height;if(j-e<h&&h<l+e&&k-e<n&&n<m+e||j-e<h&&h<l+e&&k-e<o&&o<m+e||j-e<g&&g<l+e&&k-e<n&&n<m+e||j-e<g&&g<l+e&&k-e<o&&
o<m+e){if(f.snapMode!="inner"){var p=Math.abs(k-o)<=e,q=Math.abs(m-n)<=e,r=Math.abs(j-g)<=e,s=Math.abs(l-h)<=e;if(p)b.position.top=c._convertPositionTo("relative",{top:k-c.helperProportions.height,left:0}).top-c.margins.top;if(q)b.position.top=c._convertPositionTo("relative",{top:m,left:0}).top-c.margins.top;if(r)b.position.left=c._convertPositionTo("relative",{top:0,left:j-c.helperProportions.width}).left-c.margins.left;if(s)b.position.left=c._convertPositionTo("relative",{top:0,left:l}).left-c.margins.left}var t=
p||q||r||s;if(f.snapMode!="outer"){p=Math.abs(k-n)<=e;q=Math.abs(m-o)<=e;r=Math.abs(j-h)<=e;s=Math.abs(l-g)<=e;if(p)b.position.top=c._convertPositionTo("relative",{top:k,left:0}).top-c.margins.top;if(q)b.position.top=c._convertPositionTo("relative",{top:m-c.helperProportions.height,left:0}).top-c.margins.top;if(r)b.position.left=c._convertPositionTo("relative",{top:0,left:j}).left-c.margins.left;if(s)b.position.left=c._convertPositionTo("relative",{top:0,left:l-c.helperProportions.width}).left-c.margins.left}if(!c.snapElements[i].snapping&&
(p||q||r||s||t))c.options.snap.snap&&c.options.snap.snap.call(c.element,a,d.extend(c._uiHash(),{snapItem:c.snapElements[i].item}));c.snapElements[i].snapping=p||q||r||s||t}else{c.snapElements[i].snapping&&c.options.snap.release&&c.options.snap.release.call(c.element,a,d.extend(c._uiHash(),{snapItem:c.snapElements[i].item}));c.snapElements[i].snapping=false}}}});d.ui.plugin.add("draggable","stack",{start:function(){var a=d(this).data("draggable").options;a=d.makeArray(d(a.stack)).sort(function(c,f){return(parseInt(d(c).css("zIndex"),
10)||0)-(parseInt(d(f).css("zIndex"),10)||0)});if(a.length){var b=parseInt(a[0].style.zIndex)||0;d(a).each(function(c){this.style.zIndex=b+c});this[0].style.zIndex=b+a.length}}});d.ui.plugin.add("draggable","zIndex",{start:function(a,b){a=d(b.helper);b=d(this).data("draggable").options;if(a.css("zIndex"))b._zIndex=a.css("zIndex");a.css("zIndex",b.zIndex)},stop:function(a,b){a=d(this).data("draggable").options;a._zIndex&&d(b.helper).css("zIndex",a._zIndex)}})})(jQuery);
;/*
* jQuery UI Droppable 1.8.14
*
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Droppables
*
* Depends:
* jquery.ui.core.js
* jquery.ui.widget.js
* jquery.ui.mouse.js
* jquery.ui.draggable.js
*/
(function(d){d.widget("ui.droppable",{widgetEventPrefix:"drop",options:{accept:"*",activeClass:false,addClasses:true,greedy:false,hoverClass:false,scope:"default",tolerance:"intersect"},_create:function(){var a=this.options,b=a.accept;this.isover=0;this.isout=1;this.accept=d.isFunction(b)?b:function(c){return c.is(b)};this.proportions={width:this.element[0].offsetWidth,height:this.element[0].offsetHeight};d.ui.ddmanager.droppables[a.scope]=d.ui.ddmanager.droppables[a.scope]||[];d.ui.ddmanager.droppables[a.scope].push(this);
a.addClasses&&this.element.addClass("ui-droppable")},destroy:function(){for(var a=d.ui.ddmanager.droppables[this.options.scope],b=0;b<a.length;b++)a[b]==this&&a.splice(b,1);this.element.removeClass("ui-droppable ui-droppable-disabled").removeData("droppable").unbind(".droppable");return this},_setOption:function(a,b){if(a=="accept")this.accept=d.isFunction(b)?b:function(c){return c.is(b)};d.Widget.prototype._setOption.apply(this,arguments)},_activate:function(a){var b=d.ui.ddmanager.current;this.options.activeClass&&
this.element.addClass(this.options.activeClass);b&&this._trigger("activate",a,this.ui(b))},_deactivate:function(a){var b=d.ui.ddmanager.current;this.options.activeClass&&this.element.removeClass(this.options.activeClass);b&&this._trigger("deactivate",a,this.ui(b))},_over:function(a){var b=d.ui.ddmanager.current;if(!(!b||(b.currentItem||b.element)[0]==this.element[0]))if(this.accept.call(this.element[0],b.currentItem||b.element)){this.options.hoverClass&&this.element.addClass(this.options.hoverClass);
this._trigger("over",a,this.ui(b))}},_out:function(a){var b=d.ui.ddmanager.current;if(!(!b||(b.currentItem||b.element)[0]==this.element[0]))if(this.accept.call(this.element[0],b.currentItem||b.element)){this.options.hoverClass&&this.element.removeClass(this.options.hoverClass);this._trigger("out",a,this.ui(b))}},_drop:function(a,b){var c=b||d.ui.ddmanager.current;if(!c||(c.currentItem||c.element)[0]==this.element[0])return false;var e=false;this.element.find(":data(droppable)").not(".ui-draggable-dragging").each(function(){var g=
d.data(this,"droppable");if(g.options.greedy&&!g.options.disabled&&g.options.scope==c.options.scope&&g.accept.call(g.element[0],c.currentItem||c.element)&&d.ui.intersect(c,d.extend(g,{offset:g.element.offset()}),g.options.tolerance)){e=true;return false}});if(e)return false;if(this.accept.call(this.element[0],c.currentItem||c.element)){this.options.activeClass&&this.element.removeClass(this.options.activeClass);this.options.hoverClass&&this.element.removeClass(this.options.hoverClass);this._trigger("drop",
a,this.ui(c));return this.element}return false},ui:function(a){return{draggable:a.currentItem||a.element,helper:a.helper,position:a.position,offset:a.positionAbs}}});d.extend(d.ui.droppable,{version:"1.8.14"});d.ui.intersect=function(a,b,c){if(!b.offset)return false;var e=(a.positionAbs||a.position.absolute).left,g=e+a.helperProportions.width,f=(a.positionAbs||a.position.absolute).top,h=f+a.helperProportions.height,i=b.offset.left,k=i+b.proportions.width,j=b.offset.top,l=j+b.proportions.height;
switch(c){case "fit":return i<=e&&g<=k&&j<=f&&h<=l;case "intersect":return i<e+a.helperProportions.width/2&&g-a.helperProportions.width/2<k&&j<f+a.helperProportions.height/2&&h-a.helperProportions.height/2<l;case "pointer":return d.ui.isOver((a.positionAbs||a.position.absolute).top+(a.clickOffset||a.offset.click).top,(a.positionAbs||a.position.absolute).left+(a.clickOffset||a.offset.click).left,j,i,b.proportions.height,b.proportions.width);case "touch":return(f>=j&&f<=l||h>=j&&h<=l||f<j&&h>l)&&(e>=
i&&e<=k||g>=i&&g<=k||e<i&&g>k);default:return false}};d.ui.ddmanager={current:null,droppables:{"default":[]},prepareOffsets:function(a,b){var c=d.ui.ddmanager.droppables[a.options.scope]||[],e=b?b.type:null,g=(a.currentItem||a.element).find(":data(droppable)").andSelf(),f=0;a:for(;f<c.length;f++)if(!(c[f].options.disabled||a&&!c[f].accept.call(c[f].element[0],a.currentItem||a.element))){for(var h=0;h<g.length;h++)if(g[h]==c[f].element[0]){c[f].proportions.height=0;continue a}c[f].visible=c[f].element.css("display")!=
"none";if(c[f].visible){e=="mousedown"&&c[f]._activate.call(c[f],b);c[f].offset=c[f].element.offset();c[f].proportions={width:c[f].element[0].offsetWidth,height:c[f].element[0].offsetHeight}}}},drop:function(a,b){var c=false;d.each(d.ui.ddmanager.droppables[a.options.scope]||[],function(){if(this.options){if(!this.options.disabled&&this.visible&&d.ui.intersect(a,this,this.options.tolerance))c=c||this._drop.call(this,b);if(!this.options.disabled&&this.visible&&this.accept.call(this.element[0],a.currentItem||
a.element)){this.isout=1;this.isover=0;this._deactivate.call(this,b)}}});return c},dragStart:function(a,b){a.element.parentsUntil("body").bind("scroll.droppable",function(){a.options.refreshPositions||d.ui.ddmanager.prepareOffsets(a,b)})},drag:function(a,b){a.options.refreshPositions&&d.ui.ddmanager.prepareOffsets(a,b);d.each(d.ui.ddmanager.droppables[a.options.scope]||[],function(){if(!(this.options.disabled||this.greedyChild||!this.visible)){var c=d.ui.intersect(a,this,this.options.tolerance);if(c=
!c&&this.isover==1?"isout":c&&this.isover==0?"isover":null){var e;if(this.options.greedy){var g=this.element.parents(":data(droppable):eq(0)");if(g.length){e=d.data(g[0],"droppable");e.greedyChild=c=="isover"?1:0}}if(e&&c=="isover"){e.isover=0;e.isout=1;e._out.call(e,b)}this[c]=1;this[c=="isout"?"isover":"isout"]=0;this[c=="isover"?"_over":"_out"].call(this,b);if(e&&c=="isout"){e.isout=0;e.isover=1;e._over.call(e,b)}}}})},dragStop:function(a,b){a.element.parentsUntil("body").unbind("scroll.droppable");
a.options.refreshPositions||d.ui.ddmanager.prepareOffsets(a,b)}}})(jQuery);
;/*
* jQuery UI Autocomplete 1.8.14
*
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Autocomplete
*
* Depends:
* jquery.ui.core.js
* jquery.ui.widget.js
* jquery.ui.position.js
*/
(function(d){var e=0;d.widget("ui.autocomplete",{options:{appendTo:"body",autoFocus:false,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null},pending:0,_create:function(){var a=this,b=this.element[0].ownerDocument,g;this.element.addClass("ui-autocomplete-input").attr("autocomplete","off").attr({role:"textbox","aria-autocomplete":"list","aria-haspopup":"true"}).bind("keydown.autocomplete",function(c){if(!(a.options.disabled||a.element.attr("readonly"))){g=
false;var f=d.ui.keyCode;switch(c.keyCode){case f.PAGE_UP:a._move("previousPage",c);break;case f.PAGE_DOWN:a._move("nextPage",c);break;case f.UP:a._move("previous",c);c.preventDefault();break;case f.DOWN:a._move("next",c);c.preventDefault();break;case f.ENTER:case f.NUMPAD_ENTER:if(a.menu.active){g=true;c.preventDefault()}case f.TAB:if(!a.menu.active)return;a.menu.select(c);break;case f.ESCAPE:a.element.val(a.term);a.close(c);break;default:clearTimeout(a.searching);a.searching=setTimeout(function(){if(a.term!=
a.element.val()){a.selectedItem=null;a.search(null,c)}},a.options.delay);break}}}).bind("keypress.autocomplete",function(c){if(g){g=false;c.preventDefault()}}).bind("focus.autocomplete",function(){if(!a.options.disabled){a.selectedItem=null;a.previous=a.element.val()}}).bind("blur.autocomplete",function(c){if(!a.options.disabled){clearTimeout(a.searching);a.closing=setTimeout(function(){a.close(c);a._change(c)},150)}});this._initSource();this.response=function(){return a._response.apply(a,arguments)};
this.menu=d("<ul></ul>").addClass("ui-autocomplete").appendTo(d(this.options.appendTo||"body",b)[0]).mousedown(function(c){var f=a.menu.element[0];d(c.target).closest(".ui-menu-item").length||setTimeout(function(){d(document).one("mousedown",function(h){h.target!==a.element[0]&&h.target!==f&&!d.ui.contains(f,h.target)&&a.close()})},1);setTimeout(function(){clearTimeout(a.closing)},13)}).menu({focus:function(c,f){f=f.item.data("item.autocomplete");false!==a._trigger("focus",c,{item:f})&&/^key/.test(c.originalEvent.type)&&
a.element.val(f.value)},selected:function(c,f){var h=f.item.data("item.autocomplete"),i=a.previous;if(a.element[0]!==b.activeElement){a.element.focus();a.previous=i;setTimeout(function(){a.previous=i;a.selectedItem=h},1)}false!==a._trigger("select",c,{item:h})&&a.element.val(h.value);a.term=a.element.val();a.close(c);a.selectedItem=h},blur:function(){a.menu.element.is(":visible")&&a.element.val()!==a.term&&a.element.val(a.term)}}).zIndex(this.element.zIndex()+1).css({top:0,left:0}).hide().data("menu");
d.fn.bgiframe&&this.menu.element.bgiframe()},destroy:function(){this.element.removeClass("ui-autocomplete-input").removeAttr("autocomplete").removeAttr("role").removeAttr("aria-autocomplete").removeAttr("aria-haspopup");this.menu.element.remove();d.Widget.prototype.destroy.call(this)},_setOption:function(a,b){d.Widget.prototype._setOption.apply(this,arguments);a==="source"&&this._initSource();if(a==="appendTo")this.menu.element.appendTo(d(b||"body",this.element[0].ownerDocument)[0]);a==="disabled"&&
b&&this.xhr&&this.xhr.abort()},_initSource:function(){var a=this,b,g;if(d.isArray(this.options.source)){b=this.options.source;this.source=function(c,f){f(d.ui.autocomplete.filter(b,c.term))}}else if(typeof this.options.source==="string"){g=this.options.source;this.source=function(c,f){a.xhr&&a.xhr.abort();a.xhr=d.ajax({url:g,data:c,dataType:"json",autocompleteRequest:++e,success:function(h){this.autocompleteRequest===e&&f(h)},error:function(){this.autocompleteRequest===e&&f([])}})}}else this.source=
this.options.source},search:function(a,b){a=a!=null?a:this.element.val();this.term=this.element.val();if(a.length<this.options.minLength)return this.close(b);clearTimeout(this.closing);if(this._trigger("search",b)!==false)return this._search(a)},_search:function(a){this.pending++;this.element.addClass("ui-autocomplete-loading");this.source({term:a},this.response)},_response:function(a){if(!this.options.disabled&&a&&a.length){a=this._normalize(a);this._suggest(a);this._trigger("open")}else this.close();
this.pending--;this.pending||this.element.removeClass("ui-autocomplete-loading")},close:function(a){clearTimeout(this.closing);if(this.menu.element.is(":visible")){this.menu.element.hide();this.menu.deactivate();this._trigger("close",a)}},_change:function(a){this.previous!==this.element.val()&&this._trigger("change",a,{item:this.selectedItem})},_normalize:function(a){if(a.length&&a[0].label&&a[0].value)return a;return d.map(a,function(b){if(typeof b==="string")return{label:b,value:b};return d.extend({label:b.label||
b.value,value:b.value||b.label},b)})},_suggest:function(a){var b=this.menu.element.empty().zIndex(this.element.zIndex()+1);this._renderMenu(b,a);this.menu.deactivate();this.menu.refresh();b.show();this._resizeMenu();b.position(d.extend({of:this.element},this.options.position));this.options.autoFocus&&this.menu.next(new d.Event("mouseover"))},_resizeMenu:function(){var a=this.menu.element;a.outerWidth(Math.max(a.width("").outerWidth(),this.element.outerWidth()))},_renderMenu:function(a,b){var g=this;
d.each(b,function(c,f){g._renderItem(a,f)})},_renderItem:function(a,b){return d("<li></li>").data("item.autocomplete",b).append(d("<a></a>").text(b.label)).appendTo(a)},_move:function(a,b){if(this.menu.element.is(":visible"))if(this.menu.first()&&/^previous/.test(a)||this.menu.last()&&/^next/.test(a)){this.element.val(this.term);this.menu.deactivate()}else this.menu[a](b);else this.search(null,b)},widget:function(){return this.menu.element}});d.extend(d.ui.autocomplete,{escapeRegex:function(a){return a.replace(/[-[\]{}()*+?.,\\^$|#\s]/g,
"\\$&")},filter:function(a,b){var g=new RegExp(d.ui.autocomplete.escapeRegex(b),"i");return d.grep(a,function(c){return g.test(c.label||c.value||c)})}})})(jQuery);
(function(d){d.widget("ui.menu",{_create:function(){var e=this;this.element.addClass("ui-menu ui-widget ui-widget-content ui-corner-all").attr({role:"listbox","aria-activedescendant":"ui-active-menuitem"}).click(function(a){if(d(a.target).closest(".ui-menu-item a").length){a.preventDefault();e.select(a)}});this.refresh()},refresh:function(){var e=this;this.element.children("li:not(.ui-menu-item):has(a)").addClass("ui-menu-item").attr("role","menuitem").children("a").addClass("ui-corner-all").attr("tabindex",
-1).mouseenter(function(a){e.activate(a,d(this).parent())}).mouseleave(function(){e.deactivate()})},activate:function(e,a){this.deactivate();if(this.hasScroll()){var b=a.offset().top-this.element.offset().top,g=this.element.scrollTop(),c=this.element.height();if(b<0)this.element.scrollTop(g+b);else b>=c&&this.element.scrollTop(g+b-c+a.height())}this.active=a.eq(0).children("a").addClass("ui-state-hover").attr("id","ui-active-menuitem").end();this._trigger("focus",e,{item:a})},deactivate:function(){if(this.active){this.active.children("a").removeClass("ui-state-hover").removeAttr("id");
this._trigger("blur");this.active=null}},next:function(e){this.move("next",".ui-menu-item:first",e)},previous:function(e){this.move("prev",".ui-menu-item:last",e)},first:function(){return this.active&&!this.active.prevAll(".ui-menu-item").length},last:function(){return this.active&&!this.active.nextAll(".ui-menu-item").length},move:function(e,a,b){if(this.active){e=this.active[e+"All"](".ui-menu-item").eq(0);e.length?this.activate(b,e):this.activate(b,this.element.children(a))}else this.activate(b,
this.element.children(a))},nextPage:function(e){if(this.hasScroll())if(!this.active||this.last())this.activate(e,this.element.children(".ui-menu-item:first"));else{var a=this.active.offset().top,b=this.element.height(),g=this.element.children(".ui-menu-item").filter(function(){var c=d(this).offset().top-a-b+d(this).height();return c<10&&c>-10});g.length||(g=this.element.children(".ui-menu-item:last"));this.activate(e,g)}else this.activate(e,this.element.children(".ui-menu-item").filter(!this.active||
this.last()?":first":":last"))},previousPage:function(e){if(this.hasScroll())if(!this.active||this.first())this.activate(e,this.element.children(".ui-menu-item:last"));else{var a=this.active.offset().top,b=this.element.height();result=this.element.children(".ui-menu-item").filter(function(){var g=d(this).offset().top-a+b-d(this).height();return g<10&&g>-10});result.length||(result=this.element.children(".ui-menu-item:first"));this.activate(e,result)}else this.activate(e,this.element.children(".ui-menu-item").filter(!this.active||
this.first()?":last":":first"))},hasScroll:function(){return this.element.height()<this.element[d.fn.prop?"prop":"attr"]("scrollHeight")},select:function(e){this._trigger("selected",e,{item:this.active})}})})(jQuery);
;

View file

@ -102,6 +102,13 @@ Partial.ItemSet = function ItemSet(wardrobe, selector) {
) {
$('<div/>', {'class': 'nc-icon', text: 'NC', title: 'NC'}).appendTo(li);
}
if(item.owned || item.wanted) {
var iconsWrapper = $('<div/>', {'class': 'closeted-icons'}).appendTo(li);
if(item.owned) {
$('<img/>', {alt: 'Own', title: 'You own this', src: '/images/owned.png'}).appendTo(iconsWrapper);
$('<img/>', {alt: 'Want', title: 'You want this', src: '/images/wanted.png'}).appendTo(iconsWrapper);
}
}
li.append(img).append(controls).append(info_link).append(item.name).appendTo(ul);
}
setClosetItems(wardrobe.outfit.getClosetItems());
@ -860,6 +867,7 @@ View.Search = function (wardrobe) {
loading_el = $('#preview-search-form-loading'),
no_results_el = $('#preview-search-form-no-results'),
no_results_span = no_results_el.children('span'),
your_items_links = $('.preview-search-form-your-items'),
PAGINATION = {
INNER_WINDOW: 4,
OUTER_WINDOW: 1,
@ -924,6 +932,12 @@ View.Search = function (wardrobe) {
form.submit();
});
your_items_links.click(function (e) {
e.preventDefault();
input_el.val('user:' + this.getAttribute('data-search-value'));
form.submit();
});
wardrobe.search.bind('startRequest', function () {
loading_el.delay(1000).show('slow');
});

View file

@ -0,0 +1,3 @@
/*! http://mths.be/placeholder v1.8.4 by @mathias */
(function($){var e='placeholder' in document.createElement('input'),a='placeholder' in document.createElement('textarea');if(e&&a){$.fn.placeholder=function(){return this};$.fn.placeholder.input=$.fn.placeholder.textarea=true}else{$.fn.placeholder=function(){return this.filter((e?'textarea':':input')+'[placeholder]').bind('focus.placeholder',b).bind('blur.placeholder',d).trigger('blur.placeholder').end()};$.fn.placeholder.input=e;$.fn.placeholder.textarea=a;$(function(){$('form').bind('submit.placeholder',function(){var f=$('.placeholder',this).each(b);setTimeout(function(){f.each(d)},10)})});$(window).bind('unload.placeholder',function(){$('.placeholder').val('')})}function c(g){var f={},h=/^jQuery\d+$/;$.each(g.attributes,function(k,j){if(j.specified&&!h.test(j.name)){f[j.name]=j.value}});return f}function b(){var f=$(this);if(f.val()===f.attr('placeholder')&&f.hasClass('placeholder')){if(f.data('placeholder-password')){f.hide().next().attr('id',f.removeAttr('id').data('placeholder-id')).show().focus()}else{f.val('').removeClass('placeholder')}}}function d(){var j,i=$(this),f=i,h=this.id;if(i.val()===''){if(i.is(':password')){if(!i.data('placeholder-textinput')){try{j=i.clone().attr({type:'text'})}catch(g){j=$('<input>').attr($.extend(c(this),{type:'text'}))}j.removeAttr('name').data('placeholder-password',true).data('placeholder-id',h).bind('focus.placeholder',b);i.data('placeholder-textinput',j).data('placeholder-id',h).before(j)}i=i.removeAttr('id').hide().prev().attr('id',h).show()}i.addClass('placeholder').val(i.attr('placeholder'))}else{i.removeClass('placeholder')}}}(jQuery));

File diff suppressed because it is too large Load diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

View file

@ -0,0 +1,342 @@
/*
* jQuery UI CSS Framework 1.8.14
*
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Theming/API
*/
/* Layout helpers
----------------------------------*/
.ui-helper-hidden { display: none; }
.ui-helper-hidden-accessible { position: absolute !important; clip: rect(1px 1px 1px 1px); clip: rect(1px,1px,1px,1px); }
.ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; }
.ui-helper-clearfix:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; }
.ui-helper-clearfix { display: inline-block; }
/* required comment for clearfix to work in Opera \*/
* html .ui-helper-clearfix { height:1%; }
.ui-helper-clearfix { display:block; }
/* end clearfix */
.ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); }
/* Interaction Cues
----------------------------------*/
.ui-state-disabled { cursor: default !important; }
/* Icons
----------------------------------*/
/* states and images */
.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; }
/* Misc visuals
----------------------------------*/
/* Overlays */
.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
/*
* jQuery UI CSS Framework 1.8.14
*
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Theming/API
*
* To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=segoe%20ui,%20Arial,%20sans-serif&fwDefault=bold&fsDefault=1.1em&cornerRadius=6px&bgColorHeader=ece8da&bgTextureHeader=12_gloss_wave.png&bgImgOpacityHeader=100&borderColorHeader=d4ccb0&fcHeader=433f38&iconColorHeader=847e71&bgColorContent=f5f3e5&bgTextureContent=04_highlight_hard.png&bgImgOpacityContent=100&borderColorContent=dfd9c3&fcContent=312e25&iconColorContent=808080&bgColorDefault=459e00&bgTextureDefault=04_highlight_hard.png&bgImgOpacityDefault=15&borderColorDefault=327E04&fcDefault=ffffff&iconColorDefault=eeeeee&bgColorHover=67b021&bgTextureHover=03_highlight_soft.png&bgImgOpacityHover=25&borderColorHover=327E04&fcHover=ffffff&iconColorHover=ffffff&bgColorActive=fafaf4&bgTextureActive=04_highlight_hard.png&bgImgOpacityActive=100&borderColorActive=d4ccb0&fcActive=459e00&iconColorActive=8DC262&bgColorHighlight=fcf0ba&bgTextureHighlight=02_glass.png&bgImgOpacityHighlight=55&borderColorHighlight=e8e1b5&fcHighlight=363636&iconColorHighlight=8DC262&bgColorError=ffedad&bgTextureError=03_highlight_soft.png&bgImgOpacityError=95&borderColorError=e3a345&fcError=cd5c0a&iconColorError=cd0a0a&bgColorOverlay=2b2922&bgTextureOverlay=05_inset_soft.png&bgImgOpacityOverlay=15&opacityOverlay=90&bgColorShadow=cccccc&bgTextureShadow=04_highlight_hard.png&bgImgOpacityShadow=95&opacityShadow=20&thicknessShadow=12px&offsetTopShadow=-12px&offsetLeftShadow=-12px&cornerRadiusShadow=10px
*/
/* Component containers
----------------------------------*/
.ui-widget { font-family: segoe ui, Arial, sans-serif; font-size: 1.1em; }
.ui-widget .ui-widget { font-size: 1em; }
.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: segoe ui, Arial, sans-serif; font-size: 1em; }
.ui-widget-content { border: 1px solid #dfd9c3; background: #f5f3e5 url(images/ui-bg_highlight-hard_100_f5f3e5_1x100.png) 50% top repeat-x; color: #312e25; }
.ui-widget-content a { color: #312e25; }
.ui-widget-header { border: 1px solid #d4ccb0; background: #ece8da url(images/ui-bg_gloss-wave_100_ece8da_500x100.png) 50% 50% repeat-x; color: #433f38; font-weight: bold; }
.ui-widget-header a { color: #433f38; }
/* Interaction states
----------------------------------*/
.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #327e04; background: #459e00 url(images/ui-bg_highlight-hard_15_459e00_1x100.png) 50% 50% repeat-x; font-weight: bold; color: #ffffff; }
.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #ffffff; text-decoration: none; }
.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 1px solid #327e04; background: #67b021 url(images/ui-bg_highlight-soft_25_67b021_1x100.png) 50% 50% repeat-x; font-weight: bold; color: #ffffff; }
.ui-state-hover a, .ui-state-hover a:hover { color: #ffffff; text-decoration: none; }
.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { border: 1px solid #d4ccb0; background: #fafaf4 url(images/ui-bg_highlight-hard_100_fafaf4_1x100.png) 50% 50% repeat-x; font-weight: bold; color: #459e00; }
.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #459e00; text-decoration: none; }
.ui-widget :active { outline: none; }
/* Interaction Cues
----------------------------------*/
.ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight {border: 1px solid #e8e1b5; background: #fcf0ba url(images/ui-bg_glass_55_fcf0ba_1x400.png) 50% 50% repeat-x; color: #363636; }
.ui-state-highlight a, .ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a { color: #363636; }
.ui-state-error, .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error {border: 1px solid #e3a345; background: #ffedad url(images/ui-bg_highlight-soft_95_ffedad_1x100.png) 50% top repeat-x; color: #cd5c0a; }
.ui-state-error a, .ui-widget-content .ui-state-error a, .ui-widget-header .ui-state-error a { color: #cd5c0a; }
.ui-state-error-text, .ui-widget-content .ui-state-error-text, .ui-widget-header .ui-state-error-text { color: #cd5c0a; }
.ui-priority-primary, .ui-widget-content .ui-priority-primary, .ui-widget-header .ui-priority-primary { font-weight: bold; }
.ui-priority-secondary, .ui-widget-content .ui-priority-secondary, .ui-widget-header .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; }
.ui-state-disabled, .ui-widget-content .ui-state-disabled, .ui-widget-header .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; }
/* Icons
----------------------------------*/
/* states and images */
.ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_808080_256x240.png); }
.ui-widget-content .ui-icon {background-image: url(images/ui-icons_808080_256x240.png); }
.ui-widget-header .ui-icon {background-image: url(images/ui-icons_847e71_256x240.png); }
.ui-state-default .ui-icon { background-image: url(images/ui-icons_eeeeee_256x240.png); }
.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_ffffff_256x240.png); }
.ui-state-active .ui-icon {background-image: url(images/ui-icons_8dc262_256x240.png); }
.ui-state-highlight .ui-icon {background-image: url(images/ui-icons_8dc262_256x240.png); }
.ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_cd0a0a_256x240.png); }
/* positioning */
.ui-icon-carat-1-n { background-position: 0 0; }
.ui-icon-carat-1-ne { background-position: -16px 0; }
.ui-icon-carat-1-e { background-position: -32px 0; }
.ui-icon-carat-1-se { background-position: -48px 0; }
.ui-icon-carat-1-s { background-position: -64px 0; }
.ui-icon-carat-1-sw { background-position: -80px 0; }
.ui-icon-carat-1-w { background-position: -96px 0; }
.ui-icon-carat-1-nw { background-position: -112px 0; }
.ui-icon-carat-2-n-s { background-position: -128px 0; }
.ui-icon-carat-2-e-w { background-position: -144px 0; }
.ui-icon-triangle-1-n { background-position: 0 -16px; }
.ui-icon-triangle-1-ne { background-position: -16px -16px; }
.ui-icon-triangle-1-e { background-position: -32px -16px; }
.ui-icon-triangle-1-se { background-position: -48px -16px; }
.ui-icon-triangle-1-s { background-position: -64px -16px; }
.ui-icon-triangle-1-sw { background-position: -80px -16px; }
.ui-icon-triangle-1-w { background-position: -96px -16px; }
.ui-icon-triangle-1-nw { background-position: -112px -16px; }
.ui-icon-triangle-2-n-s { background-position: -128px -16px; }
.ui-icon-triangle-2-e-w { background-position: -144px -16px; }
.ui-icon-arrow-1-n { background-position: 0 -32px; }
.ui-icon-arrow-1-ne { background-position: -16px -32px; }
.ui-icon-arrow-1-e { background-position: -32px -32px; }
.ui-icon-arrow-1-se { background-position: -48px -32px; }
.ui-icon-arrow-1-s { background-position: -64px -32px; }
.ui-icon-arrow-1-sw { background-position: -80px -32px; }
.ui-icon-arrow-1-w { background-position: -96px -32px; }
.ui-icon-arrow-1-nw { background-position: -112px -32px; }
.ui-icon-arrow-2-n-s { background-position: -128px -32px; }
.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; }
.ui-icon-arrow-2-e-w { background-position: -160px -32px; }
.ui-icon-arrow-2-se-nw { background-position: -176px -32px; }
.ui-icon-arrowstop-1-n { background-position: -192px -32px; }
.ui-icon-arrowstop-1-e { background-position: -208px -32px; }
.ui-icon-arrowstop-1-s { background-position: -224px -32px; }
.ui-icon-arrowstop-1-w { background-position: -240px -32px; }
.ui-icon-arrowthick-1-n { background-position: 0 -48px; }
.ui-icon-arrowthick-1-ne { background-position: -16px -48px; }
.ui-icon-arrowthick-1-e { background-position: -32px -48px; }
.ui-icon-arrowthick-1-se { background-position: -48px -48px; }
.ui-icon-arrowthick-1-s { background-position: -64px -48px; }
.ui-icon-arrowthick-1-sw { background-position: -80px -48px; }
.ui-icon-arrowthick-1-w { background-position: -96px -48px; }
.ui-icon-arrowthick-1-nw { background-position: -112px -48px; }
.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; }
.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; }
.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; }
.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; }
.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; }
.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; }
.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; }
.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; }
.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; }
.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; }
.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; }
.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; }
.ui-icon-arrowreturn-1-w { background-position: -64px -64px; }
.ui-icon-arrowreturn-1-n { background-position: -80px -64px; }
.ui-icon-arrowreturn-1-e { background-position: -96px -64px; }
.ui-icon-arrowreturn-1-s { background-position: -112px -64px; }
.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; }
.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; }
.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; }
.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; }
.ui-icon-arrow-4 { background-position: 0 -80px; }
.ui-icon-arrow-4-diag { background-position: -16px -80px; }
.ui-icon-extlink { background-position: -32px -80px; }
.ui-icon-newwin { background-position: -48px -80px; }
.ui-icon-refresh { background-position: -64px -80px; }
.ui-icon-shuffle { background-position: -80px -80px; }
.ui-icon-transfer-e-w { background-position: -96px -80px; }
.ui-icon-transferthick-e-w { background-position: -112px -80px; }
.ui-icon-folder-collapsed { background-position: 0 -96px; }
.ui-icon-folder-open { background-position: -16px -96px; }
.ui-icon-document { background-position: -32px -96px; }
.ui-icon-document-b { background-position: -48px -96px; }
.ui-icon-note { background-position: -64px -96px; }
.ui-icon-mail-closed { background-position: -80px -96px; }
.ui-icon-mail-open { background-position: -96px -96px; }
.ui-icon-suitcase { background-position: -112px -96px; }
.ui-icon-comment { background-position: -128px -96px; }
.ui-icon-person { background-position: -144px -96px; }
.ui-icon-print { background-position: -160px -96px; }
.ui-icon-trash { background-position: -176px -96px; }
.ui-icon-locked { background-position: -192px -96px; }
.ui-icon-unlocked { background-position: -208px -96px; }
.ui-icon-bookmark { background-position: -224px -96px; }
.ui-icon-tag { background-position: -240px -96px; }
.ui-icon-home { background-position: 0 -112px; }
.ui-icon-flag { background-position: -16px -112px; }
.ui-icon-calendar { background-position: -32px -112px; }
.ui-icon-cart { background-position: -48px -112px; }
.ui-icon-pencil { background-position: -64px -112px; }
.ui-icon-clock { background-position: -80px -112px; }
.ui-icon-disk { background-position: -96px -112px; }
.ui-icon-calculator { background-position: -112px -112px; }
.ui-icon-zoomin { background-position: -128px -112px; }
.ui-icon-zoomout { background-position: -144px -112px; }
.ui-icon-search { background-position: -160px -112px; }
.ui-icon-wrench { background-position: -176px -112px; }
.ui-icon-gear { background-position: -192px -112px; }
.ui-icon-heart { background-position: -208px -112px; }
.ui-icon-star { background-position: -224px -112px; }
.ui-icon-link { background-position: -240px -112px; }
.ui-icon-cancel { background-position: 0 -128px; }
.ui-icon-plus { background-position: -16px -128px; }
.ui-icon-plusthick { background-position: -32px -128px; }
.ui-icon-minus { background-position: -48px -128px; }
.ui-icon-minusthick { background-position: -64px -128px; }
.ui-icon-close { background-position: -80px -128px; }
.ui-icon-closethick { background-position: -96px -128px; }
.ui-icon-key { background-position: -112px -128px; }
.ui-icon-lightbulb { background-position: -128px -128px; }
.ui-icon-scissors { background-position: -144px -128px; }
.ui-icon-clipboard { background-position: -160px -128px; }
.ui-icon-copy { background-position: -176px -128px; }
.ui-icon-contact { background-position: -192px -128px; }
.ui-icon-image { background-position: -208px -128px; }
.ui-icon-video { background-position: -224px -128px; }
.ui-icon-script { background-position: -240px -128px; }
.ui-icon-alert { background-position: 0 -144px; }
.ui-icon-info { background-position: -16px -144px; }
.ui-icon-notice { background-position: -32px -144px; }
.ui-icon-help { background-position: -48px -144px; }
.ui-icon-check { background-position: -64px -144px; }
.ui-icon-bullet { background-position: -80px -144px; }
.ui-icon-radio-off { background-position: -96px -144px; }
.ui-icon-radio-on { background-position: -112px -144px; }
.ui-icon-pin-w { background-position: -128px -144px; }
.ui-icon-pin-s { background-position: -144px -144px; }
.ui-icon-play { background-position: 0 -160px; }
.ui-icon-pause { background-position: -16px -160px; }
.ui-icon-seek-next { background-position: -32px -160px; }
.ui-icon-seek-prev { background-position: -48px -160px; }
.ui-icon-seek-end { background-position: -64px -160px; }
.ui-icon-seek-start { background-position: -80px -160px; }
/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */
.ui-icon-seek-first { background-position: -80px -160px; }
.ui-icon-stop { background-position: -96px -160px; }
.ui-icon-eject { background-position: -112px -160px; }
.ui-icon-volume-off { background-position: -128px -160px; }
.ui-icon-volume-on { background-position: -144px -160px; }
.ui-icon-power { background-position: 0 -176px; }
.ui-icon-signal-diag { background-position: -16px -176px; }
.ui-icon-signal { background-position: -32px -176px; }
.ui-icon-battery-0 { background-position: -48px -176px; }
.ui-icon-battery-1 { background-position: -64px -176px; }
.ui-icon-battery-2 { background-position: -80px -176px; }
.ui-icon-battery-3 { background-position: -96px -176px; }
.ui-icon-circle-plus { background-position: 0 -192px; }
.ui-icon-circle-minus { background-position: -16px -192px; }
.ui-icon-circle-close { background-position: -32px -192px; }
.ui-icon-circle-triangle-e { background-position: -48px -192px; }
.ui-icon-circle-triangle-s { background-position: -64px -192px; }
.ui-icon-circle-triangle-w { background-position: -80px -192px; }
.ui-icon-circle-triangle-n { background-position: -96px -192px; }
.ui-icon-circle-arrow-e { background-position: -112px -192px; }
.ui-icon-circle-arrow-s { background-position: -128px -192px; }
.ui-icon-circle-arrow-w { background-position: -144px -192px; }
.ui-icon-circle-arrow-n { background-position: -160px -192px; }
.ui-icon-circle-zoomin { background-position: -176px -192px; }
.ui-icon-circle-zoomout { background-position: -192px -192px; }
.ui-icon-circle-check { background-position: -208px -192px; }
.ui-icon-circlesmall-plus { background-position: 0 -208px; }
.ui-icon-circlesmall-minus { background-position: -16px -208px; }
.ui-icon-circlesmall-close { background-position: -32px -208px; }
.ui-icon-squaresmall-plus { background-position: -48px -208px; }
.ui-icon-squaresmall-minus { background-position: -64px -208px; }
.ui-icon-squaresmall-close { background-position: -80px -208px; }
.ui-icon-grip-dotted-vertical { background-position: 0 -224px; }
.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; }
.ui-icon-grip-solid-vertical { background-position: -32px -224px; }
.ui-icon-grip-solid-horizontal { background-position: -48px -224px; }
.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }
.ui-icon-grip-diagonal-se { background-position: -80px -224px; }
/* Misc visuals
----------------------------------*/
/* Corner radius */
.ui-corner-all, .ui-corner-top, .ui-corner-left, .ui-corner-tl { -moz-border-radius-topleft: 6px; -webkit-border-top-left-radius: 6px; -khtml-border-top-left-radius: 6px; border-top-left-radius: 6px; }
.ui-corner-all, .ui-corner-top, .ui-corner-right, .ui-corner-tr { -moz-border-radius-topright: 6px; -webkit-border-top-right-radius: 6px; -khtml-border-top-right-radius: 6px; border-top-right-radius: 6px; }
.ui-corner-all, .ui-corner-bottom, .ui-corner-left, .ui-corner-bl { -moz-border-radius-bottomleft: 6px; -webkit-border-bottom-left-radius: 6px; -khtml-border-bottom-left-radius: 6px; border-bottom-left-radius: 6px; }
.ui-corner-all, .ui-corner-bottom, .ui-corner-right, .ui-corner-br { -moz-border-radius-bottomright: 6px; -webkit-border-bottom-right-radius: 6px; -khtml-border-bottom-right-radius: 6px; border-bottom-right-radius: 6px; }
/* Overlays */
.ui-widget-overlay { background: #2b2922 url(images/ui-bg_inset-soft_15_2b2922_1x100.png) 50% bottom repeat-x; opacity: .90;filter:Alpha(Opacity=90); }
.ui-widget-shadow { margin: -12px 0 0 -12px; padding: 12px; background: #cccccc url(images/ui-bg_highlight-hard_95_cccccc_1x100.png) 50% top repeat-x; opacity: .20;filter:Alpha(Opacity=20); -moz-border-radius: 10px; -khtml-border-radius: 10px; -webkit-border-radius: 10px; border-radius: 10px; }/*
* jQuery UI Autocomplete 1.8.14
*
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Autocomplete#theming
*/
.ui-autocomplete { position: absolute; cursor: default; }
/* workarounds */
* html .ui-autocomplete { width:1px; } /* without this, the menu expands to 100% in IE6 */
/*
* jQuery UI Menu 1.8.14
*
* Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Menu#theming
*/
.ui-menu {
list-style:none;
padding: 2px;
margin: 0;
display:block;
float: left;
}
.ui-menu .ui-menu {
margin-top: -3px;
}
.ui-menu .ui-menu-item {
margin:0;
padding: 0;
zoom: 1;
float: left;
clear: left;
width: 100%;
}
.ui-menu .ui-menu-item a {
text-decoration:none;
display:block;
padding:.2em .4em;
line-height:1.5;
zoom:1;
}
.ui-menu .ui-menu-item a.ui-state-hover,
.ui-menu .ui-menu-item a.ui-state-active {
font-weight: normal;
margin: -1px;
}

View file

@ -0,0 +1,5 @@
require 'spec_helper'
describe ClosetHangersController do
end

View file

@ -0,0 +1,19 @@
require 'spec_helper'
describe ClosetListsController do
describe "GET 'new'" do
it "should be successful" do
get 'new'
response.should be_success
end
end
describe "GET 'create'" do
it "should be successful" do
get 'create'
response.should be_success
end
end
end

View file

@ -0,0 +1,5 @@
require 'spec_helper'
describe ClosetPagesController do
end

View file

@ -0,0 +1,15 @@
require 'spec_helper'
# Specs in this file have access to a helper object that includes
# the ClosetHangersHelper. For example:
#
# describe ClosetHangersHelper do
# describe "string concat" do
# it "concats two strings with spaces" do
# helper.concat_strings("this","that").should == "this that"
# end
# end
# end
describe ClosetHangersHelper do
pending "add some examples to (or delete) #{__FILE__}"
end

View file

@ -0,0 +1,15 @@
require 'spec_helper'
# Specs in this file have access to a helper object that includes
# the ClosetListsHelper. For example:
#
# describe ClosetListsHelper do
# describe "string concat" do
# it "concats two strings with spaces" do
# helper.concat_strings("this","that").should == "this that"
# end
# end
# end
describe ClosetListsHelper do
pending "add some examples to (or delete) #{__FILE__}"
end

View file

@ -0,0 +1,15 @@
require 'spec_helper'
# Specs in this file have access to a helper object that includes
# the ClosetPagesHelper. For example:
#
# describe ClosetPagesHelper do
# describe "string concat" do
# it "concats two strings with spaces" do
# helper.concat_strings("this","that").should == "this that"
# end
# end
# end
describe ClosetPagesHelper do
pending "add some examples to (or delete) #{__FILE__}"
end

View file

@ -0,0 +1,5 @@
require 'spec_helper'
describe ClosetHanger do
pending "add some examples to (or delete) #{__FILE__}"
end

View file

@ -0,0 +1,5 @@
require 'spec_helper'
describe ClosetList do
pending "add some examples to (or delete) #{__FILE__}"
end

View file

@ -0,0 +1,5 @@
require 'spec_helper'
describe "closet_lists/create.html.erb" do
pending "add some examples to (or delete) #{__FILE__}"
end

View file

@ -0,0 +1,5 @@
require 'spec_helper'
describe "closet_lists/new.html.erb" do
pending "add some examples to (or delete) #{__FILE__}"
end

View file

@ -0,0 +1,5 @@
require 'spec_helper'
describe "petpages/new.html.erb" do
pending "add some examples to (or delete) #{__FILE__}"
end

BIN
vendor/cache/nokogiri-1.5.0.gem vendored Normal file

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show more