diff --git a/.gitignore b/.gitignore index 705de5c..8890ac1 100644 --- a/.gitignore +++ b/.gitignore @@ -9,13 +9,17 @@ # Ignore the default SQLite database. /db/*.sqlite3 +/db/*.sqlite3.* # Ignore all logfiles and tempfiles. /log/*.log /tmp +tmp/ # Ignore compiled assets /public/assets -# Ignore config file +# Ignore config and database files (passwords) /config/config.yml +/config/database.yml +/config/initializers/secret_token.rb diff --git a/.rvmrc b/.rvmrc new file mode 100644 index 0000000..d462736 --- /dev/null +++ b/.rvmrc @@ -0,0 +1,35 @@ +#!/usr/bin/env bash + +# This is an RVM Project .rvmrc file, used to automatically load the ruby +# development environment upon cd'ing into the directory + +# First we specify our desired [@], the @gemset name is optional, +# Only full ruby name is supported here, for short names use: +# echo "rvm use 1.8.7" > .rvmrc +environment_id="ruby-1.9.3-p385@members-hsl" + +# Uncomment the following lines if you want to verify rvm version per project +# rvmrc_rvm_version="1.18.8 (stable)" # 1.10.1 seams as a safe start +# eval "$(echo ${rvm_version}.${rvmrc_rvm_version} | awk -F. '{print "[[ "$1*65536+$2*256+$3" -ge "$4*65536+$5*256+$6" ]]"}' )" || { +# echo "This .rvmrc file requires at least RVM ${rvmrc_rvm_version}, aborting loading." +# return 1 +# } + +# First we attempt to load the desired environment directly from the environment +# file. This is very fast and efficient compared to running through the entire +# CLI and selector. If you want feedback on which environment was used then +# insert the word 'use' after --create as this triggers verbose mode. +if [[ -d "${rvm_path:-$HOME/.rvm}/environments" + && -s "${rvm_path:-$HOME/.rvm}/environments/$environment_id" ]] +then + \. "${rvm_path:-$HOME/.rvm}/environments/$environment_id" + [[ -s "${rvm_path:-$HOME/.rvm}/hooks/after_use" ]] && + \. "${rvm_path:-$HOME/.rvm}/hooks/after_use" || true +else + # If the environment file has not yet been created, use the RVM CLI to select. + rvm --create "$environment_id" || { + echo "Failed to create RVM environment '${environment_id}'." + return 1 + } +fi + diff --git a/Gemfile b/Gemfile index 8ecbde8..ab26f86 100644 --- a/Gemfile +++ b/Gemfile @@ -1,11 +1,15 @@ source 'https://rubygems.org' -gem 'rails', '3.2.3' +ruby '1.9.3' + +gem 'rails', '3.2.8' # Bundle edge Rails instead: # gem 'rails', :git => 'git://github.com/rails/rails.git' gem 'sqlite3' +gem 'pg' +gem 'taps' gem 'json' @@ -40,7 +44,11 @@ gem 'bcrypt-ruby', '~> 3.0.0' # gem 'capistrano' # To use debugger -# gem 'ruby-debug' +#gem 'debugger' #gem "paperclip", "~> 3.0" gem 'gravtastic' + +gem 'passenger' + +gem "rails-settings-cached", "0.2.4" diff --git a/Gemfile.lock b/Gemfile.lock index 94abf55..33af3c3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,116 +1,135 @@ GEM remote: https://rubygems.org/ specs: - actionmailer (3.2.3) - actionpack (= 3.2.3) + actionmailer (3.2.8) + actionpack (= 3.2.8) mail (~> 2.4.4) - actionpack (3.2.3) - activemodel (= 3.2.3) - activesupport (= 3.2.3) + actionpack (3.2.8) + activemodel (= 3.2.8) + activesupport (= 3.2.8) builder (~> 3.0.0) erubis (~> 2.7.0) - journey (~> 1.0.1) + journey (~> 1.0.4) rack (~> 1.4.0) rack-cache (~> 1.2) rack-test (~> 0.6.1) - sprockets (~> 2.1.2) - activemodel (3.2.3) - activesupport (= 3.2.3) + sprockets (~> 2.1.3) + activemodel (3.2.8) + activesupport (= 3.2.8) builder (~> 3.0.0) - activerecord (3.2.3) - activemodel (= 3.2.3) - activesupport (= 3.2.3) + activerecord (3.2.8) + activemodel (= 3.2.8) + activesupport (= 3.2.8) arel (~> 3.0.2) tzinfo (~> 0.3.29) - activeresource (3.2.3) - activemodel (= 3.2.3) - activesupport (= 3.2.3) - activesupport (3.2.3) + activeresource (3.2.8) + activemodel (= 3.2.8) + activesupport (= 3.2.8) + activesupport (3.2.8) i18n (~> 0.6) multi_json (~> 1.0) arel (3.0.2) bcrypt-ruby (3.0.1) - builder (3.0.0) - cancan (1.6.8) + builder (3.0.4) + cancan (1.6.10) coffee-rails (3.2.2) coffee-script (>= 2.2.0) railties (~> 3.2.0) coffee-script (2.2.0) coffee-script-source execjs - coffee-script-source (1.3.3) - devise (2.1.1) + coffee-script-source (1.6.3) + daemon_controller (1.1.5) + devise (2.2.7) bcrypt-ruby (~> 3.0) orm_adapter (~> 0.1) railties (~> 3.1) warden (~> 1.2.1) erubis (2.7.0) - execjs (1.4.0) - multi_json (~> 1.0) + execjs (2.0.2) gravtastic (3.2.6) - hike (1.2.1) - i18n (0.6.0) + hike (1.2.3) + i18n (0.6.5) journey (1.0.4) - jquery-rails (2.1.1) - railties (>= 3.1.0, < 5.0) - thor (~> 0.14) - json (1.7.5) - libv8 (3.3.10.4) + jquery-rails (3.0.4) + railties (>= 3.0, < 5.0) + thor (>= 0.14, < 2.0) + json (1.8.1) + libv8 (3.16.14.3) mail (2.4.4) i18n (>= 0.4.0) mime-types (~> 1.16) treetop (~> 1.4.8) - mime-types (1.19) - multi_json (1.3.6) - orm_adapter (0.1.0) + mime-types (1.25) + multi_json (1.8.2) + orm_adapter (0.4.0) + passenger (4.0.19) + daemon_controller (>= 1.1.0) + rack + rake (>= 0.8.1) + pg (0.17.0) polyglot (0.3.3) - rack (1.4.1) + rack (1.4.5) rack-cache (1.2) rack (>= 0.4) - rack-ssl (1.3.2) + rack-ssl (1.3.3) rack - rack-test (0.6.1) + rack-test (0.6.2) rack (>= 1.0) - rails (3.2.3) - actionmailer (= 3.2.3) - actionpack (= 3.2.3) - activerecord (= 3.2.3) - activeresource (= 3.2.3) - activesupport (= 3.2.3) + rails (3.2.8) + actionmailer (= 3.2.8) + actionpack (= 3.2.8) + activerecord (= 3.2.8) + activeresource (= 3.2.8) + activesupport (= 3.2.8) bundler (~> 1.0) - railties (= 3.2.3) - railties (3.2.3) - actionpack (= 3.2.3) - activesupport (= 3.2.3) + railties (= 3.2.8) + rails-settings-cached (0.2.4) + rails (>= 3.0.0) + railties (3.2.8) + actionpack (= 3.2.8) + activesupport (= 3.2.8) rack-ssl (~> 1.3.2) rake (>= 0.8.7) rdoc (~> 3.4) - thor (~> 0.14.6) - rake (0.9.2.2) - rdoc (3.12) + thor (>= 0.14.6, < 2.0) + rake (10.1.0) + rdoc (3.12.2) json (~> 1.4) - sass (3.2.1) - sass-rails (3.2.5) + ref (1.0.5) + rest-client (1.6.7) + mime-types (>= 1.16) + sass (3.2.11) + sass-rails (3.2.6) railties (~> 3.2.0) sass (>= 3.1.10) tilt (~> 1.3) + sequel (3.20.0) + sinatra (1.0) + rack (>= 1.0) sprockets (2.1.3) hike (~> 1.2) rack (~> 1.0) tilt (~> 1.1, != 1.3.0) - sqlite3 (1.3.6) - therubyracer (0.10.1) - libv8 (~> 3.3.10) - thor (0.14.6) - tilt (1.3.3) - treetop (1.4.10) + sqlite3 (1.3.8) + taps (0.3.24) + rack (>= 1.0.1) + rest-client (>= 1.4.0, < 1.7.0) + sequel (~> 3.20.0) + sinatra (~> 1.0.0) + therubyracer (0.12.0) + libv8 (~> 3.16.14.0) + ref + thor (0.18.1) + tilt (1.4.1) + treetop (1.4.15) polyglot polyglot (>= 0.3.1) - tzinfo (0.3.33) - uglifier (1.2.7) + tzinfo (0.3.38) + uglifier (2.2.1) execjs (>= 0.3.0) - multi_json (~> 1.3) - warden (1.2.1) + multi_json (~> 1.0, >= 1.0.2) + warden (1.2.3) rack (>= 1.0) PLATFORMS @@ -124,8 +143,12 @@ DEPENDENCIES gravtastic jquery-rails json - rails (= 3.2.3) + passenger + pg + rails (= 3.2.8) + rails-settings-cached (= 0.2.4) sass-rails (~> 3.2.3) sqlite3 + taps therubyracer uglifier (>= 1.0.3) diff --git a/README.rdoc b/README.rdoc index 387e615..6e019ff 100644 --- a/README.rdoc +++ b/README.rdoc @@ -10,6 +10,12 @@ Distributed under a Creative Commons Attribution 3.0 license http://creativecomm To use: * Load into a Rails 3 environment -* Rename config/config.yml.example to config/config.yml and edit appropriately -* Use the Rails console to create a new User and set user.admin = true +* Copy config/config.yml.example to config/config.yml and edit appropriately +* Copy config/database.yml.example to config/database.yml and edit appropriately +* Copy config/initializers/secret_token.rb.example to config/config/initializers/secret_token.rb and edit appropriately * Run bundle install, rake db:migrate, etc. +* Use the Rails console to create a new User with admin rights + * user = User.new + * user.password = "foobar" + * user.admin = true + * user.save diff --git a/app/assets/javascripts/ipn.js.coffee b/app/assets/javascripts/ipn.js.coffee new file mode 100644 index 0000000..7615679 --- /dev/null +++ b/app/assets/javascripts/ipn.js.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index 38f2c00..855bf09 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -12,3 +12,46 @@ *= require_tree . */ .caption { display: inline-block; background-color: #eee; border: 1px solid #333; border-radius: 5px; margin-bottom: 1em; } +.btn { +display: inline-block; +padding: 4px 10px 4px; +margin-bottom: 0; +font-size: 13px; +line-height: 18px; +color: #333; +text-align: center; +text-decoration: none; +text-shadow: 0 1px 1px rgba(255,255,255,.75); +vertical-align: middle; +background-color: #f5f5f5; +background-image: -moz-linear-gradient(top, #fff, #e6e6e6); +background-image: -ms-linear-gradient(top, #fff, #e6e6e6); +background-image: -webkit-gradient(linear,0 0,0 100%,from( #fff),to( #e6e6e6)); +background-image: -webkit-linear-gradient(top, #fff, #e6e6e6); +background-image: -o-linear-gradient(top, #fff, #e6e6e6); +background-image: linear-gradient(top, #fff, #e6e6e6); +background-repeat: repeat-x; +filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e6e6e6', GradientType=0); +border-color: #e6e6e6 #e6e6e6 #bfbfbf; +border-color: rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25); +filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); +border: 1px solid #ccc; +border-bottom-color: #bbb; +-webkit-border-radius: 4px; +-moz-border-radius: 4px; +border-radius: 4px; +-webkit-box-shadow: inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05); +-moz-box-shadow: inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05); +box-shadow: inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05); +cursor: pointer; +filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); +} +.member-status-symbol, .social-icon { + vertical-align: middle; +} +a.social-link:hover { + background: transparent; +} +.lined-table td { + border-bottom: 1px dashed black; +} diff --git a/app/assets/stylesheets/ipn.css.scss b/app/assets/stylesheets/ipn.css.scss new file mode 100644 index 0000000..ee38bc7 --- /dev/null +++ b/app/assets/stylesheets/ipn.css.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the Ipn controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/users.css.scss b/app/assets/stylesheets/users.css.scss index d372b96..0176045 100644 --- a/app/assets/stylesheets/users.css.scss +++ b/app/assets/stylesheets/users.css.scss @@ -6,8 +6,16 @@ .hoverinfo { cursor: progress; } .payment_links { background-color: #ddd; padding: 1em; border-radius: 1em; - display: inline-block; float: right; } + display: inline-block; float: right; max-width: 30%; min-width: 10em;} + +.payment-highlighted { + background-color: orange !important; +} .avatar { height: 2em; width: 2em; } +.avatar-large { +vertical-align: top; +} + textarea { height: 10em; } diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 7f162a0..3094f18 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -7,9 +7,18 @@ class ApplicationController < ActionController::Base else flash[:alert] = "Nothing to see here!" end + Rails.logger.warn "----------\r\nWARNING: AccessDenied Exception: #{exception.inspect} User: #{current_user.inspect}\r\n----------" redirect_to root_url end @payment_methods = [[nil],["PayPal"],["Dwolla"],["Bill Pay"],["Check"],["Cash"],["Other"]] @payment_instructions = {nil => nil, :paypal => "Set up a monthly recurring payment to hslfinances@gmail.com", :dwolla => "Set up a monthly recurring payment to hslfinances@gmail.com", :billpay => "Have your bank send a monthly check to HeatSync Labs Treasurer, 140 W Main St, Mesa AZ 85201", :check => "Mail to HeatSync Labs Treasurer, 140 W Main St, Mesa AZ 85201 OR put in the drop safe at the Lab with a deposit slip firmly attached each month.", :cash => "Put in the drop safe at the Lab with a deposit slip firmly attached each month.", :other => "Hmm... talk to a Treasurer!"} end + +# Add a "fit" function to sanitize inputs for mac history +class Fixnum + def fit(range) + self > range.max ? range.max : (self < range.min ? range.min : self) + end +end + diff --git a/app/controllers/cards_controller.rb b/app/controllers/cards_controller.rb index e270692..8104783 100644 --- a/app/controllers/cards_controller.rb +++ b/app/controllers/cards_controller.rb @@ -11,15 +11,16 @@ class CardsController < ApplicationController if can? :read, DoorLog then most_active_count = 0 + runner_up_count = 0 @most_active_card = nil + @runner_up_card = nil @cards.each do |card| card_num_R = card.card_number.to_i(16)%32767 - card[:accesses_this_week] = DoorLog.where('key = "R" AND data =? AND created_at > ?', card_num_R, DateTime.now - 7.days).order("created_at DESC").count - if(card[:accesses_this_week] > most_active_count) then - most_active_count = card[:accesses_this_week] - @most_active_card = card - end + card[:accesses_this_week] = DoorLog.where("key = ? AND data = ? AND created_at > ?", 'G', card_num_R, DateTime.now - 1.month).order("created_at DESC").group_by { |d| d.created_at.beginning_of_day }.count end + @most_active_cards = @cards.sort{|a,b| b[:accesses_this_week] <=> a[:accesses_this_week]} + @most_active_card = @most_active_cards[0] + @runner_up_card = @most_active_cards[1] end respond_to do |format| @@ -33,7 +34,7 @@ class CardsController < ApplicationController def show if can? :read, DoorLog then card_num_R = @card.card_number.to_i(16)%32767 - @door_logs = DoorLog.where('key = "R" AND data =?', card_num_R).order("created_at DESC") + @door_logs = DoorLog.where('key = ? AND data = ?', "G", card_num_R).order("created_at DESC") end respond_to do |format| format.html # show.html.erb diff --git a/app/controllers/certifications_controller.rb b/app/controllers/certifications_controller.rb index ac8cb88..4f91dc4 100644 --- a/app/controllers/certifications_controller.rb +++ b/app/controllers/certifications_controller.rb @@ -1,6 +1,6 @@ class CertificationsController < ApplicationController load_and_authorize_resource :certification - load_and_authorize_resource :user, :through => :certification + #load_and_authorize_resource :user, :through => :certification before_filter :authenticate_user! # GET /certifications diff --git a/app/controllers/door_logs_controller.rb b/app/controllers/door_logs_controller.rb index 345fa72..2f7df9d 100644 --- a/app/controllers/door_logs_controller.rb +++ b/app/controllers/door_logs_controller.rb @@ -26,6 +26,7 @@ class DoorLogsController < ApplicationController # GET /door_logs/auto_download def auto_download @results = DoorLog.download_from_door + @status = DoorLog.download_status # for space_api respond_to do |format| format.html # show.html.erb diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb index da2d5ef..61422ed 100644 --- a/app/controllers/home_controller.rb +++ b/app/controllers/home_controller.rb @@ -5,14 +5,22 @@ def index @recent_certs = UserCertification.where("created_at > ?", DateTime.now - 7.days).count @num_users = User.count @recent_users = User.where("created_at > ?", DateTime.now - 7.days).count + # Payments: member levels are multipled by 10 to indicate current payment; 25 x 10 = 250 + @num_paid_users = User.all.select{|u| u.member_status >= 250 }.count + @num_plus_users = User.all.select{|u| u.member_status == 1000 }.count + @num_basic_users = User.all.select{|u| u.member_status == 500 }.count + @num_associate_users = User.all.select{|u| u.member_status == 250 }.count + @num_delinquent_users = User.all.select{|u| !u.payment_status }.count if can? :read, User then @recent_user_names = User.where("member_level > 10").accessible_by(current_ability).order('created_at desc').limit(5) end @num_door_opens = DoorLog.where("key = 'G'").count @today_door_opens = DoorLog.where("key = 'G' AND created_at > ?", DateTime.now - 1.day).count @recent_door_opens = DoorLog.where("key = 'G' AND created_at > ?", DateTime.now - 7.days).count - @num_door_denieds = DoorLog.where("key = 'f'").count - @recent_door_denieds = DoorLog.where("key = 'f' AND created_at > ?", DateTime.now - 7.days).count + @num_door_denieds = DoorLog.where("key = 'D'").count + @recent_door_denieds = DoorLog.where("key = 'D' AND created_at > ?", DateTime.now - 1.month).count + @num_logins = User.sum('sign_in_count') + @recent_logins = User.where('current_sign_in_at > ?',Date.today - 7.days).count @num_macs = Mac.count @recent_macs = Mac.where("since > ?", DateTime.now - 1.day).count @@ -21,4 +29,10 @@ def index end end +def more_info + respond_to do |format| + format.html # more_info.html.erb + end +end + end diff --git a/app/controllers/ipns_controller.rb b/app/controllers/ipns_controller.rb new file mode 100644 index 0000000..6451415 --- /dev/null +++ b/app/controllers/ipns_controller.rb @@ -0,0 +1,54 @@ +class IpnsController < ApplicationController + load_and_authorize_resource :ipn, :except => [:new, :create] + before_filter :authenticate_user!, :except => [:new, :create] + + protect_from_forgery :except => [:create] + + def index + @ipns = Ipn.all + end + + def show + end + + def new + end + + def create + @ipn = Ipn.new_from_dynamic_params(params) + @ipn.data = params.to_json + @ipn.save + render :nothing => true + #unless @ipn.validate! + # Rails.logger.error "Unable to validate IPN: #{@ipn.inspect}" + #end + end + + def import + @ipn = Ipn.new_from_dynamic_params(params) + @ipn.data = params.to_json + @ipn.save + redirect_to ipn_path(@ipn) + #unless @ipn.validate! + # Rails.logger.error "Unable to validate IPN: #{@ipn.inspect}" + #end + end + + def validate + if @ipn.validate! + redirect_to ipns_url, :notice => 'Valid!' + else + redirect_to ipns_url, :notice => 'INVALID' + end + end + + def link + result = @ipn.link_payment + if result.first + redirect_to ipns_url, :notice => 'Payment was successfully linked.' + else + redirect_to ipns_url, :notice => result.last + end + end + +end diff --git a/app/controllers/macs_controller.rb b/app/controllers/macs_controller.rb index 245dfd7..05e6271 100644 --- a/app/controllers/macs_controller.rb +++ b/app/controllers/macs_controller.rb @@ -1,12 +1,66 @@ class MacsController < ApplicationController -load_and_authorize_resource :mac, :except => [:index, :scan, :import] -load_and_authorize_resource :user, :through => :mac, :except => [:index, :show, :scan, :import] +load_and_authorize_resource :mac, :except => [:create, :history] +#load_and_authorize_resource :user, :through => :mac, :except => [:index, :show, :scan, :import] + +before_filter :arp_lookup, :only => :new #require "active_record" require "optparse" #require "rubygems" def index + recent_mac_logs_ungrouped = MacLog.last(1000) + if recent_mac_logs_ungrouped.present? + @mac_time_start_date = recent_mac_logs_ungrouped.first.created_at + recent_mac_logs = recent_mac_logs_ungrouped.group_by(&:mac) + @mac_times = {} + # Go thru each mac + recent_mac_logs.each do |mac_log| + last_active = nil + # And the entries for each mac (mac_log.first is the string, mac_log.last is the array) + mac_log.last.each do |entry| + # Find an activate followed immediately by a deactivate + if entry.action == "activate" + last_active = entry + else + if last_active && entry.action == "deactivate" + # Calculate the time difference between the two and append to this mac's total time + this_entry = @mac_times[entry.mac] + if this_entry + this_time = this_entry[:time] + else + this_time = 0 + end + @mac_times[entry.mac] = {:mac => entry, :time => (entry.created_at - last_active.created_at) + this_time} + else + # No pair found; discard. + last_active = nil + end + end + end + end + @mac_times_sorted = @mac_times.sort{|a,b| b.last[:time] <=> a.last[:time] } + @most_active_mac = nil + @runner_up_mac = nil + @mac_times_sorted.each do |mac_time| + unless @most_active_mac + this_mac = Mac.find_by_mac(mac_time.first) + unless this_mac.hidden + @most_active_mac = this_mac + @most_active = mac_time + end + else + unless @runner_up_mac + this_mac = Mac.find_by_mac(mac_time.first) + unless this_mac.hidden + @runner_up_mac = this_mac + @runner_up = mac_time + end + end + end + end + end + #@active_macs = Mac.where(:active => true, :hidden => false) #@active_macs += Mac.where(:active => true, :hidden => nil) @@ -16,7 +70,7 @@ def index elsif user_signed_in? then @active_macs = Mac.where("macs.active = ? AND (macs.hidden IS NULL OR macs.hidden = ?)", true, false).includes(:user).order("users.name ASC").group("users.name") else - @active_macs = Mac.select("mac, note, user_id").where("macs.active = ? AND (macs.hidden IS NULL OR macs.hidden = ?)", true, false).joins(:user).order("users.name ASC").group("users.name") + @active_macs = Mac.select("mac, note, user_id").where("macs.active = ? AND (macs.hidden IS NULL OR macs.hidden = ?)", true, false).joins(:user).order("users.name ASC").group("users.name, mac, note, user_id") end @hidden_macs = Mac.where("macs.active = ? AND macs.hidden = ?", true, true).order("note ASC") @@ -32,10 +86,52 @@ def index end end + def history + authorize! :read_details, Mac + begin + @start_date = DateTime.parse(params[:start]) + @end_date = DateTime.parse(params[:end]) + rescue + @start_date = DateTime.now - 2.weeks + @end_date = DateTime.now + end + + @mac_logs_by_hour = MacLog.where("created_at > ? AND created_at < ?", @start_date, @end_date).group_by{|m| m.created_at.beginning_of_hour} + @mac_log_graph = [] + mac_running_balance = 0 + lowest_balance = 0 + @mac_logs_by_hour.each do |time, mac_log| + mac_log.each do |entry| + # Add one computer for activate, subtract one for deactivate + if entry.action == "activate" + mac_running_balance += 1 + elsif entry.action == "deactivate" + mac_running_balance -= 1 + end + # Keep track of the lowest number in the graph + if mac_running_balance < lowest_balance + lowest_balance = mac_running_balance + end + end + @mac_log_graph << [time.to_time.to_i*1000,mac_running_balance] + end + + if lowest_balance != 0 + # Subtract a negative balance to raise everything + @mac_log_graph = @mac_log_graph.map{ |time,balance| [time, balance - lowest_balance] } + end + + respond_to do |format| + format.html + format.json { render :json => @mac_log_graph } + end + end + # GET /macs/1 # GET /macs/1.json def show @mac = Mac.find(params[:id]) + @mac_logs = MacLog.where(:mac => @mac.mac) respond_to do |format| format.html # show.html.erb @@ -70,26 +166,34 @@ end end # POST /macs - # POST /user def create @mac = Mac.new(params[:mac]) - authorize! :update, @mac - - if can? :manage, Mac then - @users = User.accessible_by(current_ability).sort_by(&:name) - else - @users = [current_user] - end - - respond_to do |format| - if @mac.save - format.html { redirect_to macs_path, :notice => 'Mac was successfully created.' } - format.json { render :json => @mac, :status => :created, :location => @mac } + @existing_mac = Mac.find_by_mac(@mac.mac) + if can? :manage, Mac then + @users = User.accessible_by(current_ability).sort_by(&:name) + else + @users = [current_user] + end + + if @existing_mac.present? + if @existing_mac.user_id.nil? + redirect_to edit_mac_path(@existing_mac), :notice => 'This MAC already exists, edit it here:' + else +@mac.errors.add(:user,"for this MAC is already set to #{@existing_mac.user.name} -- please contact them or an admin if this is incorrect.") + render :action => "new" + end else - format.html { render :action => "new" } - format.json { render :json => @mac.errors, :status => :unprocessable_entity } + + respond_to do |format| + if @mac.save + format.html { redirect_to macs_path, :notice => 'MAC was successfully created.' } + format.json { render :json => @mac, :status => :created, :location => @mac } + else + format.html { render :action => "new" } + format.json { render :json => @mac.errors, :status => :unprocessable_entity } + end + end end - end end # PUT /macs/1 @@ -109,7 +213,7 @@ end respond_to do |format| if @mac.save - format.html { redirect_to macs_path, :notice => 'Mac was successfully updated.' } + format.html { redirect_to macs_path, :notice => 'MAC was successfully updated.' } format.json { head :no_content } else format.html { render :action => "edit" } @@ -118,115 +222,118 @@ end end end - +def arp_lookup + @ip = request.env['REMOTE_ADDR'] + @arp = %x(/usr/sbin/arp -a | grep #{@ip}) +end def scan Rails.logger.info "starting scan..." - # Command line arguments - options = {}; - OptionParser.new { |opts| - opts.banner = "Usage: pamela-scanner.rb --interface=en0" + # Command line arguments + options = {}; + OptionParser.new { |opts| + opts.banner = "Usage: pamela-scanner.rb --interface=en0" - options[:verbose] = true - opts.on("v", "--verbose", "Run verbosely") { |verbose| - options[:verbose] = verbose - } + options[:verbose] = true + opts.on("v", "--verbose", "Run verbosely") { |verbose| + options[:verbose] = verbose + } - options[:interface] = "eth0" - opts.on("i", "--interface=interface", "Network Interface") { |interface| - options[:interface] = interface - } + options[:interface] = "eth0" + opts.on("i", "--interface=interface", "Network Interface") { |interface| + options[:interface] = interface + } - options[:max_age] = 20 - opts.on("a", "--max-age=minutes", "Minutes to keep expired macs active") { |max_age| - options[:max_age] = max_age.to_i - } + options[:max_age] = 20 + opts.on("a", "--max-age=minutes", "Minutes to keep expired macs active") { |max_age| + options[:max_age] = max_age.to_i + } - options[:db_host] = "configure_me" - opts.on("r", "--db-host=host", "Database Host") { |host| - options[:db_host] = host - } + options[:db_host] = "configure_me" + opts.on("r", "--db-host=host", "Database Host") { |host| + options[:db_host] = host + } - options[:db_name] = "configure_me" - opts.on("n", "--db-name=name", "Database Name") { |name| - options[:db_name] = name - } + options[:db_name] = "configure_me" + opts.on("n", "--db-name=name", "Database Name") { |name| + options[:db_name] = name + } - options[:db_user] = "configure_me" - opts.on("u", "--db-user=user", "Database User") { |user| - options[:db_user] = user - } + options[:db_user] = "configure_me" + opts.on("u", "--db-user=user", "Database User") { |user| + options[:db_user] = user + } - options[:db_password] = "configure_me" - opts.on("p", "--db-password=password", "Database Password") { |password| - options[:db_password] = password - } + options[:db_password] = "configure_me" + opts.on("p", "--db-password=password", "Database Password") { |password| + options[:db_password] = password + } - }.parse! + }.parse! - # Open the database - #ActiveRecord::Base::establish_connection( - # :adapter => "mysql2", - # :host => options[:db_host], - # :database => options[:db_name], - # :username => options[:db_user], - # :password => options[:db_password]) + # Open the database + #ActiveRecord::Base::establish_connection( + # :adapter => "mysql2", + # :host => options[:db_host], + # :database => options[:db_name], + # :username => options[:db_user], + # :password => options[:db_password]) - #class Mac < ActiveRecord::Base - #end + #class Mac < ActiveRecord::Base + #end - #class MacLog < ActiveRecord::Base - #end + #class MacLog < ActiveRecord::Base + #end - # Scan the network for mac addresses - macs = {}; - command = sprintf("arp-scan -R --interface=%s --localnet", options[:interface]) - if options[:verbose] - Rails.logger.info "Running [#{command}]" - end - IO.popen(command) { |stdin| - Rails.logger.info "Reading stdin: "+stdin.inspect - stdin.each { |line| - next if line !~ /^([\d\.]+)\s+([[:xdigit:]:]+)\s/; - macs[$2] = $1; - } - } + # Scan the network for mac addresses + macs = {}; + command = sprintf("arp-scan -R --interface=%s --localnet", options[:interface]) + if options[:verbose] + Rails.logger.info "Running [#{command}]" + end + IO.popen(command) { |stdin| + Rails.logger.info "Reading stdin: "+stdin.inspect + stdin.each { |line| + next if line !~ /^([\d\.]+)\s+([[:xdigit:]:]+)\s/; + macs[($2).downcase] = ($1).downcase; + } + } - # Scan the existing macs and update each record as necessary - Mac.find(:all).each { |entry| - mac = entry.mac.downcase - ip = entry.ip - if macs.has_key?(mac) - if ! entry.active || ! entry.since - Rails.logger.info "Activating #{mac} at #{ip}" if options[:verbose] - entry.since = Time.now - MacLog.new(:mac => mac, :ip => ip, :action => "activate").save - end - entry.active = 1 - entry.ip = ip - entry.refreshed = Time.now - entry.save - macs.delete(mac) - next - end + # Scan the existing macs and update each record as necessary + Mac.find(:all).each { |entry| + mac = entry.mac.downcase + ip = entry.ip + if macs.has_key?(mac) + if ! entry.active || ! entry.since + Rails.logger.info "Activating #{mac} at #{ip}" if options[:verbose] + entry.since = Time.now + MacLog.new(:mac => mac, :ip => ip, :action => "activate").save + end + entry.active = 1 + entry.ip = ip + entry.refreshed = Time.now + entry.save + macs.delete(mac) + next + end - # Entry is no longer current - if entry.active - ageMinutes = ((Time.now - entry.refreshed)/60).to_i - next if ageMinutes < options[:max_age] - Rails.logger.info "Deactivating #{mac}, #{ageMinutes} minutes old" if options[:verbose] - entry.active = 0 - entry.save - MacLog.new(:mac => mac, :ip => ip, :action => "deactivate").save - end - } + # Entry is no longer current + if entry.active + ageMinutes = ((Time.now - entry.refreshed)/60).to_i + next if ageMinutes < options[:max_age] + Rails.logger.info "Deactivating #{mac}, #{ageMinutes} minutes old" if options[:verbose] + entry.active = 0 + entry.save + MacLog.new(:mac => mac, :ip => ip, :action => "deactivate").save + end + } - # Add entries for any macs not already in the db - macs.each { |mac, ip| - Rails.logger.info "Activating new entry #{mac} at #{ip}" if options[:verbose] - Mac.new(:mac => mac, :ip => ip, :active => 1, :since => Time.now, :refreshed => Time.now).save - Rails.logger.info MacLog.new(:mac => mac, :ip => ip, :action => "activate").save - } + # Add entries for any macs not already in the db + macs.each { |mac, ip| + Rails.logger.info "Activating new entry #{mac} at #{ip}" if options[:verbose] + Mac.new(:mac => mac, :ip => ip, :active => 1, :since => Time.now, :refreshed => Time.now).save + Rails.logger.info MacLog.new(:mac => mac, :ip => ip, :action => "activate").save + } @log = MacLog.all diff --git a/app/controllers/payments_controller.rb b/app/controllers/payments_controller.rb index 7d07630..a1a4a2e 100644 --- a/app/controllers/payments_controller.rb +++ b/app/controllers/payments_controller.rb @@ -1,6 +1,6 @@ class PaymentsController < ApplicationController load_and_authorize_resource :payment - load_and_authorize_resource :user, :through => :payment + #load_and_authorize_resource :user, :through => :payment before_filter :authenticate_user! # Load users and certs based on current ability @@ -16,6 +16,10 @@ class PaymentsController < ApplicationController # GET /payments.json def index @payments = @payments.order("date DESC") + @graph = { :members => chart("members"), + :total => chart("total"), + :basic => chart("basic"), + :associate => chart("associate")} respond_to do |format| format.html # index.html.erb @@ -23,6 +27,60 @@ class PaymentsController < ApplicationController end end + # Private method for index charts + def chart name + chart_name = name || "total" + if chart_name == "total" + chart_type = [25, 50, 100] + elsif chart_name == "members" + chart_type = [25, 50, 100] + elsif chart_name == "basic" + chart_type = [50] + elsif chart_name == "associate" + chart_type = [25] + else + chart_type = [] + end + + payment_months = @payments.sort_by(&:date).group_by{ |p| p.date.beginning_of_month } + @payments_by_month = [] + payment_months.each do |month| + # Only grab the last year from today + if month.first > (Date.today - 1.year) && month.first < Date.today + # Calculate sum of amounts for each month and store at end of month array + @payments_by_month << [month.first.to_time.to_i*1000, month.last.sum{|p| + amount = amount_or_level(p) + if chart_type.include?(amount) + if chart_name == "members" + 1 # Output 1 to count members + else + amount # Output dollars to count amount + end + else + 0 + end + }] + end + end + + return @payments_by_month + end + + def amount_or_level p + if p.amount + return p.amount.to_i + else + if p.user + Rails.logger.info p.user.member_level + return p.user.member_level.to_i + else + Rails.logger.info p.inspect + Rails.logger.info p.user.inspect + return 0 + end + end + end + # GET /payments/1 # GET /payments/1.json def show diff --git a/app/controllers/paypal_csvs_controller.rb b/app/controllers/paypal_csvs_controller.rb new file mode 100644 index 0000000..2f48d23 --- /dev/null +++ b/app/controllers/paypal_csvs_controller.rb @@ -0,0 +1,28 @@ +class PaypalCsvsController < ApplicationController + load_and_authorize_resource :paypal_csv + before_filter :authenticate_user! + + def index + end + + def show + end + + def new + end + + def create + PaypalCsv.batch_import_from_csv(params[:file].path) + redirect_to paypal_csvs_path, :notice => 'Paypal CSV batch was successfully loaded.' + end + + def link + result = @paypal_csv.link_payment + if result.first + redirect_to paypal_csvs_url, :notice => 'Payment was successfully linked.' + else + redirect_to paypal_csvs_url, :notice => result.last + end + end + +end diff --git a/app/controllers/settings_controller.rb b/app/controllers/settings_controller.rb new file mode 100644 index 0000000..3a1bd19 --- /dev/null +++ b/app/controllers/settings_controller.rb @@ -0,0 +1,32 @@ +class SettingsController < ApplicationController + authorize_resource + + def index + @settings = Setting.all + @@default_settings.each do |key, value| + if @settings[key].blank? + @settings[key] = value + end + end + end + + def edit + value = Setting[params[:id].to_sym] + if value.present? + @setting = {} + @setting[:var] = params[:id] + @setting[:value] = value + elsif @@default_settings[params[:id].to_sym].present? + @setting = {} + @setting[:var] = params[:id] + @setting[:value] = @@default_settings[params[:id].to_sym] + end + end + + def update + Setting[params[:id]] = params[:value] + + redirect_to settings_path + end + +end diff --git a/app/controllers/space_api_controller.rb b/app/controllers/space_api_controller.rb new file mode 100644 index 0000000..fca6c8e --- /dev/null +++ b/app/controllers/space_api_controller.rb @@ -0,0 +1,96 @@ +class SpaceApiController < ApplicationController + # Individually remove authorizing stuff since there is no SpaceApi model + authorize_resource :except => [:index, :access, :access_post] + # User auth here happens via params, instead of form. + before_filter :authenticate_user!, :except => [:index, :access, :access_post] + + def index + @json = JSON.parse(Setting.space_api_json_template) + door_status = DoorLog.show_status # Expect {:unlocked => boolean, :door_1_locked => boolean, :door_2_locked => boolean} + + @json["open"] = door_status[:unlocked] + + if( door_status[:unlocked] ) + @json["status"] = "doors_open=both" + elsif( !door_status[:door_1_locked] ) + @json["status"] = "doors_open=door1" + elsif( !door_status[:door_2_locked] ) + @json["status"] = "doors_open=door2" + else + @json["status"] = "doors_open=none" + end + + respond_to do |format| + format.html + format.json { + response.headers["Cache-Control"] = "no-cache, no-store, max-age=0, must-revalidate" + render :json => @json + } + end + end + + def access + @status = DoorLog.show_status + + # Nothing, just render form + unless user_signed_in? + @output = "Invalid email or password. Please login with your Members DB email and password below." + else + unless can? :access_doors_remotely, :door_access + @output = "Sorry, your account isn't able to control doors remotely." + end + end + end + + def access_post + @output = "" + + #if params['cmd'] == "check-login" then + # if users[params['user']] && users[params['user']]['pass'].to_s == (Digest::SHA2.new(bitlen=512) << params['pass']).to_s then + # @output += '{ "login": "okay" }' + # else + # @output += '{ "login": "fail" }' + # end + # + # exit + #end + + # Stop unless signed in already, OR if the supplied user/pass params are good. + unless current_user || check_auth(params['user'],params['pass']) + @output += "Invalid email or password." + else + # Stop unless the user can access the door system + unless can? :access_doors_remotely, :door_access + @output += "Sorry, your account isn't able to control doors remotely. Ask an admin if this is incorrect." + Rails.logger.warn "----------\r\nWARNING: DOOR ACCESS ATTEMPT DENIED. USER #{current_user.inspect}\r\n----------" + else + # Stop unless we've got a command to run + unless params['cmd'] + @output += "No command specified." + else + # Log the access + Rails.logger.info "Door access: user #{current_user.inspect}" + DoorLog.create!({:key => "rem_"+DoorLog.parse_command(params['cmd'])[:url_param], :data => current_user.id}) + + # Execute the access + @output += DoorLog.execute_command(params['cmd']) + end + end + end + + # Render the form again + render :access + end + + def check_auth(email,password) + resource = User.find_by_email(email) + if resource && resource.valid_password?(password) + resource.remember_me = true + sign_in :user, resource + return true + else + return false + end + end + +end diff --git a/app/controllers/statistics_controller.rb b/app/controllers/statistics_controller.rb new file mode 100644 index 0000000..3b33c90 --- /dev/null +++ b/app/controllers/statistics_controller.rb @@ -0,0 +1,68 @@ +class StatisticsController < ApplicationController + before_filter :load_and_authorize_user + + def index + end + + def door_log + # Get own user's door data + cards = @user.cards + card_hash = {} + cards.each{|c| card_hash[c.card_number.to_i(16)%32767] = c.card_number} + card_num_Rs = cards.map{|c| c.card_number.to_i(16)%32767} + @door_logs = DoorLog.where("data = ?", card_num_Rs).order("created_at ASC") + @door_logs.map{|l| + l.data = card_hash[l.data.to_i].to_i(16) + l.key = DoorLog.key_legend[l.key] + } + + @door_log_graph = [] + @door_logs.where("key = 'G'").group_by{|l| l.created_at.beginning_of_day}.each{|l| @door_log_graph << [l.first.to_time.to_i*1000,l.last.size]} + + respond_to do |format| + format.html + format.json { render :json => @door_logs } + end + end + + def mac_log + macs = @user.macs.where(:hidden => false).map{|m| m.mac} + @mac_logs = MacLog.where(:mac => macs) + @mac_log_graph = {} + macs.each do |mac| + mac_log = MacLog.where(:mac => mac) + + mac_times = [] + last_active = nil + mac_log.each do |entry| + # Find an activate followed immediately by a deactivate + if entry.action == "activate" + last_active = entry + else + if last_active && entry.action == "deactivate" + # Calculate the time difference between the two and append to this mac's total time + mac_times << [entry.created_at, ((entry.created_at - last_active.created_at)/60/60)] + else + # No pair found; discard. + last_active = nil + end + end + end + mac_log_graph = [] + mac_times.group_by{|m| m.first.beginning_of_day}.each{|m| mac_log_graph << [m.first.to_time.to_i*1000,m.last.map{|n| n.last}.sum.round(2)]} + # Store each mac in the hash with its graph + @mac_log_graph[mac] = mac_log_graph + end + #@mac_log_graph = mac_log_grouped.map{|g| [g.first.to_time.to_i*1000, g.last.size] } + + respond_to do |format| + format.html + format.json { render :json => @mac_logs } + end + end + + def load_and_authorize_user + @user = current_user + authorize! :read, @user + end +end diff --git a/app/controllers/user_certifications_controller.rb b/app/controllers/user_certifications_controller.rb index 6d222ee..c6ce624 100644 --- a/app/controllers/user_certifications_controller.rb +++ b/app/controllers/user_certifications_controller.rb @@ -1,12 +1,12 @@ class UserCertificationsController < ApplicationController load_and_authorize_resource :user_certification - load_and_authorize_resource :user, :through => :user_certification - load_and_authorize_resource :certification, :through => :user_certification + #load_and_authorize_resource :user, :through => :user_certification + #load_and_authorize_resource :certification, :through => :user_certification before_filter :authenticate_user! # Load users and certs based on current ability before_filter :only => [:new, :edit, :create, :update] do - @users = User.where(:hidden => false).accessible_by(current_ability).sort_by(&:name) + @users = User.where(:hidden => [false,nil]).accessible_by(current_ability).sort_by(&:name) @certifications = Certification.accessible_by(current_ability).sort_by(&:name) end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 7a6571b..04cf7b3 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -2,14 +2,24 @@ class UsersController < ApplicationController load_and_authorize_resource before_filter :authenticate_user! + def sort_by_cert(certs,id) + result = 0 + certs.each do |c| + if c.id == id + result = 1 + end + end + return result + end + # GET /users # GET /users.json def index case params[:sort] when "name" @users = @users.sort_by(&:name) - when "certifications" - @users = @users.sort_by{ |u| [-u.certifications.count,u.name] } + when "cert" + @users = @users.sort_by{ |u| [-sort_by_cert(u.certifications,params[:cert].to_i),u.name] } when "orientation" @users = @users.sort_by{ |u| [-u.orientation.to_i,u.name] } when "waiver" @@ -33,6 +43,24 @@ class UsersController < ApplicationController end end + # 'Active' users who haven't paid recently + def inactive + @users = @users.all.select{|u| u if u.payment_status == false }.sort_by{ |u| -u.delinquency } + end + + # Recent user activity + def activity + @zombie_members = User.where('sign_in_count = 0').where('member_level > 1') + @user_logins = User.where(:current_sign_in_at => 2.months.ago..Time.now).where('sign_in_count > 1') + @new_users = User.where(:created_at => 3.months.ago..Date.today) + @cardless_users = User.includes('cards').where(['users.member_level >= ?','50']).where('cards.id IS NULL') + end + + # New members (for emailing out) + def new_member_report + @new_users = User.where(:created_at => 3.months.ago..Date.today).where(:hidden => false).where(['member_level >= ?','1']) + end + # GET /users/1 # GET /users/1.json def show @@ -43,6 +71,32 @@ class UsersController < ApplicationController end end + def compose_email + @user = User.find(params[:user_id]) + authorize! :read, @user + end + + def send_email + @user = User.find(params[:user_id]) + authorize! :read, @user + @subject = params[:subject] + @body = params[:body] + if @user.send_email(current_user,@subject,@body) + redirect_to user_path(@user), :notice => "Email sent successfully." + else + flash[:alert] = "Error sending email." + render :compose_email + end + end + + # GET /user_summary/1 + def user_summary + respond_to do |format| + format.html { render :partial => "user_summary" } # show.html.erb + format.json { render :json => @user } + end + end + # GET /users/new # GET /users/new.json def new @@ -61,7 +115,7 @@ class UsersController < ApplicationController def create respond_to do |format| if @user.save - format.html { redirect_to users_url, :notice => 'User was successfully created.' } + format.html { redirect_to @user, :notice => 'User was successfully created.' } format.json { render :json => @user, :status => :created, :location => @user } else format.html { render :action => "new" } @@ -75,7 +129,7 @@ class UsersController < ApplicationController def update respond_to do |format| if @user.update_attributes(params[:user]) - format.html { redirect_to users_url, :notice => 'User was successfully updated.' } + format.html { redirect_to @user, :notice => 'User was successfully updated.' } format.json { head :no_content } else format.html { render :action => "edit" } @@ -84,6 +138,37 @@ class UsersController < ApplicationController end end + # GET /users/merge + def merge_view + @users = @users.sort_by(&:name) + + respond_to do |format| + format.html # merge_view.html.erb + end + end + + # POST /users/merge + def merge_action + @user_to_keep = User.find(params[:user][:to_keep]) + Rails.logger.info "USER TO KEEP:" + Rails.logger.info @user_to_keep.inspect + @user_to_merge = User.find(params[:user][:to_merge]) + Rails.logger.info "USER TO MERGE:" + Rails.logger.info @user_to_merge.inspect + + @user_to_keep.absorb_user(@user_to_merge) + + Rails.logger.info "RESULT:" + Rails.logger.info @user_to_keep.inspect + Rails.logger.info @user_to_keep.cards.inspect + Rails.logger.info @user_to_keep.user_certifications.inspect + Rails.logger.info @user_to_keep.payments.inspect + + respond_to do |format| + format.html { redirect_to @user_to_keep, :notice => 'Users successfully merged.' } + end + end + # DELETE /users/1 # DELETE /users/1.json def destroy diff --git a/app/helpers/ipn_helper.rb b/app/helpers/ipn_helper.rb new file mode 100644 index 0000000..a56c555 --- /dev/null +++ b/app/helpers/ipn_helper.rb @@ -0,0 +1,2 @@ +module IpnHelper +end diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb index a94aae3..4370377 100644 --- a/app/mailers/user_mailer.rb +++ b/app/mailers/user_mailer.rb @@ -1,13 +1,20 @@ class UserMailer < ActionMailer::Base - default :from => "wiki@heatsynclabs.org" + default :from => "no-reply@heatsynclabs.org" def new_user_email(user) @user = user @url = "http://members.heatsynclabs.org" - #@admins = User.where(:name => "Will Bradley") - #@admins.each do |admin| - mail(:to => 'info@heatsynclabs.org', :subject => "New HSL Member: "+user.name) - #end + mail(:to => 'member-notifications@heatsynclabs.org', + :subject => "New HSL Member: "+user.name) + end + + def email(to_user,from_user,subject,body) + @url = "http://members.heatsynclabs.org" + @body = body + @from_user = from_user + + mail(:to => to_user.email, + :subject => "HSL Message: "+subject) end end diff --git a/app/models/ability.rb b/app/models/ability.rb index 5bb255e..4253222 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -2,8 +2,8 @@ class Ability include CanCan::Ability def initialize(user) - # Anonymous can read mac - can :read, Mac + can :read, Mac # Anonymous can read mac + can :scan, Mac # Need anonymous so CRON can scan if !user.nil? @@ -13,9 +13,14 @@ class Ability can :read_details, Mac can [:update], Mac, :user_id => nil can [:create,:update], Mac, :user_id => user.id - can :read, User, :id => user.id #TODO: why can users update themselves? + can :read, Payment, :user_id => user.id + can [:read,:new_member_report], User, :id => user.id #TODO: why can users update themselves? Maybe because Devise doesn't check users/edit? can :read, UserCertification, :user_id => user.id + if user.card_access_enabled + can :access_doors_remotely, :door_access + end + # Instructors can manage certs and see users if user.instructor? can :manage, Certification @@ -29,9 +34,11 @@ class Ability can :read, UserCertification end - # Accountants can manage all + # Accountants can manage payments if user.accountant? can :manage, Payment + can :manage, Ipn + can :manage, PaypalCsv end # Admins can manage all @@ -40,8 +47,8 @@ class Ability end # Prevent all destruction for now - cannot :destroy, User - cannot :destroy, Card + #cannot :destroy, User + #cannot :destroy, Card cannot :destroy, Certification cannot :destroy, Mac cannot :destroy, MacLog diff --git a/app/models/card.rb b/app/models/card.rb index 1416ac3..700cb3b 100644 --- a/app/models/card.rb +++ b/app/models/card.rb @@ -2,7 +2,8 @@ class Card < ActiveRecord::Base require 'open-uri' attr_accessible :id, :user_id, :name, :card_number, :card_permissions - validates_uniqueness_of :id,:card_number + validates_presence_of :user_id, :card_number, :card_permissions + validates_uniqueness_of :id, :card_number belongs_to :user def upload_to_door diff --git a/app/models/door_log.rb b/app/models/door_log.rb index 20727c5..324e333 100644 --- a/app/models/door_log.rb +++ b/app/models/door_log.rb @@ -2,6 +2,126 @@ class DoorLog < ActiveRecord::Base attr_accessible :data, :key require 'open-uri' + def self.execute_command(command) + output = "" + # load config values + door_access_url = APP_CONFIG['door_access_url'] + door_access_password = APP_CONFIG['door_access_password'] + + #login + source = open("#{door_access_url}?e=#{door_access_password}").read + results = source.scan(/ok/) + + #only continue if we've got an OK login + if(results.size > 0) then + # Parse the command and result + parsing = parse_command(command) + output += parsing[:output] + url_param = parsing[:url_param] + + # Execute the command + open("#{door_access_url}?#{url_param}") + + self.download_status # Update the status cache + else + output += 'Failed to connect to door system.' + end + + #logout + open("#{door_access_url}?e=0000") + + return output + end + + def self.parse_command(command) + output = "" + url_param = "" + # @commands = [ + # ["Unlock all doors","unlock"], + # ["Unlock Front door","unlock-front"], + # ["Unlock Rear door","unlock-rear"], + # ["Lock all doors","lock"], + # ["Lock Front door","lock-front"], + # ["Lock Rear door","lock-rear"], + # ["Open Front door","open-front"], + # ["Open Rear door","open-rear"], + # ["Arm alarm","arm"], + # ["Disarm alarm","disarm"] + # ] + case command + when "open-front" + output += "Front door opened." + url_param = "o1" + when "open-rear" + output += "Rear door opened." + url_param = "o2" + when "u", "unlock" + output += "Doors unlocked, remember to re-lock them." + url_param = "u" + when "u1", "unlock-front" + output += "Front Door unlocked, remember to re-lock it." + url_param = "u=1" + when "u2", "unlock-rear" + output += "Rear Door unlocked, remember to re-lock it." + url_param = "u=2" + when "lock", "l" + output += "Doors locked." + url_param = "l" + when "lock-front", "l1" + output += "Front Door locked." + url_param = "l=1" + when "lock-rear", "l2" + output += "Rear Door locked." + url_param = "l=2" + when "arm" + output += "Armed." + url_param = "2" + when "disarm" + output += "Disarmed." + url_param = "1" + else + output += "Fail. Don't be a naughty user!" + url_param = "9" # Using 9 because it's just status, no harm done + end + return {:output => output, :url_param => url_param} + end + + def self.show_status + door_logs = DoorLog.where(key: ["door_1_locked","door_2_locked"]).order('created_at DESC').take(2) + door_1_locked = parse_locked_status(door_logs, "door_1_locked") + door_2_locked = parse_locked_status(door_logs, "door_2_locked") + + # Doors are unlocked if 1 AND 2 are NOT locked + status = {:unlocked => (!door_1_locked && !door_2_locked), :door_1_locked => door_1_locked, :door_2_locked => door_2_locked } + end + + def self.parse_locked_status(door_logs, door_key) + door_logs_selected = door_logs.select{|s| s.key == door_key } + if door_logs_selected.present? + door_data = door_logs_selected.first.data + if door_data == 0 # 0 = unlocked + return false + else + return true # 1 = locked + end + end + end + + def self.download_status + # load config values + door_access_url = APP_CONFIG['door_access_url'] + door_access_password = APP_CONFIG['door_access_password'] + + # query for status + source = open("#{door_access_url}?9").read + # expect {"armed"=>255, "activated"=>255, "alarm_3"=>1, "alarm_2"=>1, "door_1_locked"=>1, "door_2_locked"=>1} + # See https://github.com/heatsynclabs/Open_Access_Control_Ethernet for more info + @status = JSON.parse(source) + @status.each do |key,value| + DoorLog.create!({:key => key, :data => value}) + end + end + def self.download_from_door # load config values door_access_url = APP_CONFIG['door_access_url'] @@ -41,4 +161,8 @@ class DoorLog < ActiveRecord::Base end end + def self.key_legend + {'G' => "Granted", "R" => "Read", "D" => "Denied", + 'g' => "granted", "r" => "read", "d" => "denied"} + end end diff --git a/app/models/ipn.rb b/app/models/ipn.rb new file mode 100644 index 0000000..417884a --- /dev/null +++ b/app/models/ipn.rb @@ -0,0 +1,95 @@ +require 'net/http' +class Ipn < ActiveRecord::Base + attr_accessible :data + belongs_to :payment + + after_create :create_payment + + def date_parsed + begin + Date.strptime(self.payment_date, "%H:%M:%S %b %e, %Y %Z") + rescue + Date.new + end + end + + def self.new_from_dynamic_params(params) + ipn = Ipn.new() + + ipn.attributes.each do |c| + unless params[c.first.to_sym].nil? + ipn[c.first.to_sym] = params[c.first.to_sym] + end + end + + return ipn + end + + # Post back to Paypal to make sure it's valid + def validate! + uri = URI.parse('https://www.paypal.com/cgi-bin/webscr?cmd=_notify-validate') + + http = Net::HTTP.new(uri.host, uri.port) + http.open_timeout = 60 + http.read_timeout = 60 + http.verify_mode = OpenSSL::SSL::VERIFY_NONE + http.use_ssl = true + response = http.post(uri.request_uri, self.data, + 'Content-Length' => "#{self.data.size}", + 'User-Agent' => "Ruby on Rails" + ).body + + unless ["VERIFIED", "INVALID"].include?(response) + Rails.logger.error "Faulty paypal result: #{response}" + return false + end + unless response == "VERIFIED" + Rails.logger.error "Invalid IPN: #{response}" + Rails.logger.error "Data: #{self.data}" + return false + end + + return true + end + + def link_payment + create_payment + end + + private + def create_payment + # find user by email, then by payee + user = User.where("lower(email) = ?", self._from_email_address.downcase).first + user = User.where("lower(payee) = ?", self._from_email_address.downcase).first if user.nil? && self._from_email_address.present? + + # Only create payments if the IPN matches a member + if user.present? + # And is a payment (not a cancellation, etc) + payment_types = ["subscr_payment","send_money"] + if payment_types.include?(self.txn_type) + # And a member level + if User.member_levels[self.payment_gross.to_i].present? + payment = Payment.new + payment.date = Date.strptime(self.payment_date, "%H:%M:%S %b %e, %Y %Z") + payment.user_id = user.id + payment.amount = self.payment_gross + if payment.save + self.payment_id = payment.id + self.save! + else + return [false, "Unable to link payment. Payment error: #{payment.errors.full_messages.first}"] + end + else + return [false, "Unable to link payment. Couldn't find membership level '#{self.payment_gross.to_i}'."] + end + else + return [false, "Unable to link payment. Transaction is a '#{self.txn_type}' instead of '#{payment_types.inspect}'."] + end + else + return [false, "Unable to link payment. Couldn't find user/payee '#{self.payer_email}'."] + end + + return [true] + end + +end diff --git a/app/models/payment.rb b/app/models/payment.rb index ef9d681..8c4ab5b 100644 --- a/app/models/payment.rb +++ b/app/models/payment.rb @@ -1,8 +1,10 @@ class Payment < ActiveRecord::Base belongs_to :user - attr_accessible :date, :user_id, :created_by + has_one :ipn + has_one :paypal_csv + attr_accessible :date, :user_id, :created_by, :amount - validates_presence_of :user_id, :date, :created_by + validates_presence_of :user_id, :date, :amount # not created_by validates_uniqueness_of :date, :scope => :user_id, :message => ' of payment already exists for this user.' def human_date diff --git a/app/models/paypal_csv.rb b/app/models/paypal_csv.rb new file mode 100644 index 0000000..d150e8e --- /dev/null +++ b/app/models/paypal_csv.rb @@ -0,0 +1,73 @@ +require 'csv' +class PaypalCsv < ActiveRecord::Base + attr_accessible :data, :_address_status, :_counterparty_status, :_currency, :_fee, :_from_email_address, :_gross, :_item_id, :_item_title, :_name, :_net, :_status, :_time, :_time_zone, :_to_email_address, :_transaction_id, :_type, :date, :string + belongs_to :payment + + after_create :create_payment + + def date_parsed + begin + Date.strptime(self._date, "%m/%d/%Y") + rescue + Date.new + end + end + + def self.batch_import_from_csv(filename) + csv = CSV.table(filename) + csv.each do |row| + paypal_csv = PaypalCsv.new() + + paypal_csv.attributes.each do |c| + unless row[c.first.to_sym].nil? + paypal_csv[c.first.to_sym] = row[c.first.to_sym] + end + end + + paypal_csv.data = row.to_json + paypal_csv.save + end + + return true + end + + def link_payment + create_payment + end + + private + def create_payment + # find user by email, then by payee + user = User.where("lower(email) = ?", self._from_email_address.downcase).first + user = User.where("lower(payee) = ?", self._from_email_address.downcase).first if user.nil? && self._from_email_address.present? + + # Only create payments if the CSV matches a member + if user.present? + # And is a payment (not a cancellation, etc) + payment_types = ["Recurring Payment Received","Payment Received"] + if payment_types.include?(self._type) + # And a member level + if User.member_levels[self._gross.to_i].present? + payment = Payment.new + payment.date = Date.strptime(self._date, "%m/%d/%Y") #7/6/2013 for Jul 06 + payment.user_id = user.id + payment.amount = self._gross + if payment.save + self.payment_id = payment.id + self.save! + else + return [false, "Unable to link payment. Payment error: #{payment.errors.full_messages.first}"] + end + else + return [false, "Unable to link payment. Couldn't find membership level '#{self._gross.to_i}'."] + end + else + return [false, "Unable to link payment. Transaction is a '#{self._type}' instead of '#{payment_types.inspect}'."] + end + else + return [false, "Unable to link payment. Couldn't find user/payee '#{self._from_email_address}'."] + end + + return [true] + end +end diff --git a/app/models/setting.rb b/app/models/setting.rb new file mode 100644 index 0000000..58abe63 --- /dev/null +++ b/app/models/setting.rb @@ -0,0 +1,3 @@ +class Setting < RailsSettings::CachedSettings + attr_accessible :var +end diff --git a/app/models/user.rb b/app/models/user.rb index b12a900..1180736 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -9,15 +9,66 @@ class User < ActiveRecord::Base :recoverable, :rememberable, :trackable, :validatable # Setup accessible (or protected) attributes for your model - attr_accessible :email, :password, :password_confirmation, :remember_me, :name, :admin, :instructor, :member, :emergency_name, :emergency_phone, :current_skills, :desired_skills, :waiver, :emergency_email, :phone, :payment_method, :orientation, :member_level, :certifications, :hidden, :marketing_source, :payee, :accountant #TODO: make admin/instructor/member/etc not accessible + attr_accessible :email, :password, :password_confirmation, :remember_me, :name, :admin, :instructor, :member, :emergency_name, :emergency_phone, :current_skills, :desired_skills, :waiver, :emergency_email, :phone, :payment_method, :orientation, :member_level, :certifications, :hidden, :marketing_source, :payee, :accountant, :exit_reason, :twitter_url, :facebook_url, :github_url, :website_url, :email_visible, :phone_visible #TODO: make admin/instructor/member/etc not accessible has_many :cards has_many :user_certifications has_many :certifications, :through => :user_certifications has_many :payments + has_many :macs + + validates_format_of [:twitter_url, :facebook_url, :github_url, :website_url], :with => URI::regexp(%w(http https)), :allow_blank => true after_create :send_new_user_email + def absorb_user(user_to_absorb) + # copy all attributes except email, password, name, and anything that isn't blank on the destination + user_to_absorb.attributes.each_pair {|k,v| + unless (v.nil? || k == :id || k == :email || k == :password || k == :name || k == :password_confirmation || k == :hidden || k == 'hidden' || k == :encrypted_password || !self.attributes[k].blank? ) + Rails.logger.info "Updating "+k.to_s+" from "+self[k].to_s + self[k] = v + Rails.logger.info "Updated "+k.to_s+" to "+self[k].to_s + end + } + + self.save! + + user_to_absorb.cards.each {|card| + Rails.logger.info "CARD BEFORE: "+card.inspect + card.user_id = self.id + card.save! + Rails.logger.info "CARD AFTER: "+card.inspect + } + user_to_absorb.user_certifications.each {|user_cert| + Rails.logger.info "CERT BEFORE: "+user_cert.inspect + user_cert.user_id = self.id + user_cert.save! + Rails.logger.info "CERT AFTER: "+user_cert.inspect + } + user_to_absorb.payments.each {|payment| + Rails.logger.info "PAYMENT BEFORE: "+payment.inspect + payment.user_id = self.id + payment.amount = 0 if payment.amount.nil? # Bypass validation on amount + payment.save! + Rails.logger.info "PAYMENT AFTER: "+payment.inspect + } + + user_to_absorb.destroy + end + + def card_access_enabled + # If the user has at least one card with permission level 1, they have access + self.cards.where(:card_permissions => 1).count > 0 + end + + def name_with_email_and_visibility + if hidden then + "#{name} (#{email}) HIDDEN" + else + "#{name} (#{email})" + end + end + def name_with_payee_and_member_level if payee.blank? then "#{name} - #{member_level_string}" @@ -43,74 +94,117 @@ class User < ActiveRecord::Base end end + def self.member_levels + {25 => "Associate", 50 => "Basic", 75 => "Basic", 100 => "Plus"} + end + + def payment_status + results = payment_status_calculation + return results[:paid] + end + def member_status - case self.member_level.to_i - when 0 - if self.payments.count > 0 then - 2 - else - -1 - end - when 1 - 1 - when 10..24 - 10 - when 25..999 - if self.payments.count > 0 then - if self.payments.last.date < (DateTime.now - 45.days) - 3 - else - case self.member_level.to_i - when 25..49 - 25 - when 50..99 - 50 - when 100..999 - 100 - end - end - else - return 0 - end - end + results = member_status_calculation + return results[:rank] end def member_status_symbol - case self.member_level.to_i - when 0 - if self.payments.count > 0 then - ":(" - else - "" - end - when 1 - "Unable" - when 10..24 - "" - when 25..999 - if self.payments.count > 0 then - if self.payments.last.date < (DateTime.now - 45.days) - "" - else - case self.member_level.to_i - when 25..49 - "" - when 50..99 - "" - when 100..999 - "" - end - end - else - "?" - end + results = member_status_calculation + return "" + end + + def last_payment_date + self.payments.maximum(:date) + end + + def delinquency + if self.payments.count > 0 + paydate = self.payments.maximum(:date) + (Date.today - paydate).to_i + else + (Date.today - self.created_at.to_date).to_i end end + def send_email(from_user,subject,body) + Rails.logger.info UserMailer.email(self,from_user,subject,body).deliver + end + private def send_new_user_email Rails.logger.info UserMailer.new_user_email(self).deliver end + def member_status_calculation + # Begin output buffer + message = "" + icon = "" + flair = "" + rank = 0 + + # First status item is level + case self.member_level.to_i + when 0..9 + if self.payments.count > 0 then + message = "Former Member (#{(DateTime.now - self.payments.maximum(:date)).to_i/30} months ago)" + icon = :timeout + rank = 1 + else + message = "Not a Member" + icon = :no + rank = 0 + end + when 10..24 + message = "Volunteer" + icon = :heart + rank = 101 + when 25..49 + message = member_level_string + icon = :copper + rank = 250 + when 50..99 + message = member_level_string + icon = :silver + rank = 500 + when 100..999 + message = member_level_string + icon = :gold + rank = 1000 + end + + payment_results = payment_status_calculation + flair = payment_results[:flair] + rank = rank/10 unless payment_results[:paid] + message = payment_results[:message] unless payment_results[:message].blank? + + return {:message => message, :icon => icon, :flair => flair, :rank => rank} + end + + def payment_status_calculation + flair = "" + message = "" + paid = true + + # Second status item is payment status + case self.member_level.to_i + when 25..999 + # There are payments + if self.payments.count > 0 then + # They're on time + if self.payments.maximum(:date) > (DateTime.now - 60.days) + flair = "-paid" + paid = true + else + message = "Last Payment #{(DateTime.now - self.payments.maximum(:date)).to_i/30} months ago" + paid = false + end + else + message = "No Payments Recorded" + paid = false + end + end + return {:message => message, :paid => paid, :flair => flair} + end + end diff --git a/app/models/user_certification.rb b/app/models/user_certification.rb index e29b17c..271c038 100644 --- a/app/models/user_certification.rb +++ b/app/models/user_certification.rb @@ -1,8 +1,17 @@ class UserCertification < ActiveRecord::Base attr_accessible :certification_id, :user_id + validates_presence_of :certification_id, :user_id validates_uniqueness_of :certification_id, :scope => :user_id, :message => 'already exists for this user.' # Makes sure users don't get certified twice belongs_to :user belongs_to :certification + + def user_name + if user.blank? + return "n/a (user ##{user_id} missing)" + else + return self.user.name + end + end end diff --git a/app/views/cards/_form.html.erb b/app/views/cards/_form.html.erb index 3a63ed0..6a14351 100644 --- a/app/views/cards/_form.html.erb +++ b/app/views/cards/_form.html.erb @@ -11,9 +11,10 @@ <% end %> + <% @card.user_id = params[:user] if params[:user].present? %>
<%= f.label :user %>
- <%= collection_select(:card, :user_id, User.all.sort_by(&:name), :id, :name) %> + <%= collection_select(:card, :user_id, User.all.sort_by(&:name), :id, :name, :include_blank => true) %>
<%= f.label :name, "Card Note" %>
diff --git a/app/views/cards/index.html.erb b/app/views/cards/index.html.erb index 239c920..8e4fa53 100644 --- a/app/views/cards/index.html.erb +++ b/app/views/cards/index.html.erb @@ -1,13 +1,20 @@

Access Cards

-<%= link_to 'New Card', new_card_path if can? :create, Card %> -<%= link_to 'Upload all cards', upload_all_path if can? :upload_all, Card %> -<%= link_to 'Door Logs', door_logs_path if can? :read, DoorLog %> +<%= link_to 'New Card', new_card_path, :class => "btn" if can? :create, Card %> +<%= link_to 'Upload all cards', upload_all_path, :class => "btn" if can? :upload_all, Card %> +<%= link_to 'Door Logs', door_logs_path, :class => "btn" if can? :read, DoorLog %> +<%= link_to 'Space API', space_api_path, :class => "btn" %> +<%= link_to 'Remote Door Access', space_api_access_path, :class => "btn" if can? :access_doors_remotely, :door_access %>

-Most Active Card Last 7 Days: <%= @most_active_card.name unless @most_active_card.blank? %> (<%= @most_active_card.accesses_this_week unless @most_active_card.blank? %> times) +Most Active Card Last Month: <%= @most_active_card.name unless @most_active_card.blank? %> (<%= @most_active_card.accesses_this_week unless @most_active_card.blank? %> days) +

+

+<% unless @runner_up_card.blank? %> +Runner Up: <%= @runner_up_card.name %> (<%= @runner_up_card.accesses_this_week %> days) +<% end %>

- +
@@ -19,7 +26,7 @@ - + @@ -28,7 +35,14 @@ <% if !@cards.blank? %> <% @cards.each do |card| %> - + diff --git a/app/views/certifications/show.html.erb b/app/views/certifications/show.html.erb index 272deae..c9fce2b 100644 --- a/app/views/certifications/show.html.erb +++ b/app/views/certifications/show.html.erb @@ -8,13 +8,10 @@ <%= simple_format @certification.description %>

-Certified Users: -
    - <% @certification_users.each do |user| %> -
  • <%= link_to user.name, user %>
  • - <% end %> - <% if @certification_users.blank? then %>
  • n/a
  • <% end %> -
+

+ Certified Users: + <%= link_to "Click Here", user_certifications_path %> +

<% if can? :update, @certification %><%= link_to 'Edit', edit_certification_path(@certification) %> |<% end %> <%= link_to 'Back', certifications_path %> diff --git a/app/views/devise/registrations/_user.html.erb b/app/views/devise/registrations/_user.html.erb index 26e83f2..be950b3 100644 --- a/app/views/devise/registrations/_user.html.erb +++ b/app/views/devise/registrations/_user.html.erb @@ -1,12 +1,21 @@ <% if params[:flash] == "welcome_msg" then %>

Thank for you choosing to become a HeatSync Labs member! As we foster this community of learning, science, and the arts every member is important.

-You can get your payments started by following the instructions on this page. Please note electronic recurring payments are -highly- encouraged-- we do not have staff. If you must pay via cash/check, please consider prepaying for 3, 6 or 12 months up front.

-To claim member benefits such as storage, grab a volunteer during your next stop into HeatSync or schedule a time to meet up in advance. Someone should also be contacting you shortly via the email address you provided.

+You can get your payments started by clicking the payment button on this page or contacting a HeatSync member. Please note electronic recurring payments are -highly- encouraged-- we do not have paid staff. If you must pay via cash/check, please consider prepaying for 3, 6 or 12 months up front to reduce volunteer workload.

+To claim member benefits such as storage, grab a volunteer during your next stop into HeatSync or schedule a time to meet up in advance. Someone should also be contacting you shortly via the email address you provided to schedule a New Member Orientation as well. After the orientation, you'll be equipped to be an awesome member of our community, and this website will get a lot more useful.

Please also note that certain privileges like 24/7 card access require community approval.

Thanks again, and happy hacking!

<% end %> <%= form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => html) do |f| %> +
+ <%= f.label :member_level, "Membership Level" %>
+ <%= f.select :member_level, [[nil],["None",0],["Unable",1],["Volunteer",10],["Associate ($25)",25],["Basic ($50)",50],["Plus ($100)",100]] %> +
+ +
+ <%= render :partial => "/users/payment_methods", :locals => { :g => f } %> +
+
<%= f.label :name, "Full Name" %>
<%= f.text_field :name %> @@ -15,12 +24,28 @@ Thanks again, and happy hacking!

<%= f.label :email %>
<%= f.email_field :email %> + <%= f.check_box :email_visible %> + <%= f.label :email_visible, "Show Email to All Members?" %>
<%= f.label :phone %>
<%= f.text_field :phone %> + <%= f.check_box :phone_visible %> + <%= f.label :phone_visible, "Show Phone to All Members?" %>
+ +
+ <%= f.label :twitter_url %> + <%= f.text_field :twitter_url, :placeholder => "https://twitter.com/heatsynclabs" %>
+ <%= f.label :facebook_url %> + <%= f.text_field :facebook_url, :placeholder => "https://www.facebook.com/HeatSyncLabs" %>
+ <%= f.label :github_url %> + <%= f.text_field :github_url, :placeholder => "https://github.com/heatsynclabs" %>
+ <%= f.label :website_url %> + <%= f.text_field :website_url, :placeholder => "http://www.heatsynclabs.org" %> +
+
<%= f.label :emergency_name, "Emergency Contact Name" %>
<%= f.text_field :emergency_name %> @@ -33,13 +58,6 @@ Thanks again, and happy hacking!

<%= f.label :emergency_email %>
<%= f.text_field :emergency_email %>
-
- <%= f.label :member_level, "Membership Level" %>
- <%= f.select :member_level, [[nil],["None",0],["Unable",1],["Volunteer",10],["Associate ($25)",25],["Basic ($50)",50],["Plus ($100)",100]] %> -
-
- <%= render :partial => "/users/payment_methods", :locals => { :g => f } %> -
<%= f.label :current_skills, "What skills, knowledge and experience do you bring to the community?" %>
<%= f.text_area :current_skills %> diff --git a/app/views/devise/registrations/edit.html.erb b/app/views/devise/registrations/edit.html.erb index aca11bf..33de186 100644 --- a/app/views/devise/registrations/edit.html.erb +++ b/app/views/devise/registrations/edit.html.erb @@ -2,12 +2,13 @@ <%= devise_error_messages! %> -
DB ID Card # Access?Accesses Last 7 DaysDays Accessed Last Month
<%= link_to card.user.name, card %> + <% if card.user.nil? %> + n/a + <% else %> + <%= raw(card.user.member_status_symbol) %> + <%= link_to card.user.name , card %> + <% end %> + <%= card.name %> <%= card.id %> <%= card.card_number %>
diff --git a/app/views/home/more_info.html.erb b/app/views/home/more_info.html.erb new file mode 100644 index 0000000..43d28cb --- /dev/null +++ b/app/views/home/more_info.html.erb @@ -0,0 +1,2 @@ +

More Information

+<%= simple_format Setting.more_info_page %> \ No newline at end of file diff --git a/app/views/ipns/index.html.erb b/app/views/ipns/index.html.erb new file mode 100644 index 0000000..1951302 --- /dev/null +++ b/app/views/ipns/index.html.erb @@ -0,0 +1,35 @@ +

PayPal IPN Records

+

+ Automatically loaded from PayPal's servers +

+
-<% if ((can? :read, @recent_user_names) && (@recent_user_names.count > 1)) then %> +<% if ((can? :read, User) && (@recent_user_names.count > 1)) then %>

New People: (say hi!)

    <% @recent_user_names.each do |user| %> -
  • <%= link_to user.name, user %> <%= raw(user.member_status_symbol) %> (Signed up <%= user.created_at.strftime("%b %d") %>)
  • + <% if can? :read, user %> +
  • <%= link_to user.name, user %> <%= raw(user.member_status_symbol) %> (Signed up <%= user.created_at.strftime("%b %d") %>)
  • + <% end %> <% end %>
<% end %> @@ -27,10 +31,14 @@
<%= @num_users %> (<%= @recent_users %> new in the last 7 days)
# of People Certified:
<%= @num_certs %> (<%= @recent_certs %> new in the last 7 days)
+
# of Current Paying Members:
+
<%= @num_paid_users %> (<%= @num_plus_users %> plus, <%= @num_basic_users %> basic, <%= @num_associate_users %> associate. <%= @num_delinquent_users %> not-current)
# of Door Accesses Granted:
<%= @num_door_opens %> (<%= @today_door_opens %> today, <%= @recent_door_opens %> in the last 7 days)
# of Door Accesses Denied:
-
<%= @num_door_denieds %> (<%= @recent_door_denieds %> in the last 7 days)
+
<%= @num_door_denieds %> (<%= @recent_door_denieds %> in the last month)
+
# of Members DB Logins:
+
<%= @num_logins %> (<%= @recent_logins %> users last 7 days)
# of Computers in this DB:
<%= @num_macs %> (<%= @recent_macs %> seen today)
@@ -39,27 +47,10 @@

Member Resources

-
    -
  • <%= link_to "Wiki", "http://wiki.heatsynclabs.org" %>
  • -
  • <%= link_to "Discussion Group", "http://groups.google.com/group/heatsynclabs" %>
  • -
  • <%= link_to "IRC", "irc://irc.freenode.net#heatsynclabs" %>
  • -
  • <%= link_to "Live Webcams", "http://live.heatsynclabs.org/" %>
  • -
  • Lab Phone: (480) 751-1929
  • -
  • - - - Send a Message! - - Type here and your message will show up on the LED sign in the front window!
    - (Please be nice!)
    - (max 9 chars per line)
    -
    - - -
  • -
+<% if can? :access_doors_remotely, :door_access %> +
  • <%= link_to "Remote Door Access", space_api_access_path %>
+<% end %> +<%= simple_format Setting.member_resources_inset %>
+ + + + + + + <% @ipns.sort_by(&:date_parsed).reverse!.each do |ipn| %> + + + + + + + + + + <% end %> +
DateNameItemAmount
<%= ipn.payment_date %><%= ipn.first_name %> <%= ipn.last_name %><%= ipn.item_name %> + <% if ipn.payment_gross.blank? %> + <%= ipn.txn_type %> + <% else %> + <%= ipn.payment_gross %> + <% end %> + + <% if ipn.payment.present? %> + <%= link_to "Linked Payment", ipn.payment %> + <% else %> + <%= link_to "Try to link email '#{ipn.payer_email}' at membership level '#{ipn.payment_gross.to_i}'", link_ipn_path(ipn) %> + <% end %> + <%= link_to "Details", ipn %><%= link_to "Validate", validate_ipn_path(ipn) %>
diff --git a/app/views/ipns/new.html.erb b/app/views/ipns/new.html.erb new file mode 100644 index 0000000..8839fb6 --- /dev/null +++ b/app/views/ipns/new.html.erb @@ -0,0 +1,123 @@ + <%= form_tag('/ipns') do |f| %> + + + +
+ <%= submit_tag %> +
+
+ <%= label_tag :first_name %> + <%= text_field_tag :first_name, "John" %> +
+
+ <%= label_tag :last_name %> + <%= text_field_tag :last_name, "Smith" %> +
+
+ <%= label_tag :payer_email %> + <%= text_field_tag :payer_email, "jsmith@example.com" %> +
+
+ <%= label_tag :item_name %> + <%= text_field_tag :item_name, "Associate Membership" %> +
+
+ <%= label_tag :payment_gross %> + <%= text_field_tag :payment_gross, "25.00" %> +
+
+ <%= label_tag :transaction_subject %> + <%= text_field_tag :transaction_subject, "" %> +
+
+ <%= label_tag :payment_date %> + <%= text_field_tag :payment_date, "20:46:54 Jun 20, 2013 PDT" %> +
+
+ <%= label_tag :txn_type %> + <%= text_field_tag :txn_type, "subscr_payment" %> +
+
+ <%= label_tag :subscr_id %> + <%= text_field_tag :subscr_id, "I-1234567890" %> +
+
+ <%= label_tag :residence_country %> + <%= text_field_tag :residence_country, "US" %> +
+
+ <%= label_tag :mc_currency %> + <%= text_field_tag :mc_currency, "USD" %> +
+
+ <%= label_tag :business %> + <%= text_field_tag :business, "hslfinances@gmail.com" %> +
+
+ <%= label_tag :payment_type %> + <%= text_field_tag :payment_type, "instant" %> +
+
+ <%= label_tag :protection_eligibility %> + <%= text_field_tag :protection_eligibility, "Ineligible" %> +
+
+ <%= label_tag :verify_sign %> + <%= text_field_tag :verify_sign, "12ru9021j9f21j90fj1290fj2910fj0219fj0" %> +
+
+ <%= label_tag :payer_status %> + <%= text_field_tag :payer_status, "verified" %> +
+
+ <%= label_tag :txn_id %> + <%= text_field_tag :txn_id, "1234567890" %> +
+
+ <%= label_tag :receiver_email %> + <%= text_field_tag :receiver_email, "hslfinances@gmail.com" %> +
+
+ <%= label_tag :payer_id %> + <%= text_field_tag :payer_id, "V812314914" %> +
+
+ <%= label_tag :receiver_id %> + <%= text_field_tag :receiver_id, "V90R1280182" %> +
+
+ <%= label_tag :payment_status %> + <%= text_field_tag :payment_status, "Completed" %> +
+
+ <%= label_tag :payment_fee %> + <%= text_field_tag :payment_fee, "0.85" %> +
+
+ <%= label_tag :mc_fee %> + <%= text_field_tag :mc_fee, "0.85" %> +
+
+ <%= label_tag :mc_gross %> + <%= text_field_tag :mc_gross, "25.00" %> +
+
+ <%= label_tag :charset %> + <%= text_field_tag :charset, "windows-1252" %> +
+
+ <%= label_tag :notify_version %> + <%= text_field_tag :notify_version, "3.7" %> +
+
+ <%= label_tag :ipn_track_id %> + <%= text_field_tag :ipn_track_id, "9d3d032d9070" %> +
+
+ <%= submit_tag %> +
+ <% end %> \ No newline at end of file diff --git a/app/views/ipns/show.html.erb b/app/views/ipns/show.html.erb new file mode 100644 index 0000000..146481c --- /dev/null +++ b/app/views/ipns/show.html.erb @@ -0,0 +1,18 @@ +

IPN Details

+ +<% @ipn.attributes.except("data","payment_id").each do |attr| %> +

+ <%= attr.first.to_s %>: + <%= @ipn.attributes[attr.first] %> +

+<% end %> +

+ <% if @ipn.payment.present? %> + <%= link_to "Linked Payment", @ipn.payment %> + <% else %> + Couldn't link automatically. Please create payment manually or adjust the user account and try again to <%= link_to "link email '#{@ipn.payer_email}' at membership level '#{@ipn.payment_gross.to_i}'", link_ipn_path(@ipn) %>. + <% end %> + +

+ +<%= link_to "Back", ipns_path %> diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 5d907ec..81b0361 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -8,17 +8,18 @@ +<%= raw Setting.analytics_code if Setting.present? %> diff --git a/app/views/macs/_form.html.erb b/app/views/macs/_form.html.erb index a45e6e4..8287339 100644 --- a/app/views/macs/_form.html.erb +++ b/app/views/macs/_form.html.erb @@ -1,7 +1,7 @@ <%= form_for(@mac) do |f| %> <% if @mac.errors.any? %>
-

<%= pluralize(@mac.errors.count, "error") %> prohibited this Mac from being saved:

+

<%= pluralize(@mac.errors.count, "error") %> prohibited this MAC from being saved:

    <% @mac.errors.full_messages.each do |msg| %> @@ -17,7 +17,8 @@
<%= f.label :mac %>
- <%= f.text_field :mac %> + <%= f.text_field :mac, :value => (@mac.mac || @arp || nil) %> + <% if @arp.present? %>
Autodetected from your IP of <%= @ip %><% end %>
<%= f.label :note %>
diff --git a/app/views/macs/edit.html.erb b/app/views/macs/edit.html.erb index 2765d61..18fc87f 100644 --- a/app/views/macs/edit.html.erb +++ b/app/views/macs/edit.html.erb @@ -1,4 +1,4 @@ -

Editing Mac

+

Editing MAC

<%= render 'form' %> diff --git a/app/views/macs/history.html.erb b/app/views/macs/history.html.erb new file mode 100644 index 0000000..0d5c531 --- /dev/null +++ b/app/views/macs/history.html.erb @@ -0,0 +1,112 @@ + + + + + + +

Daily Computer Graph

+

Note: these numbers are not absolute. They are calculated and adjusted on-the-fly and thus may vary depending on the query parameters.

+<%= link_to 'Back to Computers', macs_path, :class => 'btn' %> +<%= link_to 'Download JSON', macs_history_path+".json", :class => 'btn' %> +
+ +<%= form_tag(nil, :method => :get) do %> + + + <%= submit_tag("Change Date", :name => nil, :class => 'btn') %> +<% end %> diff --git a/app/views/macs/index.html.erb b/app/views/macs/index.html.erb index f579337..b6866e7 100644 --- a/app/views/macs/index.html.erb +++ b/app/views/macs/index.html.erb @@ -1,5 +1,24 @@ +<% if can? :read_details, Mac %> +<% if @mac_time_start_date.present? %> +Most Active Machine Last <%= distance_of_time_in_words DateTime.now, @mac_time_start_date %>: + "> + <%= @most_active_mac.user.name unless @most_active_mac.user.blank? %> + <%= "("+@most_active_mac.note+")" unless @most_active_mac.note.blank? %> + - <%= (@most_active.last[:time] / 1.hour).round %> hrs + +
+Runner Up: + "> + <%= @runner_up_mac.user.name unless @runner_up_mac.user.blank? %> + <%= "("+@runner_up_mac.note+")" unless @runner_up_mac.note.blank? %> + - <%= (@runner_up.last[:time] / 1.hour).round %> hrs + +<% end %> +<% end %> +

What machines are on our network?

-<%= link_to "New MAC registration", new_mac_path if can? :create, Mac %> +<%= link_to "New MAC registration", new_mac_path, :class => 'btn' if can? :create, Mac %> +<%= link_to 'Activity Graph', macs_history_path, :class => 'btn' if can? :read_details, Mac %>
    <% @@ -15,6 +34,10 @@ Rails.logger.info mac.inspect %> <% end %>
+<% unless can? :read_details, Mac %> +

To see more or register your own, login!

+<% end %> + <% if can? :read_details, Mac %>
<% end %> + <% @user_certification.user_id = params[:user] if params[:user].present? %>
<%= f.label :user_id, "User" %>
- <%= collection_select(:user_certification, :user_id, @users, :id, :name) %> + <%= collection_select(:user_certification, :user_id, @users, :id, :name, :include_blank => true) %>
<%= f.label :certification_id, "Certification" %>
- <%= collection_select(:user_certification, :certification_id, @certifications, :id, :name) %> + <%= collection_select(:user_certification, :certification_id, @certifications, :id, :name, :include_blank => true) %>
<%= f.submit %> diff --git a/app/views/user_certifications/index.html.erb b/app/views/user_certifications/index.html.erb index 391f4fc..262eda4 100644 --- a/app/views/user_certifications/index.html.erb +++ b/app/views/user_certifications/index.html.erb @@ -1,13 +1,13 @@

User Certifications

-<%= link_to 'New User Certification', new_user_certification_path %> +<%= link_to 'New User Certification', new_user_certification_path if can? :create, UserCertification %> <% @grouped_user_certs.sort.each do |cert, user_certifications| %>
<%= cert.name %>
- <% user_certifications.sort{|a,b| a.user.name <=> b.user.name}.each do |user_certification| %> + <% user_certifications.sort{|a,b| a.user_name <=> b.user_name}.each do |user_certification| %>
- <%= link_to user_certification.user.name, user_certification %> + <%= link_to user_certification.user_name, user_certification %>
<% end %>
diff --git a/app/views/user_certifications/show.html.erb b/app/views/user_certifications/show.html.erb index d12c3be..1799b54 100644 --- a/app/views/user_certifications/show.html.erb +++ b/app/views/user_certifications/show.html.erb @@ -1,6 +1,6 @@

User: - <%= @user_certification.user.name %> + <%= link_to @user_certification.user.name, @user_certification.user %>

@@ -14,7 +14,12 @@

- Updated: by <%= link_to @updated_by.name, @updated_by unless @updated_by.blank? %> + Updated: by + <% if @updated_by.blank? %> + #<%= @user_certification.updated_by ||= "nil" %> + <% else %> + <%= link_to @updated_by.name, @updated_by %> + <% end %> at <%= @user_certification.updated_at %>

diff --git a/app/views/user_mailer/email.html.erb b/app/views/user_mailer/email.html.erb new file mode 100644 index 0000000..13d30d0 --- /dev/null +++ b/app/views/user_mailer/email.html.erb @@ -0,0 +1,15 @@ + + + + + + +

From: <%= link_to @from_user.name, user_url(@from_user) %>

+

+ <%= simple_format @body %> +

+

+ To reply, visit <%= link_to @url, @url %> . +

+ + diff --git a/app/views/user_mailer/new_user_email.text.erb b/app/views/user_mailer/new_user_email.text.erb index 3e64142..cf4d2ff 100644 --- a/app/views/user_mailer/new_user_email.text.erb +++ b/app/views/user_mailer/new_user_email.text.erb @@ -4,7 +4,7 @@ Please contact them at <%= @user.email %><%= " or "+@user.phone.to_s unless @use new user orientation, waiver, welcome, payment help, etc. User Details: <%= link_to @url+user_path(@user), @url+user_path(@user) %> -Member Level: <%= simple_format @user.member_level %> +Member Level: <%= simple_format @user.member_level_string %> What skills, knowledge and experience do you bring to the community? <%= simple_format @user.current_skills %> diff --git a/app/views/users/_form.html.erb b/app/views/users/_form.html.erb index 73140e4..963bfb9 100644 --- a/app/views/users/_form.html.erb +++ b/app/views/users/_form.html.erb @@ -23,6 +23,8 @@
<%= f.label :email %>
<%= f.email_field :email %> + <%= f.check_box :email_visible %> + <%= f.label :email_visible, "Show Email to All Members?" %>
<% if @user.id.blank? || !params[:password].nil? %>
@@ -38,6 +40,22 @@ Change Password
<% end %> +
+ <%= f.label :phone %>
+ <%= f.text_field :phone %> + <%= f.check_box :phone_visible %> + <%= f.label :phone_visible, "Show Phone to All Members?" %> +
+
+ <%= f.label :twitter_url %> + <%= f.text_field :twitter_url, :placeholder => "https://twitter.com/heatsynclabs" %>
+ <%= f.label :facebook_url %> + <%= f.text_field :facebook_url, :placeholder => "https://www.facebook.com/HeatSyncLabs" %>
+ <%= f.label :github_url %> + <%= f.text_field :github_url, :placeholder => "https://github.com/heatsynclabs" %>
+ <%= f.label :website_url %> + <%= f.text_field :website_url, :placeholder => "http://www.heatsynclabs.org" %> +
<%= f.label :waiver %>
<%= f.date_select :waiver, :include_blank => 'true', :default => 'nil' %> @@ -69,10 +87,6 @@ <%= f.label :payee %>
<%= f.text_field :payee%>
-
- <%= f.label :phone %>
- <%= f.text_field :phone %> -
<%= f.label :current_skills, "What skills, knowledge and experience do you bring to the community?" %>
<%= f.text_area :current_skills %> @@ -85,6 +99,10 @@ <%= f.label :marketing_source, "How'd you find out about HeatSync?" %>
<%= f.text_area :marketing_source %>
+
+ <%= f.label :exit_reason, "Reason for Leaving" %>
+ <%= f.text_area :exit_reason %> +
<%= f.label :instructor, "Instructor?" %>
<%= f.check_box :instructor %> diff --git a/app/views/users/_user_summary.html.erb b/app/views/users/_user_summary.html.erb new file mode 100644 index 0000000..93e04cd --- /dev/null +++ b/app/views/users/_user_summary.html.erb @@ -0,0 +1,95 @@ +<% user ||= @user #unless @user.blank? %> + +

+ Name: + <%= user.name %> +

+ +<% if current_user.admin? then %> +

+ Email: + <%= user.email %> +

+ +

+ Orientation? + <%= user.orientation.strftime("%B %d %Y") unless user.orientation.blank? %> +

+<% end %> + +

+ Waiver? + <%= user.waiver.strftime("%B %d %Y") unless user.waiver.blank? %> +

+ +

+ Current Member? + <%= raw(user.member_status_symbol) %> +

+ +<% if current_user.admin? then %> +

+ Desired Member Level: + <%= user.member_level %> +

+

+ Payment Method: + <%= user.payment_method %> +

+

+ Payee: + <%= user.payee %> +

+

+ Phone: + <%= user.phone %> +

+<% end %> +

+ Current Skills: + <%= simple_format user.current_skills %> +

+

+ Desired Skills: + <%= simple_format user.desired_skills %> +

+

+ Card: + <% if current_user.admin? then %> + <% user.cards.each do |c| %> + <%= link_to c.card_number, c %><%= "," unless c == user.cards.last %> + <% end %> + <% else %> + <%= unless user.cards.blank? then raw("✓") end %> + <% end %> +

+ +Certifications: +
    + <% user.certifications.each do |certification| %> +
  • <%= link_to certification.name, certification %>
  • + <% end %> + <% if user.certifications.blank? %>
  • n/a
  • <% end %> +
+ +<% if current_user.admin? then %> +

+ Payments: +

    + <% user.payments.each do |payment| %> +
  • <%= payment.date %>
  • + <% end %> +
+

+<% end %> + +<% if current_user.admin? then %> +

+ Created: + <%= user.created_at %> +

+

+ Last signed in: + <%= user.current_sign_in_at %> +

+<% end %> diff --git a/app/views/users/activity.html.erb b/app/views/users/activity.html.erb new file mode 100644 index 0000000..146a3f9 --- /dev/null +++ b/app/views/users/activity.html.erb @@ -0,0 +1,139 @@ +

Member Onboarding/Activity

+New Users | +Members Without Cards | +Logins | +Never-Logged-In Members + + +

New Users

+ + + + + + + + + + + +<% if !@new_users.blank? %> + <% @new_users.sort_by(&:created_at).reverse!.each do |user| %> + + + + + + + + <% end %> +<% end %> +
NameMember LevelAccount CreatedWaiverOrientationPayment
<%= link_to user.name, user %><%= user.member_level_string %><% if user.created_at.present? %> + <%= distance_of_time_in_words(DateTime.now, user.created_at.to_date)+" ago" %> + <% else %> + × + <% end %><% if user.waiver.present? %> + <%= distance_of_time_in_words(DateTime.now, user.waiver.to_date)+" ago" %> + <% else %> + × + <% end %><% if user.orientation.present? %> + <%= distance_of_time_in_words(DateTime.now, user.orientation.to_date)+" ago" %> + <% else %> + × + <% end %><% if user.payments.present? %> + <%= distance_of_time_in_words(Date.today, user.payments.maximum(:date))+" ago" %> + <% else %> + × + <% end %>
+ +

Basic+ Members Without Cards

+ + + + + + + + + + +<% if !@cardless_users.blank? %> + <% @cardless_users.sort_by(&:created_at).reverse!.each do |user| %> + + + + + + + <% end %> +<% end %> +
NameAccount CreatedMember LevelPaymentCard
<%= link_to user.name, user %><%= distance_of_time_in_words DateTime.now, user.created_at %> ago<%= user.member_level_string %><% if user.payments.present? %> + <% last_payment = user.payments.maximum(:date) %> + <% if Date.today-last_payment > 60 %> + + <% else %> + + <% end %> + <%= distance_of_time_in_words(Date.today, user.payments.maximum(:date))+" ago" %> + + <% else %> + × + <% end %><% if user.cards.present? %> + + <% else %> + × + <% end %>
+ + +

Logins

+ + + + + + + + + + +<% if !@user_logins.blank? %> + <% @user_logins.sort_by(&:current_sign_in_at).reverse!.each do |user| %> + + + + + + + <% end %> +<% end %> +
NameCurrent Sign InLast Sign InSign In CountAccount Created
<%= link_to user.name, user %><%= distance_of_time_in_words DateTime.now, user.current_sign_in_at %> ago + <% if user.last_sign_in_at != user.current_sign_in_at %> + <%= distance_of_time_in_words DateTime.now, user.last_sign_in_at %> ago + <% end %> + <%= user.sign_in_count %> times<%= distance_of_time_in_words DateTime.now, user.created_at %> ago
+ + +

Never-Logged-In Members

+Excluding non-members + + + + + + + + +<% if !@zombie_members.blank? %> + <% @zombie_members.sort_by(&:created_at).reverse!.each do |user| %> + + + + + <% end %> +<% end %> +
NameMember LevelAccount Created
<%= link_to user.name, user %><%= user.member_level_string %><%= distance_of_time_in_words DateTime.now, user.created_at %> ago
+ diff --git a/app/views/users/compose_email.html.erb b/app/views/users/compose_email.html.erb new file mode 100644 index 0000000..3beb74e --- /dev/null +++ b/app/views/users/compose_email.html.erb @@ -0,0 +1,14 @@ +

Send Email to <%= @user.name %>

+<%= form_tag do %> +
+ <%= label_tag :subject %>
+ <%= text_field_tag :subject, @subject, :size => 52 %> +
+
+ <%= label_tag :body %>
+ <%= text_area_tag :body, @body, :cols => 40 %> +
+
+ <%= submit_tag "Send", :class => "btn" %> +
+<% end %> \ No newline at end of file diff --git a/app/views/users/inactive.html.erb b/app/views/users/inactive.html.erb new file mode 100644 index 0000000..35c7911 --- /dev/null +++ b/app/views/users/inactive.html.erb @@ -0,0 +1,65 @@ +

Inactive Users

+ + + + + <% if current_user.admin? then %><% end %> + + <% if current_user.admin? %>+ <% end %> + + + + + + + + <% if current_user.admin? then %><% end %> + + <% if current_user.admin? then %> + + <% end %> + + + + + + + + + +<% if !@users.blank? %> + <% @users.each do |user| %> + + + <% if current_user.admin? then %><% end %> + + <% if current_user.admin? then %><% end %> + + + + + + + + + <% end %> +<% end %> +
NameEmailCertificationsOrientation?Card?Pmt MethodDesired LevelLast PaymentJoined
<%= image_tag user.gravatar_url(:default => "http://members.heatsynclabs.org/assets/nil.png"), :class => :avatar %><%= link_to user.name, user %><%= user.email %><% user.certifications.each do |c| %> + <%= link_to c.name, c %><%= "," unless c.id == user.certifications.last.id %> + <% end %> + <%= unless user.orientation.blank? then raw("") end %> + <%= unless user.cards.blank? then raw("") end %><%= user.payment_method %><%= user.member_level %><% delinquency = user.delinquency %> + <% if delinquency == 9999 %> + No Payments + <% else %> + <%= (delinquency/30).to_s+" mo. ago" %> + <% end %> + <%= user.created_at.to_date %><%= link_to 'Edit', edit_user_path(user) if can? :update, user %><%= link_to 'Destroy', user, :confirm => 'Are you sure? WARNING: THIS DOES NOT REMOVE THE USER FROM THE DOOR SYSTEM! DISABLE THEM FIRST.', :method => :delete if can? :destroy, user %>
+ +<% if current_user.orientation.blank? then %> +

There's a lot more to see here, but our records show you haven't completed the new member orientation yet. If that's incorrect, please contact a volunteer.

+<% end %> + +
diff --git a/app/views/users/index.html.erb b/app/views/users/index.html.erb index 6962171..b04529c 100644 --- a/app/views/users/index.html.erb +++ b/app/views/users/index.html.erb @@ -1,32 +1,48 @@

HeatSync People

<% if can? :create, User %> - <%= link_to 'New User', new_user_path %> + <%= link_to 'New User', new_user_path %> | <% end %> +<% if can? :manage, User %> + <%= link_to 'Merge Users', users_merge_path %> | +<% end %> +<% if current_user.admin? %> + <%= link_to 'Inactive Users', users_inactive_path %> | + <%= link_to 'Recent Activity', users_activity_path %> | +<% end %> +<%= link_to 'New Members Report', users_new_member_report_path %> + +<% @certifications = Certification.all.sort_by(&:id) %> <% if current_user.admin? then %><% end %> - + <% if current_user.admin? %><% end %> - + <% col_count = 0 %> + <% @certifications.each do |c| %> + class="col_highlight"<% end %> /> + <% col_count = col_count + 1 %> + <% end %> + <% if current_user.admin? then %><% end %> - <% if current_user.admin? then %> <% end %> - <% if current_user.admin? then %><% end %> + <% @certifications.each do |c| %> + + <% end %> @@ -36,18 +52,24 @@ + <% if current_user.admin? then %><% end %> - <% if current_user.admin? then %><% end %> - <% if current_user.admin? then %><% end %> + <% @certifications.each do |c| %> + + <% end %> diff --git a/app/views/users/merge_view.html.erb b/app/views/users/merge_view.html.erb new file mode 100644 index 0000000..b4e5522 --- /dev/null +++ b/app/views/users/merge_view.html.erb @@ -0,0 +1,48 @@ + + +

Merging users

+ +<%= form_tag('/users/merge', :method => :post) do %> +Everything except the email, password, name, id, hidden, and blank values will be moved to the "user to keep" UNLESS there is a value already there. The "user to merge" will be deleted. +
NameMember?EmailCertificationsOrientation?Waiver?Member? Card? Instructor?Admin?<%= c.name %>
<%= image_tag user.gravatar_url(:default => "http://members.heatsynclabs.org/assets/nil.png"), :class => :avatar %> <%= link_to user.name, user %><%= raw(user.member_status_symbol) %><%= user.email %><% user.certifications.each do |c| %> - <%= link_to c.name, c %><%= "," unless c.id == user.certifications.last.id %> - <% end %> <%= unless user.orientation.blank? then raw("") end %> <%= unless user.waiver.blank? then raw("") end %><%= raw(user.member_status_symbol) %> <%= unless user.cards.blank? then raw("") end %> <%= if user.instructor? then raw("✓") end %><%= if user.admin? then raw("✓") end %> + <% user.user_certifications.each do |u| %> + <% if u.certification_id == c.id %> + <%= link_to raw(""), u %> + <% end %> + <% end %> + <%= link_to 'Edit', edit_user_path(user) if can? :update, user %> <%= link_to 'Destroy', user, :confirm => 'Are you sure? WARNING: THIS DOES NOT REMOVE THE USER FROM THE DOOR SYSTEM! DISABLE THEM FIRST.', :method => :delete if can? :destroy, user %>
+ + + + + +
+
+ <%= label_tag :user_to_keep, "User to Keep" %>
+ <%= collection_select(:user, :to_keep, @users, :id, :name_with_email_and_visibility, :include_blank => true) %> +
+
+
◀◀ +
+ <%= label_tag :user_to_merge, "User to Merge" %>
+ <%= collection_select(:user, :to_merge, @users, :id, :name_with_email_and_visibility, :include_blank => true) %> +
+
+
+ +
+
+ <%= submit_tag "Merge" %> +
+<% end %> + +<%= link_to 'Back', users_path %> + + diff --git a/app/views/users/new_member_report.html.erb b/app/views/users/new_member_report.html.erb new file mode 100644 index 0000000..719ed71 --- /dev/null +++ b/app/views/users/new_member_report.html.erb @@ -0,0 +1,24 @@ +

New Members

+<% if !@new_users.blank? %> + <% @new_users.sort_by(&:created_at).reverse!.group_by{|u| u.created_at.beginning_of_month}.each do |g| %> +

<%= g.first.to_formatted_s(:month_and_year) %>

+ <% g.last.each do |user| %> +

<%= user.name %> - <%= user.member_level_string %>

+

What skills, knowledge and experience do you bring to the community?
+ <%= user.current_skills %> +

+

+ What skills, knowledge and experiences are you looking for in HeatSync? +
+ <%= user.desired_skills %> +

+

+ How'd you find out about HeatSync? +
+ <%= user.marketing_source %> +

+

 

+ <% end %> + <% end %> +<% end %> + diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb index 5d2e308..390e06a 100644 --- a/app/views/users/show.html.erb +++ b/app/views/users/show.html.erb @@ -1,31 +1,38 @@ -<%= image_tag @user.gravatar_url(:default => "http://members.heatsynclabs.org/assets/nil.png") %> - -

- Name: +

+ <%= image_tag @user.gravatar_url(:default => "http://members.heatsynclabs.org/assets/nil.png"), :class => 'avatar-large' %> <%= @user.name %> -

- -

- Waiver? - <%= @user.waiver.strftime("%B %d %Y") unless @user.waiver.blank? %> -

- -

- Current Member? <%= raw(@user.member_status_symbol) %> -

+ <%= link_to image_tag('/twitter.png', :class => 'social-icon', :title => "Twitter"), @user.twitter_url, :class => 'social-link' if @user.twitter_url.present? %> + <%= link_to image_tag('/facebook.png', :class => 'social-icon', :title => "Facebook"), @user.facebook_url, :class => 'social-link' if @user.facebook_url.present? %> + <%= link_to image_tag('/github.png', :class => 'social-icon', :title => "Github"), @user.github_url, :class => 'social-link' if @user.github_url.present? %> + <%= link_to image_tag('/website.png', :class => 'social-icon', :title => "Website"), @user.website_url, :class => 'social-link' if @user.website_url.present? %> +<%= link_to "Email User", user_compose_email_path(@user), :class => 'btn' %> +

-

- Instructor? - <%= @user.instructor? %> -

+ +<% if current_user.admin? || @user.email_visible %> +

+ Email: + <%= @user.email %> +

+<% end %> + +<% if current_user.admin? || @user.phone_visible %> +

+ Phone: + <%= @user.phone %> +

+<% end %> +

+ Current Skills: + <%= simple_format @user.current_skills %> +

+

+ Desired Skills: + <%= simple_format @user.desired_skills %> +

<% if current_user.admin? then %> -

- Email: - <%= @user.email %> -

-

Orientation? <%= @user.orientation.strftime("%B %d %Y") unless @user.orientation.blank? %> @@ -44,7 +51,7 @@

Desired Member Level: - <%= @user.member_level %> + <%= @user.member_level_string %>

Payment Method: @@ -54,54 +61,76 @@ Payee: <%= @user.payee %>

-

- Phone: - <%= @user.phone %> -

<% end %> -

- Current Skills: - <%= simple_format @user.current_skills %> -

-

- Desired Skills: - <%= simple_format @user.desired_skills %> -

+ <% if current_user.admin? then %>

Found HeatSync via: <%= simple_format @user.marketing_source %>

+

+ Left HeatSync because: + <%= simple_format @user.exit_reason %> +

<% end %> -

- Card: - <% if current_user.admin? then %> - <% @user.cards.each do |c| %> - <%= link_to c.card_number, c %><%= "," unless c == @user.cards.last %> - <% end %> - <% else %> - <%= unless @user.cards.blank? then raw("✓") end %> - <% end %> -

-Certifications: +Certifications: <%= link_to "+ Add", (new_user_certification_path+"?user="+@user.id.to_s), :class => 'btn' if can? :create, UserCertification %>
    <% @user.certifications.each do |certification| %>
  • <%= link_to certification.name, certification %>
  • <% end %> <% if @user.certifications.blank? %>
  • n/a
  • <% end %>
- -<% if current_user.admin? then %>

- Payments: -

    - <% @payments.each do |payment| %> -
  • <%= payment.date %>
  • + Card: <%= link_to "+ Add", (new_card_path+"?user="+@user.id.to_s), :class => 'btn' if can? :create, Card %> + <% if current_user.admin? then %> +
      + <% @user.cards.each do |c| %> +
    • <%= link_to c.card_number, c %><%= "," unless c == @user.cards.last %>
    • + <% end %> +
    + <% else %> + <%= unless @user.cards.blank? then raw("✓") end %> <% end %> -

-<% end %> + +Computers: <%= link_to "+ Add", new_mac_path, :class => 'btn' if can? :create, Mac %> +
    + <% @user.macs.each do |mac| %> +
  • <%= link_to mac.mac, mac %>
  • + <% end %> + <% if @user.macs.blank? %>
  • n/a
  • <% end %> +
+ +

+ <% if current_user.admin? then %> + Payments: +

    + <% @payments.each do |payment| %> +
  • <%= link_to payment.date, payment %>
  • + <% end %> +
+ <% else %> + Last Payment: + <% last_payment = @user.delinquency %> + <% if last_payment < 30 %> + Less than a month ago + <% else %> + <%= last_payment/30 %> months ago + <% end %> + <% end %> +

+ +

+ Waiver? + <%= @user.waiver.strftime("%B %d %Y") unless @user.waiver.blank? %> +

+ +

+ Instructor? + <%= @user.instructor? %> +

+ <% if current_user.admin? then %>

diff --git a/config/application.rb b/config/application.rb index 4cae87d..5b119f1 100644 --- a/config/application.rb +++ b/config/application.rb @@ -37,7 +37,7 @@ module Dooraccess config.encoding = "utf-8" # Configure sensitive parameters which will be filtered from the log file. - config.filter_parameters += [:password] + config.filter_parameters += [:password, :pass] # Use SQL instead of Active Record's schema dumper when creating the database. # This is necessary if your schema can't be completely dumped by the schema dumper, diff --git a/config/database.yml b/config/database.yml deleted file mode 100644 index 6c8cb16..0000000 --- a/config/database.yml +++ /dev/null @@ -1,31 +0,0 @@ -# SQLite version 3.x -# gem install sqlite3 -# -# Ensure the SQLite 3 gem is defined in your Gemfile -# gem 'sqlite3' -development: - adapter: sqlite3 - database: db/development.sqlite3 - pool: 5 - timeout: 5000 - -# Warning: The database defined as "test" will be erased and -# re-generated from your development database when you run "rake". -# Do not set this db to the same as development or production. -test: - adapter: sqlite3 - database: db/test.sqlite3 - pool: 5 - timeout: 5000 - -production: - adapter: sqlite3 - database: db/production.sqlite3 - pool: 5 - timeout: 5000 -# adapter: postgresql -# encoding: unicode -# database: members -# pool: 5 -# username: postgres -# password: diff --git a/config/database.yml.example b/config/database.yml.example new file mode 100644 index 0000000..6769cb8 --- /dev/null +++ b/config/database.yml.example @@ -0,0 +1,54 @@ +# SQLite version 3.x +# gem install sqlite3 +# +# Ensure the SQLite 3 gem is defined in your Gemfile +# gem 'sqlite3' + +# development: +# adapter: sqlite3 +# database: db/development.sqlite3 +# pool: 5 +# timeout: 5000 + +development: + adapter: postgresql + encoding: unicode + database: YOUR_DATABASE_development + host: localhost + pool: 5 + username: YOUR_USERNAME_HERE + password: YOUR_PASSWORD_HERE + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. + +# test: +# adapter: sqlite3 +# database: db/test.sqlite3 +# pool: 5 +# timeout: 5000 + +test: + adapter: postgresql + encoding: unicode + database: YOUR_DATABASE_test + host: localhost + pool: 5 + username: YOUR_USERNAME_HERE + password: YOUR_PASSWORD_HERE + +# production: +# adapter: sqlite3 +# database: db/production.sqlite3 +# pool: 5 +# timeout: 5000 + +production: + adapter: postgresql + encoding: unicode + database: YOUR_DATABASE_production + host: localhost + pool: 5 + username: YOUR_USERNAME_HERE + password: YOUR_PASSWORD_HERE \ No newline at end of file diff --git a/config/initializers/dates.rb b/config/initializers/dates.rb new file mode 100644 index 0000000..cdcc4fc --- /dev/null +++ b/config/initializers/dates.rb @@ -0,0 +1,2 @@ +Date::DATE_FORMATS[:month_and_year] = "%B %Y" +Time::DATE_FORMATS[:month_and_year] = "%B %Y" diff --git a/config/initializers/default_settings.rb b/config/initializers/default_settings.rb new file mode 100644 index 0000000..f3a01e7 --- /dev/null +++ b/config/initializers/default_settings.rb @@ -0,0 +1,47 @@ +@@default_settings = { + :welcome_title => "Welcome to the Hackerspace Members Site", + :welcome_body => "

We are a member-driven community workshop where you can learn, make cool stuff, meet other cool people, and make your city a better place to live!

You don't have to be a member to come visit, but if you're interested in volunteering or being a member, feel free to sign up here! For more information, Click Here.

", + :more_info_page => "No info here yet, bug a member about filling this part out!", + :member_resources_inset => "No info here yet, bug a member about filling this part out!", + :analytics_code => "", + :space_api_json_template => '{ + "api" : "0.12", + "space" : "Your Hackerspace Name Here", + "logo" : "http://example.com/logo.png", + "lat": 0, + "lon": -0, + "icon":{ + "open": "http://example.com/open.png", + "closed":"http://example.com/closed.png" + }, + "url" : "http://example.com", + "address" : "123 main st, city, state, country", + "contact" : { + "phone" : "", + "irc" : "", + "twitter" : "", + "ml" : "" + }, + "cam" : [""], + "feeds" : [{"name" : "", + "url" : ""}, + {"name" : "", + "url" : ""}], + "apis" : { + "oac" : { + "url" : "http://this-apps-url.example.com/door_access", + "description" : "https://github.com/heatsynclabs/Open-Source-Access-Control-Web-Interface" + }, + "pamela" : { + "url" : "http://this-apps-url.example.com/macs.json", + "description" : "https://github.com/heatsynclabs/Open-Source-Access-Control-Web-Interface" + } + } + }' +} + +if ActiveRecord::Base.connection.tables.include?('settings') and !defined?(::Rake) + @@default_settings.each do |key, value| + Setting.save_default(key, value) + end +end diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index 367a7da..5082246 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -103,7 +103,7 @@ Devise.setup do |config| # ==> Configuration for :rememberable # The time the user will be remembered without asking for credentials again. - # config.remember_for = 2.weeks + config.remember_for = 1.month # If true, extends the user's remember period when remembered via cookie. # config.extend_remember_period = false diff --git a/config/initializers/secret_token.rb b/config/initializers/secret_token.rb.example similarity index 64% rename from config/initializers/secret_token.rb rename to config/initializers/secret_token.rb.example index a73d68b..eae3f95 100644 --- a/config/initializers/secret_token.rb +++ b/config/initializers/secret_token.rb.example @@ -4,4 +4,4 @@ # If you change this key, all old signed cookies will become invalid! # Make sure the secret is at least 30 characters and all random, # no regular words or you'll be exposed to dictionary attacks. -Dooraccess::Application.config.secret_token = 'd258ec483070c67d9e7ba7bb8b6b506a30fb38d0d95ac561ef603785f4639a688747a4adf872f627acae9b57f44f822a0c4dc2f2fd3d8e6135ad0b491f72751f' +Dooraccess::Application.config.secret_token = YOUR_RANDOM_STRING_HERE diff --git a/config/routes.rb b/config/routes.rb index 606152a..7d1ca5b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,7 +1,18 @@ Dooraccess::Application.routes.draw do + match 'ipns/import' => 'ipns#import', :as => :import_ipn + resources :ipns + match 'ipns/:id/link' => 'ipns#link', :as => :link_ipn + match 'ipns/:id/validate' => 'ipns#validate', :as => :validate_ipn + + resources :paypal_csvs + match 'paypal_csvs/:id/link' => 'paypal_csvs#link', :as => :link_paypal_csv resources :payments + match 'statistics' => 'statistics#index', :as => :statistics + match 'statistics/mac_log' => 'statistics#mac_log', :as => :mac_statistics + match 'statistics/door_log' => 'statistics#door_log', :as => :door_statistics + resources :user_certifications resources :certifications @@ -18,23 +29,39 @@ Dooraccess::Application.routes.draw do end end - resources :users + match 'user_summary/:id' => 'users#user_summary' # User summary view + match 'users/activity' => 'users#activity' # User activity + match 'users/new_member_report' => 'users#new_member_report' # New member report (For emailing) + match 'users/merge' => 'users#merge_view', :via => :get # Merge view + match 'users/merge' => 'users#merge_action', :via => :post # Merge action + match 'users/inactive' => 'users#inactive' # Inactive users report + resources :users do + get 'email' => 'users#compose_email', :as => "compose_email" + post 'email' => 'users#send_email' + end match 'users/create' => 'users#create', :via => :post # Use POST users/create instead of POST users to avoid devise conflict match 'cards/upload_all' => 'cards#upload_all', :as => :upload_all resources :cards match 'cards/:id/upload' => 'cards#upload', :as => :upload + match 'space_api' => 'space_api#index', :as => :space_api + match 'space_api/access' => 'space_api#access', :via => :get, :as => :space_api_access + match 'space_api/access' => 'space_api#access_post', :via => :post + match 'door_logs' => 'door_logs#index', :as => :door_logs match 'door_logs/download' => 'door_logs#download', :as => :download match 'door_logs/auto_download' => 'door_logs#auto_download', :as => :auto_download match 'macs/scan' => 'macs#scan' match 'macs/import' => 'macs#import' + match 'macs/history' => 'macs#history' resources :macs - resources :mac_logs + resources :settings, :only => [:index, :edit, :update] + + match 'more_info' => 'home#more_info' root :to => "home#index" # The priority is based upon order of creation: diff --git a/db/migrate/20130824062334_create_ipns.rb b/db/migrate/20130824062334_create_ipns.rb new file mode 100644 index 0000000..fad43b4 --- /dev/null +++ b/db/migrate/20130824062334_create_ipns.rb @@ -0,0 +1,24 @@ +class CreateIpns < ActiveRecord::Migration + def change + create_table :ipns do |t| + t.integer :payment_id + t.text :data + t.string :txn_id + t.string :txn_type + t.string :first_name + t.string :last_name + t.string :payer_business_name + t.string :payer_email + t.string :payer_id + t.string :auth_amount + t.string :payment_date + t.string :payment_fee + t.string :payment_gross + t.string :payment_status + t.string :item_name + t.string :payment_type + + t.timestamps + end + end +end diff --git a/db/migrate/20130824072157_add_amount_to_payments.rb b/db/migrate/20130824072157_add_amount_to_payments.rb new file mode 100644 index 0000000..37b65e6 --- /dev/null +++ b/db/migrate/20130824072157_add_amount_to_payments.rb @@ -0,0 +1,5 @@ +class AddAmountToPayments < ActiveRecord::Migration + def change + add_column :payments, :amount, :decimal + end +end diff --git a/db/migrate/20130828104240_create_paypal_csvs.rb b/db/migrate/20130828104240_create_paypal_csvs.rb new file mode 100644 index 0000000..f0d682e --- /dev/null +++ b/db/migrate/20130828104240_create_paypal_csvs.rb @@ -0,0 +1,28 @@ +class CreatePaypalCsvs < ActiveRecord::Migration + def change + create_table :paypal_csvs do |t| + t.integer :payment_id + t.text :data + t.string :date + t.string :_time + t.string :_time_zone + t.string :_name + t.string :_type + t.string :_status + t.string :_currency + t.string :_gross + t.string :_fee + t.string :_net + t.string :_from_email_address + t.string :_to_email_address + t.string :_transaction_id + t.string :_counterparty_status + t.string :_address_status + t.string :_item_title + t.string :_item_id + t.string :string + + t.timestamps + end + end +end diff --git a/db/migrate/20130829070500_add_exit_reason_to_users.rb b/db/migrate/20130829070500_add_exit_reason_to_users.rb new file mode 100644 index 0000000..46e238d --- /dev/null +++ b/db/migrate/20130829070500_add_exit_reason_to_users.rb @@ -0,0 +1,5 @@ +class AddExitReasonToUsers < ActiveRecord::Migration + def change + add_column :users, :exit_reason, :string + end +end diff --git a/db/migrate/20130829074549_add_social_media_to_users.rb b/db/migrate/20130829074549_add_social_media_to_users.rb new file mode 100644 index 0000000..8502219 --- /dev/null +++ b/db/migrate/20130829074549_add_social_media_to_users.rb @@ -0,0 +1,10 @@ +class AddSocialMediaToUsers < ActiveRecord::Migration + def change + add_column :users, :twitter_url, :string + add_column :users, :facebook_url, :string + add_column :users, :github_url, :string + add_column :users, :website_url, :string + add_column :users, :email_visible, :boolean + add_column :users, :phone_visible, :boolean + end +end diff --git a/db/migrate/20130922060217_change_surveys_to_text.rb b/db/migrate/20130922060217_change_surveys_to_text.rb new file mode 100644 index 0000000..00670b5 --- /dev/null +++ b/db/migrate/20130922060217_change_surveys_to_text.rb @@ -0,0 +1,13 @@ +class ChangeSurveysToText < ActiveRecord::Migration + def up + change_column :users, :current_skills, :text, :limit => nil + change_column :users, :desired_skills, :text, :limit => nil + change_column :users, :marketing_source, :text, :limit => nil + end + + def down + change_column :users, :current_skills, :string + change_column :users, :desired_skills, :string + change_column :users, :marketing_source, :string + end +end diff --git a/db/migrate/20130922064241_change_member_level_to_integer.rb b/db/migrate/20130922064241_change_member_level_to_integer.rb new file mode 100644 index 0000000..3e61e12 --- /dev/null +++ b/db/migrate/20130922064241_change_member_level_to_integer.rb @@ -0,0 +1,9 @@ +class ChangeMemberLevelToInteger < ActiveRecord::Migration + def up + change_column :users, :member_level, :integer + end + + def down + change_column :users, :member_level, :string + end +end diff --git a/db/migrate/20130925015417_change_user_exit_reason_to_text.rb b/db/migrate/20130925015417_change_user_exit_reason_to_text.rb new file mode 100644 index 0000000..4e7389a --- /dev/null +++ b/db/migrate/20130925015417_change_user_exit_reason_to_text.rb @@ -0,0 +1,9 @@ +class ChangeUserExitReasonToText < ActiveRecord::Migration + def up + change_column :users, :exit_reason, :text, :limit => nil + end + + def down + change_column :users, :exit_reason, :string + end +end diff --git a/db/migrate/20130928112252_create_settings.rb b/db/migrate/20130928112252_create_settings.rb new file mode 100644 index 0000000..033d5d8 --- /dev/null +++ b/db/migrate/20130928112252_create_settings.rb @@ -0,0 +1,17 @@ +class CreateSettings < ActiveRecord::Migration + def self.up + create_table :settings do |t| + t.string :var, :null => false + t.text :value, :null => true + t.integer :thing_id, :null => true + t.string :thing_type, :limit => 30, :null => true + t.timestamps + end + + add_index :settings, [ :thing_type, :thing_id, :var ], :unique => true + end + + def self.down + drop_table :settings + end +end diff --git a/db/schema.rb b/db/schema.rb index 19fced1..9d0fb6d 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -1,3 +1,4 @@ +# encoding: UTF-8 # This file is auto-generated from the current state of the database. Instead # of editing this file, please use the migrations feature of Active Record to # incrementally modify your database, and then regenerate this schema definition. @@ -10,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20130212083412) do +ActiveRecord::Schema.define(:version => 20130928112252) do create_table "cards", :force => true do |t| t.string "card_number" @@ -35,6 +36,27 @@ ActiveRecord::Schema.define(:version => 20130212083412) do t.datetime "updated_at", :null => false end + create_table "ipns", :force => true do |t| + t.integer "payment_id" + t.text "data" + t.string "txn_id" + t.string "txn_type" + t.string "first_name" + t.string "last_name" + t.string "payer_business_name" + t.string "payer_email" + t.string "payer_id" + t.string "auth_amount" + t.string "payment_date" + t.string "payment_fee" + t.string "payment_gross" + t.string "payment_status" + t.string "item_name" + t.string "payment_type" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + create_table "mac_logs", :force => true do |t| t.string "mac" t.string "ip" @@ -64,10 +86,47 @@ ActiveRecord::Schema.define(:version => 20130212083412) do t.datetime "created_at", :null => false t.datetime "updated_at", :null => false t.integer "created_by" + t.decimal "amount" end add_index "payments", ["user_id"], :name => "index_payments_on_user_id" + create_table "paypal_csvs", :force => true do |t| + t.integer "payment_id" + t.text "data" + t.string "date" + t.string "_time" + t.string "_time_zone" + t.string "_name" + t.string "_type" + t.string "_status" + t.string "_currency" + t.string "_gross" + t.string "_fee" + t.string "_net" + t.string "_from_email_address" + t.string "_to_email_address" + t.string "_transaction_id" + t.string "_counterparty_status" + t.string "_address_status" + t.string "_item_title" + t.string "_item_id" + t.string "string" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + + create_table "settings", :force => true do |t| + t.string "var", :null => false + t.text "value" + t.integer "thing_id" + t.string "thing_type", :limit => 30 + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + + add_index "settings", ["thing_type", "thing_id", "var"], :name => "index_settings_on_thing_type_and_thing_id_and_var", :unique => true + create_table "user_certifications", :force => true do |t| t.integer "user_id" t.integer "certification_id" @@ -79,35 +138,42 @@ ActiveRecord::Schema.define(:version => 20130212083412) do create_table "users", :force => true do |t| t.string "name" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - t.string "email", :default => "", :null => false - t.string "encrypted_password", :default => "", :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + t.string "email", :default => "", :null => false + t.string "encrypted_password", :default => "", :null => false t.string "reset_password_token" t.datetime "reset_password_sent_at" t.datetime "remember_created_at" - t.integer "sign_in_count", :default => 0 + t.integer "sign_in_count", :default => 0 t.datetime "current_sign_in_at" t.datetime "last_sign_in_at" t.string "current_sign_in_ip" t.string "last_sign_in_ip" t.boolean "admin" - t.integer "member", :limit => 255 + t.integer "member" t.datetime "waiver" t.datetime "orientation" t.string "emergency_name" t.string "emergency_phone" t.string "emergency_email" - t.string "member_level" + t.integer "member_level" t.string "payment_method" t.string "phone" - t.string "current_skills" - t.string "desired_skills" + t.text "current_skills" + t.text "desired_skills" t.boolean "instructor" t.boolean "hidden" - t.string "marketing_source" + t.text "marketing_source" t.string "payee" t.boolean "accountant" + t.text "exit_reason" + t.string "twitter_url" + t.string "facebook_url" + t.string "github_url" + t.string "website_url" + t.boolean "email_visible" + t.boolean "phone_visible" end add_index "users", ["email"], :name => "index_users_on_email", :unique => true diff --git a/public/copper-coin.png b/public/copper-coin.png new file mode 100644 index 0000000..809bbbb Binary files /dev/null and b/public/copper-coin.png differ diff --git a/public/copper-paid-coin.png b/public/copper-paid-coin.png new file mode 100644 index 0000000..6d40b7b Binary files /dev/null and b/public/copper-paid-coin.png differ diff --git a/public/facebook.png b/public/facebook.png new file mode 100644 index 0000000..0da85bc Binary files /dev/null and b/public/facebook.png differ diff --git a/public/favicon.ico b/public/favicon.ico index e69de29..f3f28f6 100644 Binary files a/public/favicon.ico and b/public/favicon.ico differ diff --git a/public/github.png b/public/github.png new file mode 100644 index 0000000..707b4fd Binary files /dev/null and b/public/github.png differ diff --git a/public/gold-coin.png b/public/gold-coin.png new file mode 100644 index 0000000..e3f3836 Binary files /dev/null and b/public/gold-coin.png differ diff --git a/public/gold-paid-coin.png b/public/gold-paid-coin.png new file mode 100644 index 0000000..ffa678a Binary files /dev/null and b/public/gold-paid-coin.png differ diff --git a/public/heart-coin.png b/public/heart-coin.png new file mode 100644 index 0000000..84bfcf6 Binary files /dev/null and b/public/heart-coin.png differ diff --git a/public/no-coin.png b/public/no-coin.png new file mode 100644 index 0000000..7318232 Binary files /dev/null and b/public/no-coin.png differ diff --git a/public/silver-coin.png b/public/silver-coin.png new file mode 100644 index 0000000..0d525ea Binary files /dev/null and b/public/silver-coin.png differ diff --git a/public/silver-paid-coin.png b/public/silver-paid-coin.png new file mode 100644 index 0000000..4b30951 Binary files /dev/null and b/public/silver-paid-coin.png differ diff --git a/public/timeout-coin.png b/public/timeout-coin.png new file mode 100644 index 0000000..fbdcea4 Binary files /dev/null and b/public/timeout-coin.png differ diff --git a/public/twitter.png b/public/twitter.png new file mode 100644 index 0000000..9f73551 Binary files /dev/null and b/public/twitter.png differ diff --git a/public/website.png b/public/website.png new file mode 100644 index 0000000..f5f953e Binary files /dev/null and b/public/website.png differ diff --git a/public/wymeditor/AUTHORS b/public/wymeditor/AUTHORS new file mode 100644 index 0000000..acbc313 --- /dev/null +++ b/public/wymeditor/AUTHORS @@ -0,0 +1,20 @@ +Jean-Francois Hovinne (jf.hovinne a-t wymeditor dotorg) +Volker Mische (vmx a-t gmx dotde) +Scott Lewis (lewiscot a-t gmail dotcom) +Bermi Ferrer (wymeditor a-t bermi dotorg) +Daniel Reszka (d.reszka a-t wymeditor dotorg) +Jonatan Lundin (jonatan.lundin a-t gmail dotcom) +Samuel Cole (sam@samuelcole.name) +Wes Winham (winhamwr a-t gmail dotcom) +Gyuris Gellért (bubu a-t ujevangelizacio dothu) +Steven Bufton (www.busoco.ch) +Miroslav Bendik (mirosla.bendik a-t gmail dotcom) +Jakub Krčma (jakub.krcma a-t iinfo dotcz) +Jorge Salinas (jorgesalinasd a-t gmail dotcom) +Sebastian Kolind (sks1993 a-t gmail dotcom) +Craig MacGregor (craigerm a-t gmail dotcom) +Philipp Cordes (pc a-t irgendware dotnet) +Michael Farrell (http://micolous.id.au/) +Josef Šimánek (retro a-t ballgag dotcz) +Daniele Procida (https://github.com/evildmp) +Nick McLaughlin (https://github.com/FriedRice) diff --git a/public/wymeditor/CHANGELOG.md b/public/wymeditor/CHANGELOG.md new file mode 100644 index 0000000..c4816fe --- /dev/null +++ b/public/wymeditor/CHANGELOG.md @@ -0,0 +1,359 @@ +# Change History + +This document tracks the enhancements and bug fixes between releases of +WYMeditor. + +## 1.0.0b5 (Beta 5) + +*release-date* July 31, 2013 + +### Enhancements + +* We now have all of the documentation in one place at + [wymeditor.readthedocs.org](https://wymeditor.readthedocs.org)! No more + switching back and forth between the old Trac site and the github Wiki. The + new documentation uses [Sphinx](http://sphinx-doc.org/) and new pull + requests will now be able to come with `docs/` already committed! Thanks to + Nick McLaughlin for the herculean effort in combining the years of spread-out + documentation. +* We now document how to run WYMeditor with jQuery 1.8.x and 1.9.x via + [jquery-migrate](https://github.com/jquery/jquery-migrate/). The unit tests + now also support those versions of jQuery. +* Some community members have created a + [Django-CMS 3](https://github.com/wymeditor/djangocms_wymeditor_plugin) + plugin for WYMeditor. If you're upgrading from Django-CMS 2 to 3, you'll want + to check it out. +* Tables can now be inserted and used in lists. Previously, the editor would + not let the user insert a table anywhere in a list, but now, the user can + insert a table at any point in a list or sublist and can even insert multiple + tables in the same list item. In addition, the tables should properly indent, + outdent, and space themselves within a list. +* Elements in the editor can now easily be flagged to be removed from the + editor output by the XHTML parser. This allows elements to be "editor-only" + in the sense that they will be visible in the editor but not included in the + outputted XHTML from the editor. Simply add the class + `WYMeditor.EDITOR_ONLY_CLASS` to an element to specify it to be removed by + the XHTML parser in this manner. +* Classes can now be specified to be removed from tags' `class` attribute by + the XHTML parser. Simply add a string for each of the class names to remove + to the `WYMeditor.CLASSES_REMOVED_BY_PARSER` array, and the XHTML parser will + remove all of those classes from all the tags in the editor's output. +* `div` elements are now visible and labeled in the editor so that they can + easily be worked with. In addition, `div` containers can now be switched to + other container types using the containers panel. This change was made to + allow for the addition of the selectable default root container feature + described in the following point. +* The default root container used by the editor can now be specified as + an option to be either `p` or `div`. The specified container for this option + will be used by default when an unspecified new container is created in the + root of the editor, and it will also be used as the default container for + wrapping any text or inline elements inserted directly into the editor body. + In addition, the editor will enforce that the unchosen option for the + default root container is not allowed in the root of the document by + converting the unchosen default root container to the chosen default root + container when possible (e.g. if `div` is specified as the default root + container, the editor will convert `p` elements in the root of the document + to `div` elements when the user modifies those containers). The default root + container option can be specified as an option of the new `structureRules` + option in the editor's initialization. For example: + + ```javascript + jQuery('.wymeditor').wymeditor({ + structureRules: { + defaultRootContainer: 'div' + } + }); + ``` + +### Bug Fixes + +* WYMeditor now works properly in Chrome when using jQuery 1.4 or higher. + Thanks to several folks for outlining the fix and for Nick McLaughlin for the + pull request. +* When using the bold tool in a heading in Chrome, there was an issue where a + span with a style attribute set to `font-weight: normal` would be wrapped + around the header's content. This has been fixed so that this span will no + longer occur after bolding. +* When attempting to insert an image outside of a container in Chrome such as + in the case of inserting an image into a blank document, there was an issue + where the image was being moved outside of the wymeditor body causing none of + the user-entered values for the attributes of the image to be applied to the + image. This has been fixed so that inserting an image properly places it + within the wymeditor body and properly applies the user-entered values for + its attributes. +* When switching between a normal table cell and a table header using the + "Table Header" option in the containers panel in the editor, any `colspan` or + other attributes were being lost in the container conversion. This has been + fixed so that all attributes such as `colspan` are retained when switching + between a normal table cell and a table header. +* In various situations when working in the editor, hitting enter to + create a new container in the root of the document would erroneously + create a `div` container instead of a `p` container. This `div` + container would not be labeled in the editor, and it could not be + converted to another type of container. As part of the feature + addition of a selectable default root container, this issue has been + fixed so that the selected default root container is always inserted + when hitting enter to create a new container in the root of the + document. In addition, `div` containers are now clearly labeled in the + editor and can now be converted to other types of containers using the + containers panel. +* A couple bugs in IE dealing with content not allowed to be in the root of the + editor body not being properly wrapped in containers have been fixed. The + first bug fixed was that text directly typed into the body of the editor was + not being wrapped in a container, but this is now fixed and the text will be + properly wrapped in the default root container. The second bug fixed was that + inline elements not allowable to be directly inserted into the body of the + editor (e.g. `strong`, `em`, `a`, etc.) were not being wrapped in a + container, but this is also now fixed and those elements will be properly + wrapped in the default root container when modified in the body. + +### Build Process Improvements + +* [Grunt](http://gruntjs.com/) has been set up for the WYMeditor project so + that the unit test suite can now easily be run from the command line in a + headless [Phantom.js](http://phantomjs.org/) browser using the Grunt `test` + task. See the testing section in the README or the docs for more information. +* The WYMeditor project is now set up on [Travis CI](https://travis-ci.org/) + for continuous integration using the unit test suite run with a variety of + jQuery versions using the Grunt `test` task. +* [Selenium2](http://seleniumhq.org/) tests have been set up for the WYMeditor + project to allow for testing coverage of some behaviors that can't be + replicated well with JavaScript. See the README for information on running + the Selenium tests. + +### Deprecation + +* The `WYMeditor.editor.html()` function has been deprecated and replaced with + the `WYMeditor.editor._html()` function that has the same functionality. This + change was made to discourage users from using the `html()` function (which + is intended only for internal use) as a means to get the HTML output from the + editor because that function bypasses all parsing and cross-browser cleanup + for the HTML. In most cases, users should be using the + `WYMeditor.editor.xhtml()` function to get the HTML output from the editor + because it does parse and apply cross-browser cleanup to the HTML. Now, using + the `html()` function will still work but just give a console warning of its + deprecation, but the function will be fully removed in the release of version + 1.0.0. + +## 1.0.0b4 (Beta 4) + +*release-date* February 15, 2013 + +### Enhancements + +* Added a Danish translation. Thanks Sebastian Kolind. +* The Makefile now supports building a WYMeditor distribution based on Google's + Closure Compiler instead of UglifyJS. Thanks Michael Farrell. + +### Bug Fixes + +* The editor area is now properly displayed when using the Compact skin and + styling with `white-space: nowrap'`. Thanks to Jorge Salinas for the fix. +* Fixed several bugs related to parsing void elements (br, hr, etc) which could + be either self-closing or not. These usually manifested when used near other + inline elements (eg. span). Thanks to Craig MacGregor for the fix. +* Fixed several XHTML-strict non-compliance problems. WYMeditor aims to always + output fully-compliant XHTML. Thanks Philipp Cordes for the report and fix. +* The Makefile now fails more gracefully when you don't have UglifyJS installed + and you attempt to build a distribution. Thanks to Michael Farrell for the fix. +* Fixed the Slovak translation language code. Thanks to Josef Šimánek for the + fix. +* In 1.0.0b2, we added a bug fix to allow DIV tags to peacefully co-exist with + P tags in document structure. It turned out that this fix was a bit + half-baked and actually broke both P and DIV tags in subtle ways, especially + in webkit browsers. We've reverted that change, so DIVs are now just as + broken as they were previously, but P tags are back to being peachy. The full + fix for mixing DIV and P tags for structure is being worked on as part of + [Issue 360](https://github.com/wymeditor/wymeditor/pull/360) and will + hopefully land in 1.0.0b5, which is now definitely going to be a thing. + +### 1.0.0 Blocking Issues + +In an attempt to ovary-up (or whatever your preferred genatalia) and actually +get a 1.0.0 out the door, we've moved the goalposts a bit. The applicable 1.0.x +issues have been narrowed down a bit to the major bugs in supported browsers. I +understand that `major` is in the eye of the beholder, but this is an attempt +to more-effectively allocated the project's limited resources. The remaining +issues are listed on the [1.0.0 +milestone](https://github.com/wymeditor/wymeditor/issues?milestone=5) and +you'll notice there are no features that made the cut. It's all bug-fix mode. + +Folks have been using WYMeditor in production longer than Chrome has existed as +a browser. Perhaps it's time to not pretend like a non-1.0 version number means +anything. + +## 1.0.0b3 (Beta 3) + +*release-date* June 26, 2012 + +This is a small hotfix release to fix `jQuery.noConflict()` compatibility, +which is necessary for Drupal integration. + +## 1.0.0b2 (Beta 2) + +*release-date* June 22, 2012 + +### Enhancements + +* Added a Slovak translation. Thanks Miroslav Bendik. + +### Bug Fixes + +* A missing space in the tools HTML was triggering quirks mode in FF and other + browsers. Thanks to corphi for the fix. +* Inserting `div` tags via the API or DOM no longer breaks document structure + in chrome and safari. Thanks to Jakub Krčma for the fix. +* It is once again possible to use the Drupal 7 wysiwyg module to integrate + WYMeditor. Thanks to Jean-Francois Hovinne for the patch. +* Newline characters are no longer incorrectly removed in IE, which could cause + words in copy/pasted lists to join together. Thanks to Jakub Krčma for the + fix. +* Tables and images are once again properly inserted at your cursor location + in Internet Explorer. This was a regression bug in 1.0.0b1. + +## 1.0.0b1 (Beta 1) + +*release-date* February 27, 2012 + +We're almost there! Following up on the later October alpha release, we're +happy to announce the availability of a beta-quality WYMeditor release. This +release is not without bugs, but we think it is strictly better than 0.5.0rc2 +with a variety of enhancements and bug fixes. Users currently on an earlier +alpha or on 0.5.0rc2 are encouraged to try out this release and report any +bugs, especially those that are new since 0.5.0rc2. + +Bugs that are determined to be regressions from 0.5.0rc2 will receive the +highest priority fixes. + +Any feedback or discussion would be appreciated on the +[WYMeditor Forums](http://community.wymeditor.org/). + +Versus 0.5.0rc2 we have: + +* *19* major bug fixes +* *8* major enhancements including a new theme. +* A huge internal code refactor to make maintaining and improving WYMeditor easier +* A passing unit test suite containing more than 600 tests across all supported browsers + +### Upgrade Cycle + +Once all +[milestone 1.0.0 issues](https://github.com/wymeditor/wymeditor/issues?milestone=10&sort=created&direction=desc&state=open) +are completed, this cycle will culminate in a 1.0.0 stable release. + +### Enhancements + +* The parser now works harder to correct any invalid list nesting that might + occur due to browser-specific problems or HTML that was loaded to begin. On + every list action (indent, outdent, order/unordered conversion), the parser + crawls your list to make any necessary corrections. This ensures a much more + consistent list-editing experience, especially in Internet Explorer. +* A new *pretty* theme option is now available for modern browsers (ie9+, FF, + Chrome, Safari). This theme uses CSS instead of images to provide context + clues for blocks, resulting in fewer HTTP requests and better network + performance. Additionally, the context clues have been expanded to provide a + better "plain english" explanation of the elements. Give this new theme a + swing at [the example](http://wymeditor.no.de/wymeditor/examples/17-pretty-theme.html). + + You can enable it in your project by passing the + `iframeBasePath: "wymeditor/iframe/pretty/"` option to your WYMeditor instance. + + Thanks to first-time contributor Gyuris Gellért for the theme. +* The Embed plugin now supports embedding via an iframe. +* List indent/outdent has been rewritten to fix several outstanding bugs in + various browsers. Indent and outdent are now always opposites of each other + (outdenting what you just indented returns you to your original state) and + the behavior is consistent across all supported browsers. +* A list plugin is now available that enables tab for list indent and + shift + tab for list outdent. + + It is available at `wymeditor/plugins/list/jquery.wymeditor.list.js`. + To enable the plugin, create a ListPlugin object via the + `wymeditor.postInit` option. eg:: + + $('.wymeditor').wymeditor({ + postInit: function(wym) { + var listPlugin = new ListPlugin({}, wym); + } + }); + +* A new Table editing plugin is now available + + The table editing plugin enables the following: + + * Users can now add and remove rows and columns from existing tables. + * Users can merge table cells to create either `colspan` or `rowspan`. + * Hitting the `tab` key while inside a table now moves the cursor to the + next cell, improving usability when editing tables. This can be disabled + by passing `enableCellTabbing: false` to the plugin initialization. + + The plugin is available at `wymeditor/plugins/table/jquery.wymeditor.table.js`. + To enable the plugin, instantiate it during the `wymeditor.postInit` option. + eg:: + + $('.wymeditor').wymeditor({ + postInit: function(wym) { + var tableEditor = wym.table(); + } + }); + +* Rangy is now included as part of the distribution and used to create + consistent cross-browser selection objects. + +* A console warning message is now created if no wymPath option is provided and + it can't be automatically determined. The editor also attempts to continue + with the assumption that your wymPath is your current directory, instead of + throwing an exception immediately. + + +### Bug Fixes + +* A rare bug affecting ie8 users with certain combinations of CSS attributes + has been fixed (with a work-around). This bug would manifest as all content + in the editor temporarily and randomly disappearing after a keypress, only + to re-appear when the user moved their mouse. +* The editor height no longer changes height by a few pixels the first time + someone hovers over a tool. +* Several list indent/outdent bugs that could result in invalid HTML and broken + lists are now fixed. Users can no-longer break their lists with specific + combinations of double indents and outdents. +* The HTML parser/validator now corrects unclosed
  • tags in lists so that if + a piece of HTML has previously been affected by the broken list bug, it will + be automatically corrected. +* It is now always possible to insert tables, preformatted text and blockquotes + at the start and end of documents, as well as in between each other. + Previously, depending on your browser and version, you couldn't do one or more + of these things. +* It is now possible to paste content in to a table when using internet + explorer. +* Fixed some problems with ordered and unordered list nesting in Internet + Explorer caused by a regex failing to account for IE's insertion of + whitespace in list HTML. +* `colSpan` and `rowSpan` attributes are no longer stripped out in Internet + Explorer. +* Fixed a bug making it impossible to use *Paste From Word* inside tables or lists +* Fixed a list indent bug when indenting a list with a previous list item which + had a sublist of a different list type. This used to create a second sublist + of the original list type, which isn't what someone would expect. +* Fixed several related list outdent bugs where content could be re-ordered or + where outdent would fail to occur. +* It's now possible to consistently toggle lists between ordered and unordered + in all supported browsers. +* Turning a top-level item into a list no longer wraps the list in a paragraph + in chrome 16 and higher. +* Fixed indent/outdent when highlighting an inline node instead of the list + (eg. a bolded section). +* Updated the turkish translation (thanks Gokce). +* Attempting to indent with a cursor outside of a list no longer throws a + javascript error. +* Several bugs related to losing/moving your selection when indenting or + outdenting lists are now fixed. +* Outdenting after using backspace to join an item inside a list no longer + loses content in internet explorer. +* `` tags are now automatically self-closing and the parser no longer + forces a closing `` tag. + + Thanks to first-time contributor Steven Bufton for the fix. + + + diff --git a/public/wymeditor/GPL-license.txt b/public/wymeditor/GPL-license.txt new file mode 100644 index 0000000..11dddd0 --- /dev/null +++ b/public/wymeditor/GPL-license.txt @@ -0,0 +1,278 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. diff --git a/public/wymeditor/MIT-license.txt b/public/wymeditor/MIT-license.txt new file mode 100644 index 0000000..fc06680 --- /dev/null +++ b/public/wymeditor/MIT-license.txt @@ -0,0 +1,20 @@ +Copyright (c) 2005 - 2009 Jean-Francois Hovinne, http://www.wymeditor.org/ + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/public/wymeditor/README.md b/public/wymeditor/README.md new file mode 100644 index 0000000..3d573d9 --- /dev/null +++ b/public/wymeditor/README.md @@ -0,0 +1,340 @@ +# WYMeditor [![Build Status](https://travis-ci.org/wymeditor/wymeditor.png?branch=master)](https://travis-ci.org/wymeditor/wymeditor) + +WYMeditor is an open source web-based WYSIWYM editor with semantics and +standards in mind. The WYM-part stands for "What You Mean" compared to +the more common "What You See Is What You Get". + +## Why WYMeditor? + +WYMeditor is different from the [traditional](http://www.tinymce.com/) +[editors](http://ckeditor.com/) because we are 100% focused on providing a +simple experience for users that separates the content of their document from +the presentation of that document. We focus on enforcing web standards and +separating a document's structure (HTML) from its presentation (CSS). Your +users won't know and shouldn't care about HTML, but when they need consistent, +standards-compliant, clean content, they'll thank you. + +There are lots of choices when it comes to a browser-based editor and many of +them are stable, mature projects with thousands of users. If you need an editor +that gives total control and flexibility to the user (not you, the developer), +then WYMeditor is probably not for you. If you want an editor that you can +customize to provide the specific capabilities your users need, and you want +users focused on the structure of their content instead of tweaking fonts and +margins, you should give WYMeditor a try. + +## Try It + +Want to see what WYMeditor can do? Try the [WYMeditor +examples](http://wymeditor.no.de/wymeditor/examples/) right now. + +These examples run the bleeding edge code and give you a good idea of what +WYMeditor can do. + +## Quick Start + +1. WYMeditor requires a version of [jQuery](http://jquery.com/) between 1.3.0 + and 1.9.1. First ensure that your page includes jQuery. + + *Note*: If a version of jQuery at or above 1.8.0 is used, WYMeditor also + requires [jQuery Migrate](https://github.com/jquery/jquery-migrate/). + Ensure that your page also includes jQuery Migrate after jQuery is + included. + +2. Download the Version 1.0.0b5 archive from the [release + page](https://github.com/wymeditor/wymeditor/releases/tag/v1.0.0b5) + and extract the contents to a folder in your project. + +3. Include the `wymeditor/jquery.wymeditor.min.js` file on your page using this + script. This file will pull in anything else that's required. + + ```html + + ``` + +4. Now use the `wymeditor()` function to select one of your `textarea` elements + and turn it in to a WYMeditor instance. eg. if you have a `textarea` with + the class `my-wymeditor`: + + ```javascript + $('.my-wymeditor').wymeditor(); + ``` + *Note*: You'll probably want to do this initialization inside a + `$(document).ready()` block. + +5. If you'd like to receive the valid XHTML your editor produces on form + submission, just add the class `wymupdate` to your submit button. + + ```html + + ``` + +6. ??? + +7. Profit! + +More examples with different plugins and configuration options can be found in +your `examples` directory. + +## Compatibility + +WYMeditor is compatible with: + +### Browsers + +* IE: 7, 8, 9 and 10 +* Firefox: LTS and latests two major versions +* Opera: Latest version +* Safari: Latest version +* Google Chrome: Latest two major versions + +### jQuery + +* Versions 1.3.X to 1.7.X +* Version 1.8.x to 1.9.X when you include + [jquery-migrate](https://github.com/jquery/jquery-migrate/) + +## Contributing to WYMeditor + +### Documentation + +Our documentation uses the [Sphinx](http://sphinx-doc.org/) documentation tool. +The source lives in the `docs/` folder and every pull requests that isn't just +fixing a bug *must* come with documentation. + +You can see the current documentation at +[wymeditor.readthedocs.org](http://wymeditor.readthedocs.org). + +### Testing WYMeditor + +To maintain quality, WYMeditor includes both a [Qunit](http://qunitjs.com/) +unit test suite and a [Selenium2](http://seleniumhq.org/) test suite. You are +encouraged to run both of them, with all tests passing in all supported +browsers. If that's ever not the case, please [file a +bug](https://github.com/wymeditor/wymeditor/issues/new) so we can fix it! + +All of the following instructions assume you've already retrieved a copy of the +source, using git like so: + +```shell +git clone git://github.com/wymeditor/wymeditor.git +``` + +#### Unit Tests vs Selenium Tests + +The unit test suite covers the vast majority of required tests and can be +quickly run by anyone with a copy of the source code either from the command +line or in a browser. For the majority of behavior, a unit test will suffice. +Unfortunately, there are cases where browser behavior can't be simulated in a +unit test (these primarily involve testing browser-specific input handling and +`execCommand` behavior). To test these issues, a Selenium test is required. + +Currently, the Selenium test suite is written in python. In the future, we +would like to move to the node.js [wd](https://github.com/admc/wd) module. This +will once again allow a contributor to only require knowledge of javascript in +order to enhance WYMeditor. + +#### Running Unit Tests + +##### Running Unit Tests in a Browser + +WYMeditor includes a full unit test suite to help us ensure that the editor +works great across a variety of browsers. You simply need to serve the +WYMeditor source using some type of web server and then load the URL for the +unit tests in your browser. + +To run the tests: + +1. Put your source behind some kind of web server (apache, nginx, etc). If you + don't have one installed or don't want to fuss with configuration, you can + use python's HTTP server: + + ```shell + $ cd /path/to/my/wymeditor/src + $ python -m SimpleHTTPServer + ``` + +2. The unit test suite is located at `src/test/unit/index.html`, so if you used + the python instructions, open up your browser to + [http://localhost:8000/test/unit/index.html](http://localhost:8000/test/unit/index.html). + +All green means you're good to go. + +##### Running Unit Tests from the Command Line + +In addition to the browser test suite, you can also use +[Phantom.js](http://www.phantomjs.org/) to run the unit tests in a headless +WebKit browser from the command line using [Grunt](http://gruntjs.com/) by +following these instructions: + +1. Navigate to the root directory of the project directory cloned from git. + +2. Use [NPM](http://npmjs.org/) to install the Grunt requirements by running + this command in the root directory of the project: + + ```shell + $ npm install + ``` + *Note*: You might have to run this command as the the root user on your + system. + +3. Use NPM to install the Grunt CLI. + + ```shell + $ npm install -g grunt-cli + ``` + *Note*: You might have to run this command as the the root user on your + system. + +4. Finally, run the unit tests by running the `test` Grunt task in the root + directory of the project: + + ```shell + $ grunt test + ``` + +If the task runs with no errors or failures, you're good to go. + +##### Unit testing different jQuery versions + +The unit tests can be run with the different versions of jQuery hosted on +Google's CDN. To do this when running tests in a browser, append the URL +parameter `?jquery=` to the test suite URL. To do this when running +tests from the command line with Grunt, include the parameter +`--jquery=` when running the `test` task. + +For a browser example, to test with jQuery 1.8.0 against a local server on port +8000, use the URL: +[http://localhost:8000/test/unit/index.html?jquery=1.8.0](http://localhost:8000/test/unit/?jquery=1.8.0). + +For a command line example, to test with jQuery 1.8.0 using Grunt, use the +command: + +```shell +grunt test --jquery=1.8.0 +``` + +If no specific version of jQuery is specified to be used with the unit tests, +the newest build of the oldest fully supported minor version of jQuery will be +used by default (Currently, that version is 1.4.x). + +#### Selenium Tests + +Because WYMeditor is strongly affected by the way various browsers handle +certain native events (keystrokes, mouse navigation, switching windows), it's +not always possible to use JavaScript to actually test that behavior. For +specific cases where it's not possible to reproduce a behavior in JavaScript, +we rely on our [Selenium2](http://seleniumhq.org/) test suite to drive an +actual browser using the [Selenium 2 python +bindings](http://pypi.python.org/pypi/selenium). + +If possible, it is strongly encouraged to write a JavaScript unit test using +Qunit instead of a Selenium test. + +##### Running Selenium Tests + +1. Install the Selenium 2 python bindings, roughly following these + [installation + instructions](http://selenium-python.readthedocs.org/en/latest/installation.html). + The specific version of the python Selenium bindings and the nose testing + framework we require are defined in a [pip](http://www.pip-installer.org/) + requirements file located at `wym_selenium/requirements.txt`. To install + these, we recommend that you first create an isolated python + [virtualenv](http://www.virtualenv.org/): + + ```shell + $ mkdir -p ~/.virtualenvs + $ virtualenv ~/.virtualenvs/wym + ``` + +2. Then use pip to install the requirements: + + ```shell + (wym)$ cd /path/to/wymeditor + (wym)$ pip install -r selenium_requirements.txt + ``` + +3. To run the Selenium tests, you'll first need to serve the `src` directory + with a web server. If you have python installed, then you can simply open a + terminal and run: + + ```shell + $ cd /path/to/wymeditor + $ make testserver + ``` + + You'll need to keep this terminal open when running the tests. + +4. Then you can use make once again (in another terminal) to actually run the + tests: + + ```shell + $ source ~/.virtualenvs/wym/bin/activate + (wym)$ cd /path/to/wymeditor + (wym)$ make selenium + ``` + +### Building WYMeditor + +1. Get a copy of the source using git: + + ```shell + git clone git://github.com/wymeditor/wymeditor.git + ``` + +2. Install `make`, Node.js and [UglifyJS](https://github.com/mishoo/UglifyJS/). + To install UglifyJS using [NPM](http://npmjs.org/) run the following: + + ```shell + npm install -g uglify-js + ``` + +3. Run `make` from your git clone: + + ```shell + $ cd wymeditor + $ make + ``` + +The resulting compressed distribution will appear in your `dist` directory. + +#### Building with Google's Closure Compiler (Java) + +The default WYMeditor distribution is built with +[UglifyJS](https://github.com/mishoo/UglifyJS), which requires the installation +of Node.js. If you prefer Java and/or Google's Closure Compiler, you can follow +these instructions instead. + +1. Install `make` and Java. + +2. Download [Closure Compiler + application](https://developers.google.com/closure/compiler/), extracting + `compiler.jar` into your `wymeditor` directory. + +3. Run `make` from your git clone: + + ```shell + $ cd /path/to/wymeditor + $ make min_closure archive + ``` + +#### A Note on Build Scripts + +The project is currently in the process of moving entirely from `make` to Grunt +as a build tool. Any help porting the remaining `make` tasks to Grunt would be +wonderful, as it's a bit confusing right now. + +## Getting Help + + - **Documentation:** [wymeditor.readthedocs.org](http://wymeditor.readthedocs.org) + - **Forum:** http://community.wymeditor.org + - **Issue tracking:** https://github.com/wymeditor/wymeditor/issues + - **Official branch:** https://github.com/wymeditor/wymeditor + +[Read more on +contributing](https://wymeditor.readthedocs.org/en/latest/version_2.0/contributing.html) + +## Copyright + +Copyright (c) 2005 - 2013 Jean-Francois Hovinne, +Dual licensed under the MIT (MIT-license.txt) +and GPL (GPL-license.txt) licenses. diff --git a/public/wymeditor/examples/01-basic.html b/public/wymeditor/examples/01-basic.html new file mode 100644 index 0000000..72fc7a2 --- /dev/null +++ b/public/wymeditor/examples/01-basic.html @@ -0,0 +1,56 @@ + + + + + +WYMeditor + + + + + + + + + + +

    WYMeditor integration example

    +

    WYMeditor is a web-based XHTML WYSIWYM editor.

    +
    + + +
    + + + + diff --git a/public/wymeditor/examples/02-custom-language.html b/public/wymeditor/examples/02-custom-language.html new file mode 100644 index 0000000..24d95d9 --- /dev/null +++ b/public/wymeditor/examples/02-custom-language.html @@ -0,0 +1,56 @@ + + + + + +WYMeditor + + + + + + + + +

    WYMeditor integration example - custom language

    +

    WYMeditor is a web-based XHTML WYSIWYM editor.

    +
    + + +
    + + + + diff --git a/public/wymeditor/examples/03-plugin.html b/public/wymeditor/examples/03-plugin.html new file mode 100644 index 0000000..14adbec --- /dev/null +++ b/public/wymeditor/examples/03-plugin.html @@ -0,0 +1,64 @@ + + + + + +WYMeditor + + + + + + + + + +

    WYMeditor integration example - plugin activation

    +

    WYMeditor is a web-based XHTML WYSIWYM editor.

    +
    + + +
    + + + + diff --git a/public/wymeditor/examples/04-multiple-instances.html b/public/wymeditor/examples/04-multiple-instances.html new file mode 100644 index 0000000..6890781 --- /dev/null +++ b/public/wymeditor/examples/04-multiple-instances.html @@ -0,0 +1,67 @@ + + + + + +WYMeditor + + + + + + + + +

    WYMeditor integration example - multiple instances

    +

    WYMeditor is a web-based XHTML WYSIWYM editor.

    +
    + + +
    + +
    + +
    + + +
    + +
    + +
    + + +
    + + + + diff --git a/public/wymeditor/examples/05-custom-dialog.html b/public/wymeditor/examples/05-custom-dialog.html new file mode 100644 index 0000000..e10e8dc --- /dev/null +++ b/public/wymeditor/examples/05-custom-dialog.html @@ -0,0 +1,102 @@ + + + + + +WYMeditor + + + + + + + + +

    WYMeditor integration example - custom dialog

    +

    WYMeditor is a web-based XHTML WYSIWYM editor.

    +
    + + +
    + + + + diff --git a/public/wymeditor/examples/06-custom-menu.html b/public/wymeditor/examples/06-custom-menu.html new file mode 100644 index 0000000..0bfb6a7 --- /dev/null +++ b/public/wymeditor/examples/06-custom-menu.html @@ -0,0 +1,73 @@ + + + + + +WYMeditor + + + + + + + + +

    WYMeditor integration example - custom menu

    +

    WYMeditor is a web-based XHTML WYSIWYM editor.

    +
    + + +
    + + + + diff --git a/public/wymeditor/examples/07-custom-button.html b/public/wymeditor/examples/07-custom-button.html new file mode 100644 index 0000000..5c6bc4e --- /dev/null +++ b/public/wymeditor/examples/07-custom-button.html @@ -0,0 +1,74 @@ + + + + + +WYMeditor + + + + + + + + +

    WYMeditor integration example - custom button

    +

    WYMeditor is a web-based XHTML WYSIWYM editor.

    +
    + + +
    + + + + diff --git a/public/wymeditor/examples/08-custom-panel.html b/public/wymeditor/examples/08-custom-panel.html new file mode 100644 index 0000000..785d455 --- /dev/null +++ b/public/wymeditor/examples/08-custom-panel.html @@ -0,0 +1,63 @@ + + + + + +WYMeditor + + + + + + + + +

    WYMeditor integration example - custom panel

    +

    WYMeditor is a web-based XHTML WYSIWYM editor.

    +
    + + +
    + + + + diff --git a/public/wymeditor/examples/09-two-panels.html b/public/wymeditor/examples/09-two-panels.html new file mode 100644 index 0000000..a6ecdaf --- /dev/null +++ b/public/wymeditor/examples/09-two-panels.html @@ -0,0 +1,52 @@ + + + + + +WYMeditor + + + + + + + + +

    WYMeditor integration example - two panels

    +

    WYMeditor is a web-based XHTML WYSIWYM editor.

    +
    + + +
    + + + + diff --git a/public/wymeditor/examples/10-resizable.html b/public/wymeditor/examples/10-resizable.html new file mode 100644 index 0000000..a5af48c --- /dev/null +++ b/public/wymeditor/examples/10-resizable.html @@ -0,0 +1,60 @@ + + + + + +WYMeditor + + + + + + + + + + + + + +

    WYMeditor integration example - resizable plugin

    +

    WYMeditor is a web-based XHTML WYSIWYM editor.

    +
    + + +
    + + + + diff --git a/public/wymeditor/examples/11-custom-parser.html b/public/wymeditor/examples/11-custom-parser.html new file mode 100644 index 0000000..c5efc1a --- /dev/null +++ b/public/wymeditor/examples/11-custom-parser.html @@ -0,0 +1,75 @@ + + + + + +WYMeditor + + + + + + + + +

    WYMeditor custom XHTML parser example

    +

    WYMeditor is a web-based XHTML WYSIWYM editor.

    +
    + + +
    + + + + diff --git a/public/wymeditor/examples/12-custom-layout.html b/public/wymeditor/examples/12-custom-layout.html new file mode 100644 index 0000000..e358564 --- /dev/null +++ b/public/wymeditor/examples/12-custom-layout.html @@ -0,0 +1,107 @@ + + + + + +WYMeditor + + + + + + + + +

    WYMeditor basic integration example with simple skin customisation

    +

    WYMeditor is a web-based XHTML WYSIWYM editor.

    +
    + + +
    + + + + diff --git a/public/wymeditor/examples/13-silver-skin.html b/public/wymeditor/examples/13-silver-skin.html new file mode 100644 index 0000000..a08c068 --- /dev/null +++ b/public/wymeditor/examples/13-silver-skin.html @@ -0,0 +1,61 @@ + + + + + +WYMeditor + + + + + + + + + + + + + +

    WYMeditor integration example - resizable plugin

    +

    WYMeditor is a web-based XHTML WYSIWYM editor.

    +
    + + +
    + + + + diff --git a/public/wymeditor/examples/14-more-inline-elements.html b/public/wymeditor/examples/14-more-inline-elements.html new file mode 100644 index 0000000..f18d914 --- /dev/null +++ b/public/wymeditor/examples/14-more-inline-elements.html @@ -0,0 +1,168 @@ + + + + + +WYMeditor + + + + + + + + +

    WYMeditor integration example - more inline elements

    +

    Adding abbr, acronym, cite, +code, del, ins +and span support

    +

    WYMeditor is a web-based XHTML WYSIWYM editor.

    +
    + + +
    + + + + diff --git a/public/wymeditor/examples/15-rdfa-editor.html b/public/wymeditor/examples/15-rdfa-editor.html new file mode 100644 index 0000000..2793080 --- /dev/null +++ b/public/wymeditor/examples/15-rdfa-editor.html @@ -0,0 +1,188 @@ + + + + + +RDFa editor + + + + + + + + + +

    WYMeditor based RDFa editor (proof of concept)

    +

    WYMeditor is a web-based XHTML WYSIWYM editor.

    +

    More information on the forum: WYMeditor for RDFa

    +
    + + +
    + + + + diff --git a/public/wymeditor/examples/16-google-apis.html b/public/wymeditor/examples/16-google-apis.html new file mode 100644 index 0000000..9759943 --- /dev/null +++ b/public/wymeditor/examples/16-google-apis.html @@ -0,0 +1,46 @@ + + + + + +WYMeditor + + + + + + + + + + +

    WYMeditor integration example

    +

    WYMeditor is a web-based XHTML WYSIWYM editor.

    +
    + + +
    + + + + diff --git a/public/wymeditor/examples/17-pretty-theme.html b/public/wymeditor/examples/17-pretty-theme.html new file mode 100644 index 0000000..daefeda --- /dev/null +++ b/public/wymeditor/examples/17-pretty-theme.html @@ -0,0 +1,199 @@ + + + + + +WYMeditor + + + + + + + + + + +

    WYMeditor integration example

    +

    WYMeditor is a web-based XHTML WYSIWYM editor.

    +
    + + +
    + + + + diff --git a/public/wymeditor/examples/18-compact-skin.html b/public/wymeditor/examples/18-compact-skin.html new file mode 100644 index 0000000..e038ad8 --- /dev/null +++ b/public/wymeditor/examples/18-compact-skin.html @@ -0,0 +1,51 @@ + + + + + Test Page + + + + + + + + + + + + + +
    +
    + +
    +
    + + + diff --git a/public/wymeditor/examples/19-structured-headings.html b/public/wymeditor/examples/19-structured-headings.html new file mode 100644 index 0000000..ff2ab14 --- /dev/null +++ b/public/wymeditor/examples/19-structured-headings.html @@ -0,0 +1,62 @@ + + + + + +WYMeditor + + + + + + + + + + + + +

    WYMeditor integration example - structured headings plugin

    +

    WYMeditor is a web-based XHTML WYSIWYM editor.

    +
    + + +
    + + + + + diff --git a/public/wymeditor/examples/README b/public/wymeditor/examples/README new file mode 100644 index 0000000..389b30f --- /dev/null +++ b/public/wymeditor/examples/README @@ -0,0 +1,46 @@ +WYMeditor : what you see is What You Mean web-based editor +Copyright (c) 2005 - 2009 Jean-Francois Hovinne, http://www.wymeditor.org/ +Dual licensed under the MIT (MIT-license.txt) +and GPL (GPL-license.txt) licenses. + +For further information visit: + http://www.wymeditor.org/ + +File Name: + README + Readme file for WYMeditor integration examples. + +File Authors: + Jean-François Hovinne - http://www.hovinne.com/ + + +INTEGRATION EXAMPLES +==================== + +This directory contains step-by-step examples to help you +get started integrating WYMeditor in your application. + +Please start with 01-basic.html. + +For more information, please read the documentation, available at: +http://trac.wymeditor.org/trac/wiki + +If you need help, feel free to create an account on the forum or subscribe +to the WYMeditor-user mailing-list, and ask your question. +http://forum.wymeditor.org/ +http://lists.wymeditor.org/ + +More examples will be added from time to time. +You can check them out with Subversion: +$ svn co svn://svn.wymeditor.org/wymeditor/trunk/src/examples + +Subversion is an open source version control system, available at: +http://subversion.tigris.org/ + +A Windows Subversion client is available at: +http://tortoisesvn.tigris.org/ + +Note: if you test the examples locally with FF3, you'll need to turn off +security.fileuri.strict_origin_policy (type about:config in the URL and search +for the value). +More info: http://kb.mozillazine.org/Security.fileuri.strict_origin_policy diff --git a/public/wymeditor/examples/index.html b/public/wymeditor/examples/index.html new file mode 100644 index 0000000..1a8bbe7 --- /dev/null +++ b/public/wymeditor/examples/index.html @@ -0,0 +1,59 @@ + + + + + + WYMeditor + + + +

    WYMeditor integration examples

    +

    + WYMeditor is a web-based XHTML WYSIWYM editor. Please study the source code of each page to learn more about the examples and how to implement them. +

    +

    + Note: These examples might not work locally in your browser due to browser security policies regarding frames. Serve WYMeditor and this page from a proper web server or try out the demos online. +

    +

    +

      +
    1. Basic integration
    2. +
    3. Custom language
    4. +
    5. Plugin
    6. +
    7. Multiple instances
    8. +
    9. Custom dialog
    10. +
    11. Custom menu
    12. +
    13. Custom button
    14. +
    15. Custom panel
    16. +
    17. Two panels
    18. +
    19. Resizable
    20. +
    21. Custom XHTML parser
    22. +
    23. Custom layout
    24. +
    25. Silver skin, hovertools, resizable
    26. +
    27. More inline elements
    28. +
    29. RDFa editor
    30. +
    31. Google APIs
    32. +
    33. Pretty theme
    34. +
    35. Compact Skin
    36. +
    37. Structured headings plugin
    38. +
    +

    + + + diff --git a/public/wymeditor/examples/styles.css b/public/wymeditor/examples/styles.css new file mode 100644 index 0000000..5c77940 --- /dev/null +++ b/public/wymeditor/examples/styles.css @@ -0,0 +1,64 @@ +/* YOU CAN ADD NON-WYMEDITOR RELATED STYLES HERE */ + +body { + font-family: sans-serif; +} + +h1 { + font-size: 1.5em; +} + +/* EDITOR RELATED STYLES - CUSTOMIZE FOR YOUR NEEDS */ +/* + The classes panel, the visual feedback and the preview + will be affected by these values. + + - Commented styles inside style definitions are used for visual + feedback when using the editor. + - Comments before opening the style are used as caption. + - Comments after the class name and before the style declaration ({) + define the jQuery expression that decides whether this + style should be applied or not. + + Note: the WYMeditor and /WYMeditor comments below are required + for the CSS parser to work properly. +*/ + +/* WYMeditor */ + +/* PARA: Date */ +p.date { + color: #ccf; + /* background-color: #ff9; border: 2px solid #ee9; */ +} + +/* PARA: Hidden note */ +p.hidden-note /* p[@class!="important"] */ { + display: none; + /* color: #999; border: 2px solid #ccc; */ +} + +/* PARA: Important */ +p.important /* p[@class!="hidden-note"] */ { + color: red; font-weight: bold; + /* color: red; font-weight: bold; border: 2px solid red; */ +} + +img.border { + border: 1px solid #ccc; + /* border: 4px solid #ccc; */ +} + +/* LIST: Special */ +ul.special, +ol.special { + color: green; + /** / background-color: #fc9; border: 2px solid red; /**/ +} + + +/* /WYMeditor */ +/* END EDITOR RELATED STYLES */ + + +/* YOU CAN ADD NON-WYMEDITOR RELATED STYLES HERE */ diff --git a/public/wymeditor/jquery/GPL-LICENSE.txt b/public/wymeditor/jquery/GPL-LICENSE.txt new file mode 100644 index 0000000..11dddd0 --- /dev/null +++ b/public/wymeditor/jquery/GPL-LICENSE.txt @@ -0,0 +1,278 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. diff --git a/public/wymeditor/jquery/MIT-LICENSE.txt b/public/wymeditor/jquery/MIT-LICENSE.txt new file mode 100644 index 0000000..965a831 --- /dev/null +++ b/public/wymeditor/jquery/MIT-LICENSE.txt @@ -0,0 +1,20 @@ +Copyright (c) 2007 John Resig, http://jquery.com/ + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/public/wymeditor/jquery/README b/public/wymeditor/jquery/README new file mode 100644 index 0000000..cb93648 --- /dev/null +++ b/public/wymeditor/jquery/README @@ -0,0 +1,67 @@ + +-------------------------------- +jQuery - New Wave Javascript +http://jquery.com/ +-------------------------------- + +What you need to build your own jQuery: + - Make sure that you have Java installed. + + If not, go to this page and download "Java Runtime Environment (JRE) 5.0" + http://java.sun.com/javase/downloads/index.jsp + + - You now have two options for building jQuery, if you have access to + common UNIX commands (like 'make', 'mkdir', 'rm', 'cat', and 'echo') + then simply type 'make' to build all the components. + + - The other option is if you have Ant installed (or are on Windows and + don't have access to make). You can download Ant from here: + http://ant.apache.org/bindownload.cgi + + If you do have Ant, everytime (in this README) that I say 'make', do + 'ant' instead - it works identically (for all intents and purposes). + +How to build your own jQuery: + +In the main directory of the distribution (the one that this file is in), type +the following to make all versions of jQuery, the documentation, and the test suite: + make + +Here are each of the individual items that are buildable from the Makefile. + +make jquery + The standard, uncompressed, jQuery code. + Makes: ./dist/jquery.js + +make lite + jQuery without all the additional inline documentation and test cases. + Makes: ./dist/jquery.lite.js + +make pack + A compressed version of jQuery (made with Packer). + Makes: ./dist/jquery.pack.js + +make docs + Builds a complete copy of the documentation, based upon the jQuery source. + Makes ./docs/ + Open this file in your browser: + ./docs/index.xml + +make test + Builds a complete copy of the test suite, based upon the jQuery source. + Makes ./test/ + Open this file in your browser: + ./test/index.html + +Finally, you can remove all the built files using the command: + make clean + +Additionally, if you want to install jQuery to a location that is not this +directory, you can specify the PREFIX directory, for example: + make PREFIX=/home/john/test/ +OR + make PREFIX=~/www/ docs + +If you have any questions, please feel free to ask them on the jQuery +mailing list, which can be found here: + http://jquery.com/discuss/ diff --git a/public/wymeditor/jquery/jquery-migrate.min.js b/public/wymeditor/jquery/jquery-migrate.min.js new file mode 100644 index 0000000..8b7ec47 --- /dev/null +++ b/public/wymeditor/jquery/jquery-migrate.min.js @@ -0,0 +1,2 @@ +/*! jQuery Migrate v1.2.1 | (c) 2005, 2013 jQuery Foundation, Inc. and other contributors | jquery.org/license */ +jQuery.migrateMute===void 0&&(jQuery.migrateMute=!0),function(e,t,n){function r(n){var r=t.console;i[n]||(i[n]=!0,e.migrateWarnings.push(n),r&&r.warn&&!e.migrateMute&&(r.warn("JQMIGRATE: "+n),e.migrateTrace&&r.trace&&r.trace()))}function a(t,a,i,o){if(Object.defineProperty)try{return Object.defineProperty(t,a,{configurable:!0,enumerable:!0,get:function(){return r(o),i},set:function(e){r(o),i=e}}),n}catch(s){}e._definePropertyBroken=!0,t[a]=i}var i={};e.migrateWarnings=[],!e.migrateMute&&t.console&&t.console.log&&t.console.log("JQMIGRATE: Logging is active"),e.migrateTrace===n&&(e.migrateTrace=!0),e.migrateReset=function(){i={},e.migrateWarnings.length=0},"BackCompat"===document.compatMode&&r("jQuery is not compatible with Quirks Mode");var o=e("",{size:1}).attr("size")&&e.attrFn,s=e.attr,u=e.attrHooks.value&&e.attrHooks.value.get||function(){return null},c=e.attrHooks.value&&e.attrHooks.value.set||function(){return n},l=/^(?:input|button)$/i,d=/^[238]$/,p=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,f=/^(?:checked|selected)$/i;a(e,"attrFn",o||{},"jQuery.attrFn is deprecated"),e.attr=function(t,a,i,u){var c=a.toLowerCase(),g=t&&t.nodeType;return u&&(4>s.length&&r("jQuery.fn.attr( props, pass ) is deprecated"),t&&!d.test(g)&&(o?a in o:e.isFunction(e.fn[a])))?e(t)[a](i):("type"===a&&i!==n&&l.test(t.nodeName)&&t.parentNode&&r("Can't change the 'type' of an input or button in IE 6/7/8"),!e.attrHooks[c]&&p.test(c)&&(e.attrHooks[c]={get:function(t,r){var a,i=e.prop(t,r);return i===!0||"boolean"!=typeof i&&(a=t.getAttributeNode(r))&&a.nodeValue!==!1?r.toLowerCase():n},set:function(t,n,r){var a;return n===!1?e.removeAttr(t,r):(a=e.propFix[r]||r,a in t&&(t[a]=!0),t.setAttribute(r,r.toLowerCase())),r}},f.test(c)&&r("jQuery.fn.attr('"+c+"') may use property instead of attribute")),s.call(e,t,a,i))},e.attrHooks.value={get:function(e,t){var n=(e.nodeName||"").toLowerCase();return"button"===n?u.apply(this,arguments):("input"!==n&&"option"!==n&&r("jQuery.fn.attr('value') no longer gets properties"),t in e?e.value:null)},set:function(e,t){var a=(e.nodeName||"").toLowerCase();return"button"===a?c.apply(this,arguments):("input"!==a&&"option"!==a&&r("jQuery.fn.attr('value', val) no longer sets properties"),e.value=t,n)}};var g,h,v=e.fn.init,m=e.parseJSON,y=/^([^<]*)(<[\w\W]+>)([^>]*)$/;e.fn.init=function(t,n,a){var i;return t&&"string"==typeof t&&!e.isPlainObject(n)&&(i=y.exec(e.trim(t)))&&i[0]&&("<"!==t.charAt(0)&&r("$(html) HTML strings must start with '<' character"),i[3]&&r("$(html) HTML text after last tag is ignored"),"#"===i[0].charAt(0)&&(r("HTML string cannot start with a '#' character"),e.error("JQMIGRATE: Invalid selector string (XSS)")),n&&n.context&&(n=n.context),e.parseHTML)?v.call(this,e.parseHTML(i[2],n,!0),n,a):v.apply(this,arguments)},e.fn.init.prototype=e.fn,e.parseJSON=function(e){return e||null===e?m.apply(this,arguments):(r("jQuery.parseJSON requires a valid JSON string"),null)},e.uaMatch=function(e){e=e.toLowerCase();var t=/(chrome)[ \/]([\w.]+)/.exec(e)||/(webkit)[ \/]([\w.]+)/.exec(e)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(e)||/(msie) ([\w.]+)/.exec(e)||0>e.indexOf("compatible")&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(e)||[];return{browser:t[1]||"",version:t[2]||"0"}},e.browser||(g=e.uaMatch(navigator.userAgent),h={},g.browser&&(h[g.browser]=!0,h.version=g.version),h.chrome?h.webkit=!0:h.webkit&&(h.safari=!0),e.browser=h),a(e,"browser",e.browser,"jQuery.browser is deprecated"),e.sub=function(){function t(e,n){return new t.fn.init(e,n)}e.extend(!0,t,this),t.superclass=this,t.fn=t.prototype=this(),t.fn.constructor=t,t.sub=this.sub,t.fn.init=function(r,a){return a&&a instanceof e&&!(a instanceof t)&&(a=t(a)),e.fn.init.call(this,r,a,n)},t.fn.init.prototype=t.fn;var n=t(document);return r("jQuery.sub() is deprecated"),t},e.ajaxSetup({converters:{"text json":e.parseJSON}});var b=e.fn.data;e.fn.data=function(t){var a,i,o=this[0];return!o||"events"!==t||1!==arguments.length||(a=e.data(o,t),i=e._data(o,t),a!==n&&a!==i||i===n)?b.apply(this,arguments):(r("Use of jQuery.fn.data('events') is deprecated"),i)};var j=/\/(java|ecma)script/i,w=e.fn.andSelf||e.fn.addBack;e.fn.andSelf=function(){return r("jQuery.fn.andSelf() replaced by jQuery.fn.addBack()"),w.apply(this,arguments)},e.clean||(e.clean=function(t,a,i,o){a=a||document,a=!a.nodeType&&a[0]||a,a=a.ownerDocument||a,r("jQuery.clean() is deprecated");var s,u,c,l,d=[];if(e.merge(d,e.buildFragment(t,a).childNodes),i)for(c=function(e){return!e.type||j.test(e.type)?o?o.push(e.parentNode?e.parentNode.removeChild(e):e):i.appendChild(e):n},s=0;null!=(u=d[s]);s++)e.nodeName(u,"script")&&c(u)||(i.appendChild(u),u.getElementsByTagName!==n&&(l=e.grep(e.merge([],u.getElementsByTagName("script")),c),d.splice.apply(d,[s+1,0].concat(l)),s+=l.length));return d});var Q=e.event.add,x=e.event.remove,k=e.event.trigger,N=e.fn.toggle,T=e.fn.live,M=e.fn.die,S="ajaxStart|ajaxStop|ajaxSend|ajaxComplete|ajaxError|ajaxSuccess",C=RegExp("\\b(?:"+S+")\\b"),H=/(?:^|\s)hover(\.\S+|)\b/,A=function(t){return"string"!=typeof t||e.event.special.hover?t:(H.test(t)&&r("'hover' pseudo-event is deprecated, use 'mouseenter mouseleave'"),t&&t.replace(H,"mouseenter$1 mouseleave$1"))};e.event.props&&"attrChange"!==e.event.props[0]&&e.event.props.unshift("attrChange","attrName","relatedNode","srcElement"),e.event.dispatch&&a(e.event,"handle",e.event.dispatch,"jQuery.event.handle is undocumented and deprecated"),e.event.add=function(e,t,n,a,i){e!==document&&C.test(t)&&r("AJAX events should be attached to document: "+t),Q.call(this,e,A(t||""),n,a,i)},e.event.remove=function(e,t,n,r,a){x.call(this,e,A(t)||"",n,r,a)},e.fn.error=function(){var e=Array.prototype.slice.call(arguments,0);return r("jQuery.fn.error() is deprecated"),e.splice(0,0,"error"),arguments.length?this.bind.apply(this,e):(this.triggerHandler.apply(this,e),this)},e.fn.toggle=function(t,n){if(!e.isFunction(t)||!e.isFunction(n))return N.apply(this,arguments);r("jQuery.fn.toggle(handler, handler...) is deprecated");var a=arguments,i=t.guid||e.guid++,o=0,s=function(n){var r=(e._data(this,"lastToggle"+t.guid)||0)%o;return e._data(this,"lastToggle"+t.guid,r+1),n.preventDefault(),a[r].apply(this,arguments)||!1};for(s.guid=i;a.length>o;)a[o++].guid=i;return this.click(s)},e.fn.live=function(t,n,a){return r("jQuery.fn.live() is deprecated"),T?T.apply(this,arguments):(e(this.context).on(t,this.selector,n,a),this)},e.fn.die=function(t,n){return r("jQuery.fn.die() is deprecated"),M?M.apply(this,arguments):(e(this.context).off(t,this.selector||"**",n),this)},e.event.trigger=function(e,t,n,a){return n||C.test(e)||r("Global events are undocumented and deprecated"),k.call(this,e,t,n||document,a)},e.each(S.split("|"),function(t,n){e.event.special[n]={setup:function(){var t=this;return t!==document&&(e.event.add(document,n+"."+e.guid,function(){e.event.trigger(n,null,t,!0)}),e._data(this,n,e.guid++)),!1},teardown:function(){return this!==document&&e.event.remove(document,n+"."+e._data(this,n)),!1}}})}(jQuery,window); \ No newline at end of file diff --git a/public/wymeditor/jquery/jquery-ui-1.8.11.custom.min.js b/public/wymeditor/jquery/jquery-ui-1.8.11.custom.min.js new file mode 100755 index 0000000..9c2beb6 --- /dev/null +++ b/public/wymeditor/jquery/jquery-ui-1.8.11.custom.min.js @@ -0,0 +1,97 @@ +/*! + * jQuery UI 1.8.11 + * + * 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){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.11",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,l,m){c.each(e,function(){g-=parseFloat(c.curCSS(f,"padding"+this,true))||0;if(l)g-=parseFloat(c.curCSS(f, +"border"+this+"Width",true))||0;if(m)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){var b=a.nodeName.toLowerCase(),d=c.attr(a,"tabindex");if("area"===b){b=a.parentNode;d=b.name;if(!a.href||!d||b.nodeName.toLowerCase()!=="map")return false;a=c("img[usemap=#"+d+"]")[0];return!!a&&k(a)}return(/input|select|textarea|button|object/.test(b)?!a.disabled:"a"==b?a.href||!isNaN(d):!isNaN(d))&&k(a)},tabbable:function(a){var b=c.attr(a,"tabindex");return(isNaN(b)||b>=0)&&c(a).is(":focusable")}}); +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;e0)return true;a[b]=1;d=a[b]>0;a[b]=0;return d},isOverAxis:function(a,b,d){return a>b&&a=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 Resizable 1.8.11 + * + * 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/Resizables + * + * Depends: + * jquery.ui.core.js + * jquery.ui.mouse.js + * jquery.ui.widget.js + */ +(function(e){e.widget("ui.resizable",e.ui.mouse,{widgetEventPrefix:"resize",options:{alsoResize:false,animate:false,animateDuration:"slow",animateEasing:"swing",aspectRatio:false,autoHide:false,containment:false,ghost:false,grid:false,handles:"e,s,se",helper:false,maxHeight:null,maxWidth:null,minHeight:10,minWidth:10,zIndex:1E3},_create:function(){var b=this,a=this.options;this.element.addClass("ui-resizable");e.extend(this,{_aspectRatio:!!a.aspectRatio,aspectRatio:a.aspectRatio,originalElement:this.element, +_proportionallyResizeElements:[],_helper:a.helper||a.ghost||a.animate?a.helper||"ui-resizable-helper":null});if(this.element[0].nodeName.match(/canvas|textarea|input|select|button|img/i)){/relative/.test(this.element.css("position"))&&e.browser.opera&&this.element.css({position:"relative",top:"auto",left:"auto"});this.element.wrap(e('
    ').css({position:this.element.css("position"),width:this.element.outerWidth(),height:this.element.outerHeight(), +top:this.element.css("top"),left:this.element.css("left")}));this.element=this.element.parent().data("resizable",this.element.data("resizable"));this.elementIsWrapper=true;this.element.css({marginLeft:this.originalElement.css("marginLeft"),marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom")});this.originalElement.css({marginLeft:0,marginTop:0,marginRight:0,marginBottom:0});this.originalResizeStyle= +this.originalElement.css("resize");this.originalElement.css("resize","none");this._proportionallyResizeElements.push(this.originalElement.css({position:"static",zoom:1,display:"block"}));this.originalElement.css({margin:this.originalElement.css("margin")});this._proportionallyResize()}this.handles=a.handles||(!e(".ui-resizable-handle",this.element).length?"e,s,se":{n:".ui-resizable-n",e:".ui-resizable-e",s:".ui-resizable-s",w:".ui-resizable-w",se:".ui-resizable-se",sw:".ui-resizable-sw",ne:".ui-resizable-ne", +nw:".ui-resizable-nw"});if(this.handles.constructor==String){if(this.handles=="all")this.handles="n,e,s,w,se,sw,ne,nw";var c=this.handles.split(",");this.handles={};for(var d=0;d
  • ');/sw|se|ne|nw/.test(f)&&g.css({zIndex:++a.zIndex});"se"==f&&g.addClass("ui-icon ui-icon-gripsmall-diagonal-se");this.handles[f]=".ui-resizable-"+f;this.element.append(g)}}this._renderAxis=function(h){h=h||this.element;for(var i in this.handles){if(this.handles[i].constructor== +String)this.handles[i]=e(this.handles[i],this.element).show();if(this.elementIsWrapper&&this.originalElement[0].nodeName.match(/textarea|input|select|button/i)){var j=e(this.handles[i],this.element),k=0;k=/sw|ne|nw|se|n|s/.test(i)?j.outerHeight():j.outerWidth();j=["padding",/ne|nw|n/.test(i)?"Top":/se|sw|s/.test(i)?"Bottom":/^e$/.test(i)?"Right":"Left"].join("");h.css(j,k);this._proportionallyResize()}e(this.handles[i])}};this._renderAxis(this.element);this._handles=e(".ui-resizable-handle",this.element).disableSelection(); +this._handles.mouseover(function(){if(!b.resizing){if(this.className)var h=this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i);b.axis=h&&h[1]?h[1]:"se"}});if(a.autoHide){this._handles.hide();e(this.element).addClass("ui-resizable-autohide").hover(function(){e(this).removeClass("ui-resizable-autohide");b._handles.show()},function(){if(!b.resizing){e(this).addClass("ui-resizable-autohide");b._handles.hide()}})}this._mouseInit()},destroy:function(){this._mouseDestroy();var b=function(c){e(c).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing").removeData("resizable").unbind(".resizable").find(".ui-resizable-handle").remove()}; +if(this.elementIsWrapper){b(this.element);var a=this.element;a.after(this.originalElement.css({position:a.css("position"),width:a.outerWidth(),height:a.outerHeight(),top:a.css("top"),left:a.css("left")})).remove()}this.originalElement.css("resize",this.originalResizeStyle);b(this.originalElement);return this},_mouseCapture:function(b){var a=false;for(var c in this.handles)if(e(this.handles[c])[0]==b.target)a=true;return!this.options.disabled&&a},_mouseStart:function(b){var a=this.options,c=this.element.position(), +d=this.element;this.resizing=true;this.documentScroll={top:e(document).scrollTop(),left:e(document).scrollLeft()};if(d.is(".ui-draggable")||/absolute/.test(d.css("position")))d.css({position:"absolute",top:c.top,left:c.left});e.browser.opera&&/relative/.test(d.css("position"))&&d.css({position:"relative",top:"auto",left:"auto"});this._renderProxy();c=m(this.helper.css("left"));var f=m(this.helper.css("top"));if(a.containment){c+=e(a.containment).scrollLeft()||0;f+=e(a.containment).scrollTop()||0}this.offset= +this.helper.offset();this.position={left:c,top:f};this.size=this._helper?{width:d.outerWidth(),height:d.outerHeight()}:{width:d.width(),height:d.height()};this.originalSize=this._helper?{width:d.outerWidth(),height:d.outerHeight()}:{width:d.width(),height:d.height()};this.originalPosition={left:c,top:f};this.sizeDiff={width:d.outerWidth()-d.width(),height:d.outerHeight()-d.height()};this.originalMousePosition={left:b.pageX,top:b.pageY};this.aspectRatio=typeof a.aspectRatio=="number"?a.aspectRatio: +this.originalSize.width/this.originalSize.height||1;a=e(".ui-resizable-"+this.axis).css("cursor");e("body").css("cursor",a=="auto"?this.axis+"-resize":a);d.addClass("ui-resizable-resizing");this._propagate("start",b);return true},_mouseDrag:function(b){var a=this.helper,c=this.originalMousePosition,d=this._change[this.axis];if(!d)return false;c=d.apply(this,[b,b.pageX-c.left||0,b.pageY-c.top||0]);if(this._aspectRatio||b.shiftKey)c=this._updateRatio(c,b);c=this._respectSize(c,b);this._propagate("resize", +b);a.css({top:this.position.top+"px",left:this.position.left+"px",width:this.size.width+"px",height:this.size.height+"px"});!this._helper&&this._proportionallyResizeElements.length&&this._proportionallyResize();this._updateCache(c);this._trigger("resize",b,this.ui());return false},_mouseStop:function(b){this.resizing=false;var a=this.options,c=this;if(this._helper){var d=this._proportionallyResizeElements,f=d.length&&/textarea/i.test(d[0].nodeName);d=f&&e.ui.hasScroll(d[0],"left")?0:c.sizeDiff.height; +f=f?0:c.sizeDiff.width;f={width:c.helper.width()-f,height:c.helper.height()-d};d=parseInt(c.element.css("left"),10)+(c.position.left-c.originalPosition.left)||null;var g=parseInt(c.element.css("top"),10)+(c.position.top-c.originalPosition.top)||null;a.animate||this.element.css(e.extend(f,{top:g,left:d}));c.helper.height(c.size.height);c.helper.width(c.size.width);this._helper&&!a.animate&&this._proportionallyResize()}e("body").css("cursor","auto");this.element.removeClass("ui-resizable-resizing"); +this._propagate("stop",b);this._helper&&this.helper.remove();return false},_updateCache:function(b){this.offset=this.helper.offset();if(l(b.left))this.position.left=b.left;if(l(b.top))this.position.top=b.top;if(l(b.height))this.size.height=b.height;if(l(b.width))this.size.width=b.width},_updateRatio:function(b){var a=this.position,c=this.size,d=this.axis;if(b.height)b.width=c.height*this.aspectRatio;else if(b.width)b.height=c.width/this.aspectRatio;if(d=="sw"){b.left=a.left+(c.width-b.width);b.top= +null}if(d=="nw"){b.top=a.top+(c.height-b.height);b.left=a.left+(c.width-b.width)}return b},_respectSize:function(b){var a=this.options,c=this.axis,d=l(b.width)&&a.maxWidth&&a.maxWidthb.width,h=l(b.height)&&a.minHeight&&a.minHeight>b.height;if(g)b.width=a.minWidth;if(h)b.height=a.minHeight;if(d)b.width=a.maxWidth;if(f)b.height=a.maxHeight;var i=this.originalPosition.left+this.originalSize.width,j=this.position.top+ +this.size.height,k=/sw|nw|w/.test(c);c=/nw|ne|n/.test(c);if(g&&k)b.left=i-a.minWidth;if(d&&k)b.left=i-a.maxWidth;if(h&&c)b.top=j-a.minHeight;if(f&&c)b.top=j-a.maxHeight;if((a=!b.width&&!b.height)&&!b.left&&b.top)b.top=null;else if(a&&!b.top&&b.left)b.left=null;return b},_proportionallyResize:function(){if(this._proportionallyResizeElements.length)for(var b=this.helper||this.element,a=0;a
    ');var a=e.browser.msie&&e.browser.version<7,c=a?1:0;a=a?2:-1;this.helper.addClass(this._helper).css({width:this.element.outerWidth()+a,height:this.element.outerHeight()+a,position:"absolute",left:this.elementOffset.left-c+"px",top:this.elementOffset.top-c+"px",zIndex:++b.zIndex});this.helper.appendTo("body").disableSelection()}else this.helper=this.element},_change:{e:function(b, +a){return{width:this.originalSize.width+a}},w:function(b,a){return{left:this.originalPosition.left+a,width:this.originalSize.width-a}},n:function(b,a,c){return{top:this.originalPosition.top+c,height:this.originalSize.height-c}},s:function(b,a,c){return{height:this.originalSize.height+c}},se:function(b,a,c){return e.extend(this._change.s.apply(this,arguments),this._change.e.apply(this,[b,a,c]))},sw:function(b,a,c){return e.extend(this._change.s.apply(this,arguments),this._change.w.apply(this,[b,a, +c]))},ne:function(b,a,c){return e.extend(this._change.n.apply(this,arguments),this._change.e.apply(this,[b,a,c]))},nw:function(b,a,c){return e.extend(this._change.n.apply(this,arguments),this._change.w.apply(this,[b,a,c]))}},_propagate:function(b,a){e.ui.plugin.call(this,b,[a,this.ui()]);b!="resize"&&this._trigger(b,a,this.ui())},plugins:{},ui:function(){return{originalElement:this.originalElement,element:this.element,helper:this.helper,position:this.position,size:this.size,originalSize:this.originalSize, +originalPosition:this.originalPosition}}});e.extend(e.ui.resizable,{version:"1.8.11"});e.ui.plugin.add("resizable","alsoResize",{start:function(){var b=e(this).data("resizable").options,a=function(c){e(c).each(function(){var d=e(this);d.data("resizable-alsoresize",{width:parseInt(d.width(),10),height:parseInt(d.height(),10),left:parseInt(d.css("left"),10),top:parseInt(d.css("top"),10),position:d.css("position")})})};if(typeof b.alsoResize=="object"&&!b.alsoResize.parentNode)if(b.alsoResize.length){b.alsoResize= +b.alsoResize[0];a(b.alsoResize)}else e.each(b.alsoResize,function(c){a(c)});else a(b.alsoResize)},resize:function(b,a){var c=e(this).data("resizable");b=c.options;var d=c.originalSize,f=c.originalPosition,g={height:c.size.height-d.height||0,width:c.size.width-d.width||0,top:c.position.top-f.top||0,left:c.position.left-f.left||0},h=function(i,j){e(i).each(function(){var k=e(this),q=e(this).data("resizable-alsoresize"),p={},r=j&&j.length?j:k.parents(a.originalElement[0]).length?["width","height"]:["width", +"height","top","left"];e.each(r,function(n,o){if((n=(q[o]||0)+(g[o]||0))&&n>=0)p[o]=n||null});if(e.browser.opera&&/relative/.test(k.css("position"))){c._revertToRelativePosition=true;k.css({position:"absolute",top:"auto",left:"auto"})}k.css(p)})};typeof b.alsoResize=="object"&&!b.alsoResize.nodeType?e.each(b.alsoResize,function(i,j){h(i,j)}):h(b.alsoResize)},stop:function(){var b=e(this).data("resizable"),a=b.options,c=function(d){e(d).each(function(){var f=e(this);f.css({position:f.data("resizable-alsoresize").position})})}; +if(b._revertToRelativePosition){b._revertToRelativePosition=false;typeof a.alsoResize=="object"&&!a.alsoResize.nodeType?e.each(a.alsoResize,function(d){c(d)}):c(a.alsoResize)}e(this).removeData("resizable-alsoresize")}});e.ui.plugin.add("resizable","animate",{stop:function(b){var a=e(this).data("resizable"),c=a.options,d=a._proportionallyResizeElements,f=d.length&&/textarea/i.test(d[0].nodeName),g=f&&e.ui.hasScroll(d[0],"left")?0:a.sizeDiff.height;f={width:a.size.width-(f?0:a.sizeDiff.width),height:a.size.height- +g};g=parseInt(a.element.css("left"),10)+(a.position.left-a.originalPosition.left)||null;var h=parseInt(a.element.css("top"),10)+(a.position.top-a.originalPosition.top)||null;a.element.animate(e.extend(f,h&&g?{top:h,left:g}:{}),{duration:c.animateDuration,easing:c.animateEasing,step:function(){var i={width:parseInt(a.element.css("width"),10),height:parseInt(a.element.css("height"),10),top:parseInt(a.element.css("top"),10),left:parseInt(a.element.css("left"),10)};d&&d.length&&e(d[0]).css({width:i.width, +height:i.height});a._updateCache(i);a._propagate("resize",b)}})}});e.ui.plugin.add("resizable","containment",{start:function(){var b=e(this).data("resizable"),a=b.element,c=b.options.containment;if(a=c instanceof e?c.get(0):/parent/.test(c)?a.parent().get(0):c){b.containerElement=e(a);if(/document/.test(c)||c==document){b.containerOffset={left:0,top:0};b.containerPosition={left:0,top:0};b.parentData={element:e(document),left:0,top:0,width:e(document).width(),height:e(document).height()||document.body.parentNode.scrollHeight}}else{var d= +e(a),f=[];e(["Top","Right","Left","Bottom"]).each(function(i,j){f[i]=m(d.css("padding"+j))});b.containerOffset=d.offset();b.containerPosition=d.position();b.containerSize={height:d.innerHeight()-f[3],width:d.innerWidth()-f[1]};c=b.containerOffset;var g=b.containerSize.height,h=b.containerSize.width;h=e.ui.hasScroll(a,"left")?a.scrollWidth:h;g=e.ui.hasScroll(a)?a.scrollHeight:g;b.parentData={element:a,left:c.left,top:c.top,width:h,height:g}}}},resize:function(b){var a=e(this).data("resizable"),c=a.options, +d=a.containerOffset,f=a.position;b=a._aspectRatio||b.shiftKey;var g={top:0,left:0},h=a.containerElement;if(h[0]!=document&&/static/.test(h.css("position")))g=d;if(f.left<(a._helper?d.left:0)){a.size.width+=a._helper?a.position.left-d.left:a.position.left-g.left;if(b)a.size.height=a.size.width/c.aspectRatio;a.position.left=c.helper?d.left:0}if(f.top<(a._helper?d.top:0)){a.size.height+=a._helper?a.position.top-d.top:a.position.top;if(b)a.size.width=a.size.height*c.aspectRatio;a.position.top=a._helper? +d.top:0}a.offset.left=a.parentData.left+a.position.left;a.offset.top=a.parentData.top+a.position.top;c=Math.abs((a._helper?a.offset.left-g.left:a.offset.left-g.left)+a.sizeDiff.width);d=Math.abs((a._helper?a.offset.top-g.top:a.offset.top-d.top)+a.sizeDiff.height);f=a.containerElement.get(0)==a.element.parent().get(0);g=/relative|absolute/.test(a.containerElement.css("position"));if(f&&g)c-=a.parentData.left;if(c+a.size.width>=a.parentData.width){a.size.width=a.parentData.width-c;if(b)a.size.height= +a.size.width/a.aspectRatio}if(d+a.size.height>=a.parentData.height){a.size.height=a.parentData.height-d;if(b)a.size.width=a.size.height*a.aspectRatio}},stop:function(){var b=e(this).data("resizable"),a=b.options,c=b.containerOffset,d=b.containerPosition,f=b.containerElement,g=e(b.helper),h=g.offset(),i=g.outerWidth()-b.sizeDiff.width;g=g.outerHeight()-b.sizeDiff.height;b._helper&&!a.animate&&/relative/.test(f.css("position"))&&e(this).css({left:h.left-d.left-c.left,width:i,height:g});b._helper&&!a.animate&& +/static/.test(f.css("position"))&&e(this).css({left:h.left-d.left-c.left,width:i,height:g})}});e.ui.plugin.add("resizable","ghost",{start:function(){var b=e(this).data("resizable"),a=b.options,c=b.size;b.ghost=b.originalElement.clone();b.ghost.css({opacity:0.25,display:"block",position:"relative",height:c.height,width:c.width,margin:0,left:0,top:0}).addClass("ui-resizable-ghost").addClass(typeof a.ghost=="string"?a.ghost:"");b.ghost.appendTo(b.helper)},resize:function(){var b=e(this).data("resizable"); +b.ghost&&b.ghost.css({position:"relative",height:b.size.height,width:b.size.width})},stop:function(){var b=e(this).data("resizable");b.ghost&&b.helper&&b.helper.get(0).removeChild(b.ghost.get(0))}});e.ui.plugin.add("resizable","grid",{resize:function(){var b=e(this).data("resizable"),a=b.options,c=b.size,d=b.originalSize,f=b.originalPosition,g=b.axis;a.grid=typeof a.grid=="number"?[a.grid,a.grid]:a.grid;var h=Math.round((c.width-d.width)/(a.grid[0]||1))*(a.grid[0]||1);a=Math.round((c.height-d.height)/ +(a.grid[1]||1))*(a.grid[1]||1);if(/^(se|s|e)$/.test(g)){b.size.width=d.width+h;b.size.height=d.height+a}else if(/^(ne)$/.test(g)){b.size.width=d.width+h;b.size.height=d.height+a;b.position.top=f.top-a}else{if(/^(sw)$/.test(g)){b.size.width=d.width+h;b.size.height=d.height+a}else{b.size.width=d.width+h;b.size.height=d.height+a;b.position.top=f.top-a}b.position.left=f.left-h}}});var m=function(b){return parseInt(b,10)||0},l=function(b){return!isNaN(parseInt(b,10))}})(jQuery); +; \ No newline at end of file diff --git a/public/wymeditor/jquery/jquery.js b/public/wymeditor/jquery/jquery.js new file mode 100755 index 0000000..1097010 --- /dev/null +++ b/public/wymeditor/jquery/jquery.js @@ -0,0 +1,6078 @@ +/*! + * jQuery JavaScript Library v1.4.1 + * http://jquery.com/ + * + * Copyright 2010, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * Copyright 2010, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * + * Date: Mon Jan 25 19:43:33 2010 -0500 + */ +(function( window, undefined ) { + +// Define a local copy of jQuery +var jQuery = function( selector, context ) { + // The jQuery object is actually just the init constructor 'enhanced' + return new jQuery.fn.init( selector, context ); + }, + + // Map over jQuery in case of overwrite + _jQuery = window.jQuery, + + // Map over the $ in case of overwrite + _$ = window.$, + + // Use the correct document accordingly with window argument (sandbox) + document = window.document, + + // A central reference to the root jQuery(document) + rootjQuery, + + // A simple way to check for HTML strings or ID strings + // (both of which we optimize for) + quickExpr = /^[^<]*(<[\w\W]+>)[^>]*$|^#([\w-]+)$/, + + // Is it a simple selector + isSimple = /^.[^:#\[\.,]*$/, + + // Check if a string has a non-whitespace character in it + rnotwhite = /\S/, + + // Used for trimming whitespace + rtrim = /^(\s|\u00A0)+|(\s|\u00A0)+$/g, + + // Match a standalone tag + rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/, + + // Keep a UserAgent string for use with jQuery.browser + userAgent = navigator.userAgent, + + // For matching the engine and version of the browser + browserMatch, + + // Has the ready events already been bound? + readyBound = false, + + // The functions to execute on DOM ready + readyList = [], + + // The ready event handler + DOMContentLoaded, + + // Save a reference to some core methods + toString = Object.prototype.toString, + hasOwnProperty = Object.prototype.hasOwnProperty, + push = Array.prototype.push, + slice = Array.prototype.slice, + indexOf = Array.prototype.indexOf; + +jQuery.fn = jQuery.prototype = { + init: function( selector, context ) { + var match, elem, ret, doc; + + // Handle $(""), $(null), or $(undefined) + if ( !selector ) { + return this; + } + + // Handle $(DOMElement) + if ( selector.nodeType ) { + this.context = this[0] = selector; + this.length = 1; + return this; + } + + // Handle HTML strings + if ( typeof selector === "string" ) { + // Are we dealing with HTML string or an ID? + match = quickExpr.exec( selector ); + + // Verify a match, and that no context was specified for #id + if ( match && (match[1] || !context) ) { + + // HANDLE: $(html) -> $(array) + if ( match[1] ) { + doc = (context ? context.ownerDocument || context : document); + + // If a single string is passed in and it's a single tag + // just do a createElement and skip the rest + ret = rsingleTag.exec( selector ); + + if ( ret ) { + if ( jQuery.isPlainObject( context ) ) { + selector = [ document.createElement( ret[1] ) ]; + jQuery.fn.attr.call( selector, context, true ); + + } else { + selector = [ doc.createElement( ret[1] ) ]; + } + + } else { + ret = buildFragment( [ match[1] ], [ doc ] ); + selector = (ret.cacheable ? ret.fragment.cloneNode(true) : ret.fragment).childNodes; + } + + // HANDLE: $("#id") + } else { + elem = document.getElementById( match[2] ); + + if ( elem ) { + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem.id !== match[2] ) { + return rootjQuery.find( selector ); + } + + // Otherwise, we inject the element directly into the jQuery object + this.length = 1; + this[0] = elem; + } + + this.context = document; + this.selector = selector; + return this; + } + + // HANDLE: $("TAG") + } else if ( !context && /^\w+$/.test( selector ) ) { + this.selector = selector; + this.context = document; + selector = document.getElementsByTagName( selector ); + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return (context || rootjQuery).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return jQuery( context ).find( selector ); + } + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) { + return rootjQuery.ready( selector ); + } + + if (selector.selector !== undefined) { + this.selector = selector.selector; + this.context = selector.context; + } + + return jQuery.isArray( selector ) ? + this.setArray( selector ) : + jQuery.makeArray( selector, this ); + }, + + // Start with an empty selector + selector: "", + + // The current version of jQuery being used + jquery: "1.4.1", + + // The default length of a jQuery object is 0 + length: 0, + + // The number of elements contained in the matched element set + size: function() { + return this.length; + }, + + toArray: function() { + return slice.call( this, 0 ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + return num == null ? + + // Return a 'clean' array + this.toArray() : + + // Return just the object + ( num < 0 ? this.slice(num)[ 0 ] : this[ num ] ); + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems, name, selector ) { + // Build a new jQuery matched element set + var ret = jQuery( elems || null ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + ret.context = this.context; + + if ( name === "find" ) { + ret.selector = this.selector + (this.selector ? " " : "") + selector; + } else if ( name ) { + ret.selector = this.selector + "." + name + "(" + selector + ")"; + } + + // Return the newly-formed element set + return ret; + }, + + // Force the current matched set of elements to become + // the specified array of elements (destroying the stack in the process) + // You should use pushStack() in order to do this, but maintain the stack + setArray: function( elems ) { + // Resetting the length to 0, then using the native Array push + // is a super-fast way to populate an object with array-like properties + this.length = 0; + push.apply( this, elems ); + + return this; + }, + + // Execute a callback for every element in the matched set. + // (You can seed the arguments with an array of args, but this is + // only used internally.) + each: function( callback, args ) { + return jQuery.each( this, callback, args ); + }, + + ready: function( fn ) { + // Attach the listeners + jQuery.bindReady(); + + // If the DOM is already ready + if ( jQuery.isReady ) { + // Execute the function immediately + fn.call( document, jQuery ); + + // Otherwise, remember the function for later + } else if ( readyList ) { + // Add the function to the wait list + readyList.push( fn ); + } + + return this; + }, + + eq: function( i ) { + return i === -1 ? + this.slice( i ) : + this.slice( i, +i + 1 ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ), + "slice", slice.call(arguments).join(",") ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map(this, function( elem, i ) { + return callback.call( elem, i, elem ); + })); + }, + + end: function() { + return this.prevObject || jQuery(null); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: [].sort, + splice: [].splice +}; + +// Give the init function the jQuery prototype for later instantiation +jQuery.fn.init.prototype = jQuery.fn; + +jQuery.extend = jQuery.fn.extend = function() { + // copy reference to target object + var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options, name, src, copy; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + target = arguments[1] || {}; + // skip the boolean and the target + i = 2; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !jQuery.isFunction(target) ) { + target = {}; + } + + // extend jQuery itself if only one argument is passed + if ( length === i ) { + target = this; + --i; + } + + for ( ; i < length; i++ ) { + // Only deal with non-null/undefined values + if ( (options = arguments[ i ]) != null ) { + // Extend the base object + for ( name in options ) { + src = target[ name ]; + copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) { + continue; + } + + // Recurse if we're merging object literal values or arrays + if ( deep && copy && ( jQuery.isPlainObject(copy) || jQuery.isArray(copy) ) ) { + var clone = src && ( jQuery.isPlainObject(src) || jQuery.isArray(src) ) ? src + : jQuery.isArray(copy) ? [] : {}; + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend({ + noConflict: function( deep ) { + window.$ = _$; + + if ( deep ) { + window.jQuery = _jQuery; + } + + return jQuery; + }, + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // Handle when the DOM is ready + ready: function() { + // Make sure that the DOM is not already loaded + if ( !jQuery.isReady ) { + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( !document.body ) { + return setTimeout( jQuery.ready, 13 ); + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If there are functions bound, to execute + if ( readyList ) { + // Execute all of them + var fn, i = 0; + while ( (fn = readyList[ i++ ]) ) { + fn.call( document, jQuery ); + } + + // Reset the list of functions + readyList = null; + } + + // Trigger any bound ready events + if ( jQuery.fn.triggerHandler ) { + jQuery( document ).triggerHandler( "ready" ); + } + } + }, + + bindReady: function() { + if ( readyBound ) { + return; + } + + readyBound = true; + + // Catch cases where $(document).ready() is called after the + // browser event has already occurred. + if ( document.readyState === "complete" ) { + return jQuery.ready(); + } + + // Mozilla, Opera and webkit nightlies currently support this event + if ( document.addEventListener ) { + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", jQuery.ready, false ); + + // If IE event model is used + } else if ( document.attachEvent ) { + // ensure firing before onload, + // maybe late but safe also for iframes + document.attachEvent("onreadystatechange", DOMContentLoaded); + + // A fallback to window.onload, that will always work + window.attachEvent( "onload", jQuery.ready ); + + // If IE and not a frame + // continually check to see if the document is ready + var toplevel = false; + + try { + toplevel = window.frameElement == null; + } catch(e) {} + + if ( document.documentElement.doScroll && toplevel ) { + doScrollCheck(); + } + } + }, + + // See test/unit/core.js for details concerning isFunction. + // Since version 1.3, DOM methods and functions like alert + // aren't supported. They return false on IE (#2968). + isFunction: function( obj ) { + return toString.call(obj) === "[object Function]"; + }, + + isArray: function( obj ) { + return toString.call(obj) === "[object Array]"; + }, + + isPlainObject: function( obj ) { + // Must be an Object. + // Because of IE, we also have to check the presence of the constructor property. + // Make sure that DOM nodes and window objects don't pass through, as well + if ( !obj || toString.call(obj) !== "[object Object]" || obj.nodeType || obj.setInterval ) { + return false; + } + + // Not own constructor property must be Object + if ( obj.constructor + && !hasOwnProperty.call(obj, "constructor") + && !hasOwnProperty.call(obj.constructor.prototype, "isPrototypeOf") ) { + return false; + } + + // Own properties are enumerated firstly, so to speed up, + // if last one is own, then all properties are own. + + var key; + for ( key in obj ) {} + + return key === undefined || hasOwnProperty.call( obj, key ); + }, + + isEmptyObject: function( obj ) { + for ( var name in obj ) { + return false; + } + return true; + }, + + error: function( msg ) { + throw msg; + }, + + parseJSON: function( data ) { + if ( typeof data !== "string" || !data ) { + return null; + } + + // Make sure the incoming data is actual JSON + // Logic borrowed from http://json.org/json2.js + if ( /^[\],:{}\s]*$/.test(data.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, "@") + .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, "]") + .replace(/(?:^|:|,)(?:\s*\[)+/g, "")) ) { + + // Try to use the native JSON parser first + return window.JSON && window.JSON.parse ? + window.JSON.parse( data ) : + (new Function("return " + data))(); + + } else { + jQuery.error( "Invalid JSON: " + data ); + } + }, + + noop: function() {}, + + // Evalulates a script in a global context + globalEval: function( data ) { + if ( data && rnotwhite.test(data) ) { + // Inspired by code by Andrea Giammarchi + // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html + var head = document.getElementsByTagName("head")[0] || document.documentElement, + script = document.createElement("script"); + + script.type = "text/javascript"; + + if ( jQuery.support.scriptEval ) { + script.appendChild( document.createTextNode( data ) ); + } else { + script.text = data; + } + + // Use insertBefore instead of appendChild to circumvent an IE6 bug. + // This arises when a base node is used (#2709). + head.insertBefore( script, head.firstChild ); + head.removeChild( script ); + } + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase(); + }, + + // args is for internal usage only + each: function( object, callback, args ) { + var name, i = 0, + length = object.length, + isObj = length === undefined || jQuery.isFunction(object); + + if ( args ) { + if ( isObj ) { + for ( name in object ) { + if ( callback.apply( object[ name ], args ) === false ) { + break; + } + } + } else { + for ( ; i < length; ) { + if ( callback.apply( object[ i++ ], args ) === false ) { + break; + } + } + } + + // A special, fast, case for the most common use of each + } else { + if ( isObj ) { + for ( name in object ) { + if ( callback.call( object[ name ], name, object[ name ] ) === false ) { + break; + } + } + } else { + for ( var value = object[0]; + i < length && callback.call( value, i, value ) !== false; value = object[++i] ) {} + } + } + + return object; + }, + + trim: function( text ) { + return (text || "").replace( rtrim, "" ); + }, + + // results is for internal usage only + makeArray: function( array, results ) { + var ret = results || []; + + if ( array != null ) { + // The window, strings (and functions) also have 'length' + // The extra typeof function check is to prevent crashes + // in Safari 2 (See: #3039) + if ( array.length == null || typeof array === "string" || jQuery.isFunction(array) || (typeof array !== "function" && array.setInterval) ) { + push.call( ret, array ); + } else { + jQuery.merge( ret, array ); + } + } + + return ret; + }, + + inArray: function( elem, array ) { + if ( array.indexOf ) { + return array.indexOf( elem ); + } + + for ( var i = 0, length = array.length; i < length; i++ ) { + if ( array[ i ] === elem ) { + return i; + } + } + + return -1; + }, + + merge: function( first, second ) { + var i = first.length, j = 0; + + if ( typeof second.length === "number" ) { + for ( var l = second.length; j < l; j++ ) { + first[ i++ ] = second[ j ]; + } + } else { + while ( second[j] !== undefined ) { + first[ i++ ] = second[ j++ ]; + } + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, inv ) { + var ret = []; + + // Go through the array, only saving the items + // that pass the validator function + for ( var i = 0, length = elems.length; i < length; i++ ) { + if ( !inv !== !callback( elems[ i ], i ) ) { + ret.push( elems[ i ] ); + } + } + + return ret; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var ret = [], value; + + // Go through the array, translating each of the items to their + // new value (or values). + for ( var i = 0, length = elems.length; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret[ ret.length ] = value; + } + } + + return ret.concat.apply( [], ret ); + }, + + // A global GUID counter for objects + guid: 1, + + proxy: function( fn, proxy, thisObject ) { + if ( arguments.length === 2 ) { + if ( typeof proxy === "string" ) { + thisObject = fn; + fn = thisObject[ proxy ]; + proxy = undefined; + + } else if ( proxy && !jQuery.isFunction( proxy ) ) { + thisObject = proxy; + proxy = undefined; + } + } + + if ( !proxy && fn ) { + proxy = function() { + return fn.apply( thisObject || this, arguments ); + }; + } + + // Set the guid of unique handler to the same of original handler, so it can be removed + if ( fn ) { + proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; + } + + // So proxy can be declared as an argument + return proxy; + }, + + // Use of jQuery.browser is frowned upon. + // More details: http://docs.jquery.com/Utilities/jQuery.browser + uaMatch: function( ua ) { + ua = ua.toLowerCase(); + + var match = /(webkit)[ \/]([\w.]+)/.exec( ua ) || + /(opera)(?:.*version)?[ \/]([\w.]+)/.exec( ua ) || + /(msie) ([\w.]+)/.exec( ua ) || + !/compatible/.test( ua ) && /(mozilla)(?:.*? rv:([\w.]+))?/.exec( ua ) || + []; + + return { browser: match[1] || "", version: match[2] || "0" }; + }, + + browser: {} +}); + +browserMatch = jQuery.uaMatch( userAgent ); +if ( browserMatch.browser ) { + jQuery.browser[ browserMatch.browser ] = true; + jQuery.browser.version = browserMatch.version; +} + +// Deprecated, use jQuery.browser.webkit instead +if ( jQuery.browser.webkit ) { + jQuery.browser.safari = true; +} + +if ( indexOf ) { + jQuery.inArray = function( elem, array ) { + return indexOf.call( array, elem ); + }; +} + +// All jQuery objects should point back to these +rootjQuery = jQuery(document); + +// Cleanup functions for the document ready method +if ( document.addEventListener ) { + DOMContentLoaded = function() { + document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + jQuery.ready(); + }; + +} else if ( document.attachEvent ) { + DOMContentLoaded = function() { + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( document.readyState === "complete" ) { + document.detachEvent( "onreadystatechange", DOMContentLoaded ); + jQuery.ready(); + } + }; +} + +// The DOM ready check for Internet Explorer +function doScrollCheck() { + if ( jQuery.isReady ) { + return; + } + + try { + // If IE is used, use the trick by Diego Perini + // http://javascript.nwbox.com/IEContentLoaded/ + document.documentElement.doScroll("left"); + } catch( error ) { + setTimeout( doScrollCheck, 1 ); + return; + } + + // and execute any waiting functions + jQuery.ready(); +} + +function evalScript( i, elem ) { + if ( elem.src ) { + jQuery.ajax({ + url: elem.src, + async: false, + dataType: "script" + }); + } else { + jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" ); + } + + if ( elem.parentNode ) { + elem.parentNode.removeChild( elem ); + } +} + +// Mutifunctional method to get and set values to a collection +// The value/s can be optionally by executed if its a function +function access( elems, key, value, exec, fn, pass ) { + var length = elems.length; + + // Setting many attributes + if ( typeof key === "object" ) { + for ( var k in key ) { + access( elems, k, key[k], exec, fn, value ); + } + return elems; + } + + // Setting one attribute + if ( value !== undefined ) { + // Optionally, function values get executed if exec is true + exec = !pass && exec && jQuery.isFunction(value); + + for ( var i = 0; i < length; i++ ) { + fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass ); + } + + return elems; + } + + // Getting an attribute + return length ? fn( elems[0], key ) : null; +} + +function now() { + return (new Date).getTime(); +} +(function() { + + jQuery.support = {}; + + var root = document.documentElement, + script = document.createElement("script"), + div = document.createElement("div"), + id = "script" + now(); + + div.style.display = "none"; + div.innerHTML = "
    a"; + + var all = div.getElementsByTagName("*"), + a = div.getElementsByTagName("a")[0]; + + // Can't get basic test support + if ( !all || !all.length || !a ) { + return; + } + + jQuery.support = { + // IE strips leading whitespace when .innerHTML is used + leadingWhitespace: div.firstChild.nodeType === 3, + + // Make sure that tbody elements aren't automatically inserted + // IE will insert them into empty tables + tbody: !div.getElementsByTagName("tbody").length, + + // Make sure that link elements get serialized correctly by innerHTML + // This requires a wrapper element in IE + htmlSerialize: !!div.getElementsByTagName("link").length, + + // Get the style information from getAttribute + // (IE uses .cssText insted) + style: /red/.test( a.getAttribute("style") ), + + // Make sure that URLs aren't manipulated + // (IE normalizes it by default) + hrefNormalized: a.getAttribute("href") === "/a", + + // Make sure that element opacity exists + // (IE uses filter instead) + // Use a regex to work around a WebKit issue. See #5145 + opacity: /^0.55$/.test( a.style.opacity ), + + // Verify style float existence + // (IE uses styleFloat instead of cssFloat) + cssFloat: !!a.style.cssFloat, + + // Make sure that if no value is specified for a checkbox + // that it defaults to "on". + // (WebKit defaults to "" instead) + checkOn: div.getElementsByTagName("input")[0].value === "on", + + // Make sure that a selected-by-default option has a working selected property. + // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) + optSelected: document.createElement("select").appendChild( document.createElement("option") ).selected, + + // Will be defined later + checkClone: false, + scriptEval: false, + noCloneEvent: true, + boxModel: null + }; + + script.type = "text/javascript"; + try { + script.appendChild( document.createTextNode( "window." + id + "=1;" ) ); + } catch(e) {} + + root.insertBefore( script, root.firstChild ); + + // Make sure that the execution of code works by injecting a script + // tag with appendChild/createTextNode + // (IE doesn't support this, fails, and uses .text instead) + if ( window[ id ] ) { + jQuery.support.scriptEval = true; + delete window[ id ]; + } + + root.removeChild( script ); + + if ( div.attachEvent && div.fireEvent ) { + div.attachEvent("onclick", function click() { + // Cloning a node shouldn't copy over any + // bound event handlers (IE does this) + jQuery.support.noCloneEvent = false; + div.detachEvent("onclick", click); + }); + div.cloneNode(true).fireEvent("onclick"); + } + + div = document.createElement("div"); + div.innerHTML = ""; + + var fragment = document.createDocumentFragment(); + fragment.appendChild( div.firstChild ); + + // WebKit doesn't clone checked state correctly in fragments + jQuery.support.checkClone = fragment.cloneNode(true).cloneNode(true).lastChild.checked; + + // Figure out if the W3C box model works as expected + // document.body must exist before we can do this + jQuery(function() { + var div = document.createElement("div"); + div.style.width = div.style.paddingLeft = "1px"; + + document.body.appendChild( div ); + jQuery.boxModel = jQuery.support.boxModel = div.offsetWidth === 2; + document.body.removeChild( div ).style.display = 'none'; + div = null; + }); + + // Technique from Juriy Zaytsev + // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/ + var eventSupported = function( eventName ) { + var el = document.createElement("div"); + eventName = "on" + eventName; + + var isSupported = (eventName in el); + if ( !isSupported ) { + el.setAttribute(eventName, "return;"); + isSupported = typeof el[eventName] === "function"; + } + el = null; + + return isSupported; + }; + + jQuery.support.submitBubbles = eventSupported("submit"); + jQuery.support.changeBubbles = eventSupported("change"); + + // release memory in IE + root = script = div = all = a = null; +})(); + +jQuery.props = { + "for": "htmlFor", + "class": "className", + readonly: "readOnly", + maxlength: "maxLength", + cellspacing: "cellSpacing", + rowspan: "rowSpan", + colspan: "colSpan", + tabindex: "tabIndex", + usemap: "useMap", + frameborder: "frameBorder" +}; +var expando = "jQuery" + now(), uuid = 0, windowData = {}; +var emptyObject = {}; + +jQuery.extend({ + cache: {}, + + expando:expando, + + // The following elements throw uncatchable exceptions if you + // attempt to add expando properties to them. + noData: { + "embed": true, + "object": true, + "applet": true + }, + + data: function( elem, name, data ) { + if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) { + return; + } + + elem = elem == window ? + windowData : + elem; + + var id = elem[ expando ], cache = jQuery.cache, thisCache; + + // Handle the case where there's no name immediately + if ( !name && !id ) { + return null; + } + + // Compute a unique ID for the element + if ( !id ) { + id = ++uuid; + } + + // Avoid generating a new cache unless none exists and we + // want to manipulate it. + if ( typeof name === "object" ) { + elem[ expando ] = id; + thisCache = cache[ id ] = jQuery.extend(true, {}, name); + } else if ( cache[ id ] ) { + thisCache = cache[ id ]; + } else if ( typeof data === "undefined" ) { + thisCache = emptyObject; + } else { + thisCache = cache[ id ] = {}; + } + + // Prevent overriding the named cache with undefined values + if ( data !== undefined ) { + elem[ expando ] = id; + thisCache[ name ] = data; + } + + return typeof name === "string" ? thisCache[ name ] : thisCache; + }, + + removeData: function( elem, name ) { + if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) { + return; + } + + elem = elem == window ? + windowData : + elem; + + var id = elem[ expando ], cache = jQuery.cache, thisCache = cache[ id ]; + + // If we want to remove a specific section of the element's data + if ( name ) { + if ( thisCache ) { + // Remove the section of cache data + delete thisCache[ name ]; + + // If we've removed all the data, remove the element's cache + if ( jQuery.isEmptyObject(thisCache) ) { + jQuery.removeData( elem ); + } + } + + // Otherwise, we want to remove all of the element's data + } else { + // Clean up the element expando + try { + delete elem[ expando ]; + } catch( e ) { + // IE has trouble directly removing the expando + // but it's ok with using removeAttribute + if ( elem.removeAttribute ) { + elem.removeAttribute( expando ); + } + } + + // Completely remove the data cache + delete cache[ id ]; + } + } +}); + +jQuery.fn.extend({ + data: function( key, value ) { + if ( typeof key === "undefined" && this.length ) { + return jQuery.data( this[0] ); + + } else if ( typeof key === "object" ) { + return this.each(function() { + jQuery.data( this, key ); + }); + } + + var parts = key.split("."); + parts[1] = parts[1] ? "." + parts[1] : ""; + + if ( value === undefined ) { + var data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]); + + if ( data === undefined && this.length ) { + data = jQuery.data( this[0], key ); + } + return data === undefined && parts[1] ? + this.data( parts[0] ) : + data; + } else { + return this.trigger("setData" + parts[1] + "!", [parts[0], value]).each(function() { + jQuery.data( this, key, value ); + }); + } + }, + + removeData: function( key ) { + return this.each(function() { + jQuery.removeData( this, key ); + }); + } +}); +jQuery.extend({ + queue: function( elem, type, data ) { + if ( !elem ) { + return; + } + + type = (type || "fx") + "queue"; + var q = jQuery.data( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( !data ) { + return q || []; + } + + if ( !q || jQuery.isArray(data) ) { + q = jQuery.data( elem, type, jQuery.makeArray(data) ); + + } else { + q.push( data ); + } + + return q; + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), fn = queue.shift(); + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + } + + if ( fn ) { + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift("inprogress"); + } + + fn.call(elem, function() { + jQuery.dequeue(elem, type); + }); + } + } +}); + +jQuery.fn.extend({ + queue: function( type, data ) { + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + } + + if ( data === undefined ) { + return jQuery.queue( this[0], type ); + } + return this.each(function( i, elem ) { + var queue = jQuery.queue( this, type, data ); + + if ( type === "fx" && queue[0] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + }); + }, + dequeue: function( type ) { + return this.each(function() { + jQuery.dequeue( this, type ); + }); + }, + + // Based off of the plugin by Clint Helfers, with permission. + // http://blindsignals.com/index.php/2009/07/jquery-delay/ + delay: function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[time] || time : time; + type = type || "fx"; + + return this.queue( type, function() { + var elem = this; + setTimeout(function() { + jQuery.dequeue( elem, type ); + }, time ); + }); + }, + + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + } +}); +var rclass = /[\n\t]/g, + rspace = /\s+/, + rreturn = /\r/g, + rspecialurl = /href|src|style/, + rtype = /(button|input)/i, + rfocusable = /(button|input|object|select|textarea)/i, + rclickable = /^(a|area)$/i, + rradiocheck = /radio|checkbox/; + +jQuery.fn.extend({ + attr: function( name, value ) { + return access( this, name, value, true, jQuery.attr ); + }, + + removeAttr: function( name, fn ) { + return this.each(function(){ + jQuery.attr( this, name, "" ); + if ( this.nodeType === 1 ) { + this.removeAttribute( name ); + } + }); + }, + + addClass: function( value ) { + if ( jQuery.isFunction(value) ) { + return this.each(function(i) { + var self = jQuery(this); + self.addClass( value.call(this, i, self.attr("class")) ); + }); + } + + if ( value && typeof value === "string" ) { + var classNames = (value || "").split( rspace ); + + for ( var i = 0, l = this.length; i < l; i++ ) { + var elem = this[i]; + + if ( elem.nodeType === 1 ) { + if ( !elem.className ) { + elem.className = value; + + } else { + var className = " " + elem.className + " "; + for ( var c = 0, cl = classNames.length; c < cl; c++ ) { + if ( className.indexOf( " " + classNames[c] + " " ) < 0 ) { + elem.className += " " + classNames[c]; + } + } + } + } + } + } + + return this; + }, + + removeClass: function( value ) { + if ( jQuery.isFunction(value) ) { + return this.each(function(i) { + var self = jQuery(this); + self.removeClass( value.call(this, i, self.attr("class")) ); + }); + } + + if ( (value && typeof value === "string") || value === undefined ) { + var classNames = (value || "").split(rspace); + + for ( var i = 0, l = this.length; i < l; i++ ) { + var elem = this[i]; + + if ( elem.nodeType === 1 && elem.className ) { + if ( value ) { + var className = (" " + elem.className + " ").replace(rclass, " "); + for ( var c = 0, cl = classNames.length; c < cl; c++ ) { + className = className.replace(" " + classNames[c] + " ", " "); + } + elem.className = className.substring(1, className.length - 1); + + } else { + elem.className = ""; + } + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value, isBool = typeof stateVal === "boolean"; + + if ( jQuery.isFunction( value ) ) { + return this.each(function(i) { + var self = jQuery(this); + self.toggleClass( value.call(this, i, self.attr("class"), stateVal), stateVal ); + }); + } + + return this.each(function() { + if ( type === "string" ) { + // toggle individual class names + var className, i = 0, self = jQuery(this), + state = stateVal, + classNames = value.split( rspace ); + + while ( (className = classNames[ i++ ]) ) { + // check each className given, space seperated list + state = isBool ? state : !self.hasClass( className ); + self[ state ? "addClass" : "removeClass" ]( className ); + } + + } else if ( type === "undefined" || type === "boolean" ) { + if ( this.className ) { + // store className if set + jQuery.data( this, "__className__", this.className ); + } + + // toggle whole className + this.className = this.className || value === false ? "" : jQuery.data( this, "__className__" ) || ""; + } + }); + }, + + hasClass: function( selector ) { + var className = " " + selector + " "; + for ( var i = 0, l = this.length; i < l; i++ ) { + if ( (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) { + return true; + } + } + + return false; + }, + + val: function( value ) { + if ( value === undefined ) { + var elem = this[0]; + + if ( elem ) { + if ( jQuery.nodeName( elem, "option" ) ) { + return (elem.attributes.value || {}).specified ? elem.value : elem.text; + } + + // We need to handle select boxes special + if ( jQuery.nodeName( elem, "select" ) ) { + var index = elem.selectedIndex, + values = [], + options = elem.options, + one = elem.type === "select-one"; + + // Nothing was selected + if ( index < 0 ) { + return null; + } + + // Loop through all the selected options + for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) { + var option = options[ i ]; + + if ( option.selected ) { + // Get the specifc value for the option + value = jQuery(option).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + } + + // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified + if ( rradiocheck.test( elem.type ) && !jQuery.support.checkOn ) { + return elem.getAttribute("value") === null ? "on" : elem.value; + } + + + // Everything else, we just grab the value + return (elem.value || "").replace(rreturn, ""); + + } + + return undefined; + } + + var isFunction = jQuery.isFunction(value); + + return this.each(function(i) { + var self = jQuery(this), val = value; + + if ( this.nodeType !== 1 ) { + return; + } + + if ( isFunction ) { + val = value.call(this, i, self.val()); + } + + // Typecast each time if the value is a Function and the appended + // value is therefore different each time. + if ( typeof val === "number" ) { + val += ""; + } + + if ( jQuery.isArray(val) && rradiocheck.test( this.type ) ) { + this.checked = jQuery.inArray( self.val(), val ) >= 0; + + } else if ( jQuery.nodeName( this, "select" ) ) { + var values = jQuery.makeArray(val); + + jQuery( "option", this ).each(function() { + this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0; + }); + + if ( !values.length ) { + this.selectedIndex = -1; + } + + } else { + this.value = val; + } + }); + } +}); + +jQuery.extend({ + attrFn: { + val: true, + css: true, + html: true, + text: true, + data: true, + width: true, + height: true, + offset: true + }, + + attr: function( elem, name, value, pass ) { + // don't set attributes on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) { + return undefined; + } + + if ( pass && name in jQuery.attrFn ) { + return jQuery(elem)[name](value); + } + + var notxml = elem.nodeType !== 1 || !jQuery.isXMLDoc( elem ), + // Whether we are setting (or getting) + set = value !== undefined; + + // Try to normalize/fix the name + name = notxml && jQuery.props[ name ] || name; + + // Only do all the following if this is a node (faster for style) + if ( elem.nodeType === 1 ) { + // These attributes require special treatment + var special = rspecialurl.test( name ); + + // Safari mis-reports the default selected property of an option + // Accessing the parent's selectedIndex property fixes it + if ( name === "selected" && !jQuery.support.optSelected ) { + var parent = elem.parentNode; + if ( parent ) { + parent.selectedIndex; + + // Make sure that it also works with optgroups, see #5701 + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + } + + // If applicable, access the attribute via the DOM 0 way + if ( name in elem && notxml && !special ) { + if ( set ) { + // We can't allow the type property to be changed (since it causes problems in IE) + if ( name === "type" && rtype.test( elem.nodeName ) && elem.parentNode ) { + jQuery.error( "type property can't be changed" ); + } + + elem[ name ] = value; + } + + // browsers index elements by id/name on forms, give priority to attributes. + if ( jQuery.nodeName( elem, "form" ) && elem.getAttributeNode(name) ) { + return elem.getAttributeNode( name ).nodeValue; + } + + // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set + // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + if ( name === "tabIndex" ) { + var attributeNode = elem.getAttributeNode( "tabIndex" ); + + return attributeNode && attributeNode.specified ? + attributeNode.value : + rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? + 0 : + undefined; + } + + return elem[ name ]; + } + + if ( !jQuery.support.style && notxml && name === "style" ) { + if ( set ) { + elem.style.cssText = "" + value; + } + + return elem.style.cssText; + } + + if ( set ) { + // convert the value to a string (all browsers do this but IE) see #1070 + elem.setAttribute( name, "" + value ); + } + + var attr = !jQuery.support.hrefNormalized && notxml && special ? + // Some attributes require a special call on IE + elem.getAttribute( name, 2 ) : + elem.getAttribute( name ); + + // Non-existent attributes return null, we normalize to undefined + return attr === null ? undefined : attr; + } + + // elem is actually elem.style ... set the style + // Using attr for specific style information is now deprecated. Use style insead. + return jQuery.style( elem, name, value ); + } +}); +var fcleanup = function( nm ) { + return nm.replace(/[^\w\s\.\|`]/g, function( ch ) { + return "\\" + ch; + }); +}; + +/* + * A number of helper functions used for managing events. + * Many of the ideas behind this code originated from + * Dean Edwards' addEvent library. + */ +jQuery.event = { + + // Bind an event to an element + // Original by Dean Edwards + add: function( elem, types, handler, data ) { + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // For whatever reason, IE has trouble passing the window object + // around, causing it to be cloned in the process + if ( elem.setInterval && ( elem !== window && !elem.frameElement ) ) { + elem = window; + } + + // Make sure that the function being executed has a unique ID + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // if data is passed, bind to handler + if ( data !== undefined ) { + // Create temporary function pointer to original handler + var fn = handler; + + // Create unique handler function, wrapped around original handler + handler = jQuery.proxy( fn ); + + // Store data in unique handler + handler.data = data; + } + + // Init the element's event structure + var events = jQuery.data( elem, "events" ) || jQuery.data( elem, "events", {} ), + handle = jQuery.data( elem, "handle" ), eventHandle; + + if ( !handle ) { + eventHandle = function() { + // Handle the second event of a trigger and when + // an event is called after a page has unloaded + return typeof jQuery !== "undefined" && !jQuery.event.triggered ? + jQuery.event.handle.apply( eventHandle.elem, arguments ) : + undefined; + }; + + handle = jQuery.data( elem, "handle", eventHandle ); + } + + // If no handle is found then we must be trying to bind to one of the + // banned noData elements + if ( !handle ) { + return; + } + + // Add elem as a property of the handle function + // This is to prevent a memory leak with non-native + // event in IE. + handle.elem = elem; + + // Handle multiple events separated by a space + // jQuery(...).bind("mouseover mouseout", fn); + types = types.split( /\s+/ ); + + var type, i = 0; + + while ( (type = types[ i++ ]) ) { + // Namespaced event handlers + var namespaces = type.split("."); + type = namespaces.shift(); + + if ( i > 1 ) { + handler = jQuery.proxy( handler ); + + if ( data !== undefined ) { + handler.data = data; + } + } + + handler.type = namespaces.slice(0).sort().join("."); + + // Get the current list of functions bound to this event + var handlers = events[ type ], + special = this.special[ type ] || {}; + + // Init the event handler queue + if ( !handlers ) { + handlers = events[ type ] = {}; + + // Check for a special event handler + // Only use addEventListener/attachEvent if the special + // events handler returns false + if ( !special.setup || special.setup.call( elem, data, namespaces, handler) === false ) { + // Bind the global event handler to the element + if ( elem.addEventListener ) { + elem.addEventListener( type, handle, false ); + } else if ( elem.attachEvent ) { + elem.attachEvent( "on" + type, handle ); + } + } + } + + if ( special.add ) { + var modifiedHandler = special.add.call( elem, handler, data, namespaces, handlers ); + if ( modifiedHandler && jQuery.isFunction( modifiedHandler ) ) { + modifiedHandler.guid = modifiedHandler.guid || handler.guid; + modifiedHandler.data = modifiedHandler.data || handler.data; + modifiedHandler.type = modifiedHandler.type || handler.type; + handler = modifiedHandler; + } + } + + // Add the function to the element's handler list + handlers[ handler.guid ] = handler; + + // Keep track of which events have been used, for global triggering + this.global[ type ] = true; + } + + // Nullify elem to prevent memory leaks in IE + elem = null; + }, + + global: {}, + + // Detach an event or set of events from an element + remove: function( elem, types, handler ) { + // don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + var events = jQuery.data( elem, "events" ), ret, type, fn; + + if ( events ) { + // Unbind all events for the element + if ( types === undefined || (typeof types === "string" && types.charAt(0) === ".") ) { + for ( type in events ) { + this.remove( elem, type + (types || "") ); + } + } else { + // types is actually an event object here + if ( types.type ) { + handler = types.handler; + types = types.type; + } + + // Handle multiple events separated by a space + // jQuery(...).unbind("mouseover mouseout", fn); + types = types.split(/\s+/); + var i = 0; + while ( (type = types[ i++ ]) ) { + // Namespaced event handlers + var namespaces = type.split("."); + type = namespaces.shift(); + var all = !namespaces.length, + cleaned = jQuery.map( namespaces.slice(0).sort(), fcleanup ), + namespace = new RegExp("(^|\\.)" + cleaned.join("\\.(?:.*\\.)?") + "(\\.|$)"), + special = this.special[ type ] || {}; + + if ( events[ type ] ) { + // remove the given handler for the given type + if ( handler ) { + fn = events[ type ][ handler.guid ]; + delete events[ type ][ handler.guid ]; + + // remove all handlers for the given type + } else { + for ( var handle in events[ type ] ) { + // Handle the removal of namespaced events + if ( all || namespace.test( events[ type ][ handle ].type ) ) { + delete events[ type ][ handle ]; + } + } + } + + if ( special.remove ) { + special.remove.call( elem, namespaces, fn); + } + + // remove generic event handler if no more handlers exist + for ( ret in events[ type ] ) { + break; + } + if ( !ret ) { + if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) { + if ( elem.removeEventListener ) { + elem.removeEventListener( type, jQuery.data( elem, "handle" ), false ); + } else if ( elem.detachEvent ) { + elem.detachEvent( "on" + type, jQuery.data( elem, "handle" ) ); + } + } + ret = null; + delete events[ type ]; + } + } + } + } + + // Remove the expando if it's no longer used + for ( ret in events ) { + break; + } + if ( !ret ) { + var handle = jQuery.data( elem, "handle" ); + if ( handle ) { + handle.elem = null; + } + jQuery.removeData( elem, "events" ); + jQuery.removeData( elem, "handle" ); + } + } + }, + + // bubbling is internal + trigger: function( event, data, elem /*, bubbling */ ) { + // Event object or event type + var type = event.type || event, + bubbling = arguments[3]; + + if ( !bubbling ) { + event = typeof event === "object" ? + // jQuery.Event object + event[expando] ? event : + // Object literal + jQuery.extend( jQuery.Event(type), event ) : + // Just the event type (string) + jQuery.Event(type); + + if ( type.indexOf("!") >= 0 ) { + event.type = type = type.slice(0, -1); + event.exclusive = true; + } + + // Handle a global trigger + if ( !elem ) { + // Don't bubble custom events when global (to avoid too much overhead) + event.stopPropagation(); + + // Only trigger if we've ever bound an event for it + if ( this.global[ type ] ) { + jQuery.each( jQuery.cache, function() { + if ( this.events && this.events[type] ) { + jQuery.event.trigger( event, data, this.handle.elem ); + } + }); + } + } + + // Handle triggering a single element + + // don't do events on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) { + return undefined; + } + + // Clean up in case it is reused + event.result = undefined; + event.target = elem; + + // Clone the incoming data, if any + data = jQuery.makeArray( data ); + data.unshift( event ); + } + + event.currentTarget = elem; + + // Trigger the event, it is assumed that "handle" is a function + var handle = jQuery.data( elem, "handle" ); + if ( handle ) { + handle.apply( elem, data ); + } + + var parent = elem.parentNode || elem.ownerDocument; + + // Trigger an inline bound script + try { + if ( !(elem && elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()]) ) { + if ( elem[ "on" + type ] && elem[ "on" + type ].apply( elem, data ) === false ) { + event.result = false; + } + } + + // prevent IE from throwing an error for some elements with some event types, see #3533 + } catch (e) {} + + if ( !event.isPropagationStopped() && parent ) { + jQuery.event.trigger( event, data, parent, true ); + + } else if ( !event.isDefaultPrevented() ) { + var target = event.target, old, + isClick = jQuery.nodeName(target, "a") && type === "click"; + + if ( !isClick && !(target && target.nodeName && jQuery.noData[target.nodeName.toLowerCase()]) ) { + try { + if ( target[ type ] ) { + // Make sure that we don't accidentally re-trigger the onFOO events + old = target[ "on" + type ]; + + if ( old ) { + target[ "on" + type ] = null; + } + + this.triggered = true; + target[ type ](); + } + + // prevent IE from throwing an error for some elements with some event types, see #3533 + } catch (e) {} + + if ( old ) { + target[ "on" + type ] = old; + } + + this.triggered = false; + } + } + }, + + handle: function( event ) { + // returned undefined or false + var all, handlers; + + event = arguments[0] = jQuery.event.fix( event || window.event ); + event.currentTarget = this; + + // Namespaced event handlers + var namespaces = event.type.split("."); + event.type = namespaces.shift(); + + // Cache this now, all = true means, any handler + all = !namespaces.length && !event.exclusive; + + var namespace = new RegExp("(^|\\.)" + namespaces.slice(0).sort().join("\\.(?:.*\\.)?") + "(\\.|$)"); + + handlers = ( jQuery.data(this, "events") || {} )[ event.type ]; + + for ( var j in handlers ) { + var handler = handlers[ j ]; + + // Filter the functions by class + if ( all || namespace.test(handler.type) ) { + // Pass in a reference to the handler function itself + // So that we can later remove it + event.handler = handler; + event.data = handler.data; + + var ret = handler.apply( this, arguments ); + + if ( ret !== undefined ) { + event.result = ret; + if ( ret === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + + if ( event.isImmediatePropagationStopped() ) { + break; + } + + } + } + + return event.result; + }, + + props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "), + + fix: function( event ) { + if ( event[ expando ] ) { + return event; + } + + // store a copy of the original event object + // and "clone" to set read-only properties + var originalEvent = event; + event = jQuery.Event( originalEvent ); + + for ( var i = this.props.length, prop; i; ) { + prop = this.props[ --i ]; + event[ prop ] = originalEvent[ prop ]; + } + + // Fix target property, if necessary + if ( !event.target ) { + event.target = event.srcElement || document; // Fixes #1925 where srcElement might not be defined either + } + + // check if target is a textnode (safari) + if ( event.target.nodeType === 3 ) { + event.target = event.target.parentNode; + } + + // Add relatedTarget, if necessary + if ( !event.relatedTarget && event.fromElement ) { + event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement; + } + + // Calculate pageX/Y if missing and clientX/Y available + if ( event.pageX == null && event.clientX != null ) { + var doc = document.documentElement, body = document.body; + event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0); + event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0); + } + + // Add which for key events + if ( !event.which && ((event.charCode || event.charCode === 0) ? event.charCode : event.keyCode) ) { + event.which = event.charCode || event.keyCode; + } + + // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs) + if ( !event.metaKey && event.ctrlKey ) { + event.metaKey = event.ctrlKey; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + // Note: button is not normalized, so don't use it + if ( !event.which && event.button !== undefined ) { + event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) )); + } + + return event; + }, + + // Deprecated, use jQuery.guid instead + guid: 1E8, + + // Deprecated, use jQuery.proxy instead + proxy: jQuery.proxy, + + special: { + ready: { + // Make sure the ready event is setup + setup: jQuery.bindReady, + teardown: jQuery.noop + }, + + live: { + add: function( proxy, data, namespaces, live ) { + jQuery.extend( proxy, data || {} ); + + proxy.guid += data.selector + data.live; + data.liveProxy = proxy; + + jQuery.event.add( this, data.live, liveHandler, data ); + + }, + + remove: function( namespaces ) { + if ( namespaces.length ) { + var remove = 0, name = new RegExp("(^|\\.)" + namespaces[0] + "(\\.|$)"); + + jQuery.each( (jQuery.data(this, "events").live || {}), function() { + if ( name.test(this.type) ) { + remove++; + } + }); + + if ( remove < 1 ) { + jQuery.event.remove( this, namespaces[0], liveHandler ); + } + } + }, + special: {} + }, + beforeunload: { + setup: function( data, namespaces, fn ) { + // We only want to do this special case on windows + if ( this.setInterval ) { + this.onbeforeunload = fn; + } + + return false; + }, + teardown: function( namespaces, fn ) { + if ( this.onbeforeunload === fn ) { + this.onbeforeunload = null; + } + } + } + } +}; + +jQuery.Event = function( src ) { + // Allow instantiation without the 'new' keyword + if ( !this.preventDefault ) { + return new jQuery.Event( src ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + // Event type + } else { + this.type = src; + } + + // timeStamp is buggy for some events on Firefox(#3843) + // So we won't rely on the native value + this.timeStamp = now(); + + // Mark it as fixed + this[ expando ] = true; +}; + +function returnFalse() { + return false; +} +function returnTrue() { + return true; +} + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + preventDefault: function() { + this.isDefaultPrevented = returnTrue; + + var e = this.originalEvent; + if ( !e ) { + return; + } + + // if preventDefault exists run it on the original event + if ( e.preventDefault ) { + e.preventDefault(); + } + // otherwise set the returnValue property of the original event to false (IE) + e.returnValue = false; + }, + stopPropagation: function() { + this.isPropagationStopped = returnTrue; + + var e = this.originalEvent; + if ( !e ) { + return; + } + // if stopPropagation exists run it on the original event + if ( e.stopPropagation ) { + e.stopPropagation(); + } + // otherwise set the cancelBubble property of the original event to true (IE) + e.cancelBubble = true; + }, + stopImmediatePropagation: function() { + this.isImmediatePropagationStopped = returnTrue; + this.stopPropagation(); + }, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse +}; + +// Checks if an event happened on an element within another element +// Used in jQuery.event.special.mouseenter and mouseleave handlers +var withinElement = function( event ) { + // Check if mouse(over|out) are still within the same parent element + var parent = event.relatedTarget; + + // Traverse up the tree + while ( parent && parent !== this ) { + // Firefox sometimes assigns relatedTarget a XUL element + // which we cannot access the parentNode property of + try { + parent = parent.parentNode; + + // assuming we've left the element since we most likely mousedover a xul element + } catch(e) { + break; + } + } + + if ( parent !== this ) { + // set the correct event type + event.type = event.data; + + // handle event if we actually just moused on to a non sub-element + jQuery.event.handle.apply( this, arguments ); + } + +}, + +// In case of event delegation, we only need to rename the event.type, +// liveHandler will take care of the rest. +delegate = function( event ) { + event.type = event.data; + jQuery.event.handle.apply( this, arguments ); +}; + +// Create mouseenter and mouseleave events +jQuery.each({ + mouseenter: "mouseover", + mouseleave: "mouseout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + setup: function( data ) { + jQuery.event.add( this, fix, data && data.selector ? delegate : withinElement, orig ); + }, + teardown: function( data ) { + jQuery.event.remove( this, fix, data && data.selector ? delegate : withinElement ); + } + }; +}); + +// submit delegation +if ( !jQuery.support.submitBubbles ) { + +jQuery.event.special.submit = { + setup: function( data, namespaces, fn ) { + if ( this.nodeName.toLowerCase() !== "form" ) { + jQuery.event.add(this, "click.specialSubmit." + fn.guid, function( e ) { + var elem = e.target, type = elem.type; + + if ( (type === "submit" || type === "image") && jQuery( elem ).closest("form").length ) { + return trigger( "submit", this, arguments ); + } + }); + + jQuery.event.add(this, "keypress.specialSubmit." + fn.guid, function( e ) { + var elem = e.target, type = elem.type; + + if ( (type === "text" || type === "password") && jQuery( elem ).closest("form").length && e.keyCode === 13 ) { + return trigger( "submit", this, arguments ); + } + }); + + } else { + return false; + } + }, + + remove: function( namespaces, fn ) { + jQuery.event.remove( this, "click.specialSubmit" + (fn ? "."+fn.guid : "") ); + jQuery.event.remove( this, "keypress.specialSubmit" + (fn ? "."+fn.guid : "") ); + } +}; + +} + +// change delegation, happens here so we have bind. +if ( !jQuery.support.changeBubbles ) { + +var formElems = /textarea|input|select/i; + +function getVal( elem ) { + var type = elem.type, val = elem.value; + + if ( type === "radio" || type === "checkbox" ) { + val = elem.checked; + + } else if ( type === "select-multiple" ) { + val = elem.selectedIndex > -1 ? + jQuery.map( elem.options, function( elem ) { + return elem.selected; + }).join("-") : + ""; + + } else if ( elem.nodeName.toLowerCase() === "select" ) { + val = elem.selectedIndex; + } + + return val; +} + +function testChange( e ) { + var elem = e.target, data, val; + + if ( !formElems.test( elem.nodeName ) || elem.readOnly ) { + return; + } + + data = jQuery.data( elem, "_change_data" ); + val = getVal(elem); + + // the current data will be also retrieved by beforeactivate + if ( e.type !== "focusout" || elem.type !== "radio" ) { + jQuery.data( elem, "_change_data", val ); + } + + if ( data === undefined || val === data ) { + return; + } + + if ( data != null || val ) { + e.type = "change"; + return jQuery.event.trigger( e, arguments[1], elem ); + } +} + +jQuery.event.special.change = { + filters: { + focusout: testChange, + + click: function( e ) { + var elem = e.target, type = elem.type; + + if ( type === "radio" || type === "checkbox" || elem.nodeName.toLowerCase() === "select" ) { + return testChange.call( this, e ); + } + }, + + // Change has to be called before submit + // Keydown will be called before keypress, which is used in submit-event delegation + keydown: function( e ) { + var elem = e.target, type = elem.type; + + if ( (e.keyCode === 13 && elem.nodeName.toLowerCase() !== "textarea") || + (e.keyCode === 32 && (type === "checkbox" || type === "radio")) || + type === "select-multiple" ) { + return testChange.call( this, e ); + } + }, + + // Beforeactivate happens also before the previous element is blurred + // with this event you can't trigger a change event, but you can store + // information/focus[in] is not needed anymore + beforeactivate: function( e ) { + var elem = e.target; + + if ( elem.nodeName.toLowerCase() === "input" && elem.type === "radio" ) { + jQuery.data( elem, "_change_data", getVal(elem) ); + } + } + }, + setup: function( data, namespaces, fn ) { + for ( var type in changeFilters ) { + jQuery.event.add( this, type + ".specialChange." + fn.guid, changeFilters[type] ); + } + + return formElems.test( this.nodeName ); + }, + remove: function( namespaces, fn ) { + for ( var type in changeFilters ) { + jQuery.event.remove( this, type + ".specialChange" + (fn ? "."+fn.guid : ""), changeFilters[type] ); + } + + return formElems.test( this.nodeName ); + } +}; + +var changeFilters = jQuery.event.special.change.filters; + +} + +function trigger( type, elem, args ) { + args[0].type = type; + return jQuery.event.handle.apply( elem, args ); +} + +// Create "bubbling" focus and blur events +if ( document.addEventListener ) { + jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { + jQuery.event.special[ fix ] = { + setup: function() { + this.addEventListener( orig, handler, true ); + }, + teardown: function() { + this.removeEventListener( orig, handler, true ); + } + }; + + function handler( e ) { + e = jQuery.event.fix( e ); + e.type = fix; + return jQuery.event.handle.call( this, e ); + } + }); +} + +jQuery.each(["bind", "one"], function( i, name ) { + jQuery.fn[ name ] = function( type, data, fn ) { + // Handle object literals + if ( typeof type === "object" ) { + for ( var key in type ) { + this[ name ](key, data, type[key], fn); + } + return this; + } + + if ( jQuery.isFunction( data ) ) { + fn = data; + data = undefined; + } + + var handler = name === "one" ? jQuery.proxy( fn, function( event ) { + jQuery( this ).unbind( event, handler ); + return fn.apply( this, arguments ); + }) : fn; + + return type === "unload" && name !== "one" ? + this.one( type, data, fn ) : + this.each(function() { + jQuery.event.add( this, type, handler, data ); + }); + }; +}); + +jQuery.fn.extend({ + unbind: function( type, fn ) { + // Handle object literals + if ( typeof type === "object" && !type.preventDefault ) { + for ( var key in type ) { + this.unbind(key, type[key]); + } + return this; + } + + return this.each(function() { + jQuery.event.remove( this, type, fn ); + }); + }, + trigger: function( type, data ) { + return this.each(function() { + jQuery.event.trigger( type, data, this ); + }); + }, + + triggerHandler: function( type, data ) { + if ( this[0] ) { + var event = jQuery.Event( type ); + event.preventDefault(); + event.stopPropagation(); + jQuery.event.trigger( event, data, this[0] ); + return event.result; + } + }, + + toggle: function( fn ) { + // Save reference to arguments for access in closure + var args = arguments, i = 1; + + // link all the functions, so any of them can unbind this click handler + while ( i < args.length ) { + jQuery.proxy( fn, args[ i++ ] ); + } + + return this.click( jQuery.proxy( fn, function( event ) { + // Figure out which function to execute + var lastToggle = ( jQuery.data( this, "lastToggle" + fn.guid ) || 0 ) % i; + jQuery.data( this, "lastToggle" + fn.guid, lastToggle + 1 ); + + // Make sure that clicks stop + event.preventDefault(); + + // and execute the function + return args[ lastToggle ].apply( this, arguments ) || false; + })); + }, + + hover: function( fnOver, fnOut ) { + return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); + } +}); + +jQuery.each(["live", "die"], function( i, name ) { + jQuery.fn[ name ] = function( types, data, fn ) { + var type, i = 0; + + if ( jQuery.isFunction( data ) ) { + fn = data; + data = undefined; + } + + types = (types || "").split( /\s+/ ); + + while ( (type = types[ i++ ]) != null ) { + type = type === "focus" ? "focusin" : // focus --> focusin + type === "blur" ? "focusout" : // blur --> focusout + type === "hover" ? types.push("mouseleave") && "mouseenter" : // hover support + type; + + if ( name === "live" ) { + // bind live handler + jQuery( this.context ).bind( liveConvert( type, this.selector ), { + data: data, selector: this.selector, live: type + }, fn ); + + } else { + // unbind live handler + jQuery( this.context ).unbind( liveConvert( type, this.selector ), fn ? { guid: fn.guid + this.selector + type } : null ); + } + } + + return this; + } +}); + +function liveHandler( event ) { + var stop, elems = [], selectors = [], args = arguments, + related, match, fn, elem, j, i, l, data, + live = jQuery.extend({}, jQuery.data( this, "events" ).live); + + // Make sure we avoid non-left-click bubbling in Firefox (#3861) + if ( event.button && event.type === "click" ) { + return; + } + + for ( j in live ) { + fn = live[j]; + if ( fn.live === event.type || + fn.altLive && jQuery.inArray(event.type, fn.altLive) > -1 ) { + + data = fn.data; + if ( !(data.beforeFilter && data.beforeFilter[event.type] && + !data.beforeFilter[event.type](event)) ) { + selectors.push( fn.selector ); + } + } else { + delete live[j]; + } + } + + match = jQuery( event.target ).closest( selectors, event.currentTarget ); + + for ( i = 0, l = match.length; i < l; i++ ) { + for ( j in live ) { + fn = live[j]; + elem = match[i].elem; + related = null; + + if ( match[i].selector === fn.selector ) { + // Those two events require additional checking + if ( fn.live === "mouseenter" || fn.live === "mouseleave" ) { + related = jQuery( event.relatedTarget ).closest( fn.selector )[0]; + } + + if ( !related || related !== elem ) { + elems.push({ elem: elem, fn: fn }); + } + } + } + } + + for ( i = 0, l = elems.length; i < l; i++ ) { + match = elems[i]; + event.currentTarget = match.elem; + event.data = match.fn.data; + if ( match.fn.apply( match.elem, args ) === false ) { + stop = false; + break; + } + } + + return stop; +} + +function liveConvert( type, selector ) { + return "live." + (type ? type + "." : "") + selector.replace(/\./g, "`").replace(/ /g, "&"); +} + +jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " + + "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + + "change select submit keydown keypress keyup error").split(" "), function( i, name ) { + + // Handle event binding + jQuery.fn[ name ] = function( fn ) { + return fn ? this.bind( name, fn ) : this.trigger( name ); + }; + + if ( jQuery.attrFn ) { + jQuery.attrFn[ name ] = true; + } +}); + +// Prevent memory leaks in IE +// Window isn't included so as not to unbind existing unload events +// More info: +// - http://isaacschlueter.com/2006/10/msie-memory-leaks/ +if ( window.attachEvent && !window.addEventListener ) { + window.attachEvent("onunload", function() { + for ( var id in jQuery.cache ) { + if ( jQuery.cache[ id ].handle ) { + // Try/Catch is to handle iframes being unloaded, see #4280 + try { + jQuery.event.remove( jQuery.cache[ id ].handle.elem ); + } catch(e) {} + } + } + }); +} +/*! + * Sizzle CSS Selector Engine - v1.0 + * Copyright 2009, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * More information: http://sizzlejs.com/ + */ +(function(){ + +var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, + done = 0, + toString = Object.prototype.toString, + hasDuplicate = false, + baseHasDuplicate = true; + +// Here we check if the JavaScript engine is using some sort of +// optimization where it does not always call our comparision +// function. If that is the case, discard the hasDuplicate value. +// Thus far that includes Google Chrome. +[0, 0].sort(function(){ + baseHasDuplicate = false; + return 0; +}); + +var Sizzle = function(selector, context, results, seed) { + results = results || []; + var origContext = context = context || document; + + if ( context.nodeType !== 1 && context.nodeType !== 9 ) { + return []; + } + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + var parts = [], m, set, checkSet, extra, prune = true, contextXML = isXML(context), + soFar = selector; + + // Reset the position of the chunker regexp (start from head) + while ( (chunker.exec(""), m = chunker.exec(soFar)) !== null ) { + soFar = m[3]; + + parts.push( m[1] ); + + if ( m[2] ) { + extra = m[3]; + break; + } + } + + if ( parts.length > 1 && origPOS.exec( selector ) ) { + if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { + set = posProcess( parts[0] + parts[1], context ); + } else { + set = Expr.relative[ parts[0] ] ? + [ context ] : + Sizzle( parts.shift(), context ); + + while ( parts.length ) { + selector = parts.shift(); + + if ( Expr.relative[ selector ] ) { + selector += parts.shift(); + } + + set = posProcess( selector, set ); + } + } + } else { + // Take a shortcut and set the context if the root selector is an ID + // (but not if it'll be faster if the inner selector is an ID) + if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && + Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { + var ret = Sizzle.find( parts.shift(), context, contextXML ); + context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0]; + } + + if ( context ) { + var ret = seed ? + { expr: parts.pop(), set: makeArray(seed) } : + Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); + set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set; + + if ( parts.length > 0 ) { + checkSet = makeArray(set); + } else { + prune = false; + } + + while ( parts.length ) { + var cur = parts.pop(), pop = cur; + + if ( !Expr.relative[ cur ] ) { + cur = ""; + } else { + pop = parts.pop(); + } + + if ( pop == null ) { + pop = context; + } + + Expr.relative[ cur ]( checkSet, pop, contextXML ); + } + } else { + checkSet = parts = []; + } + } + + if ( !checkSet ) { + checkSet = set; + } + + if ( !checkSet ) { + Sizzle.error( cur || selector ); + } + + if ( toString.call(checkSet) === "[object Array]" ) { + if ( !prune ) { + results.push.apply( results, checkSet ); + } else if ( context && context.nodeType === 1 ) { + for ( var i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && contains(context, checkSet[i])) ) { + results.push( set[i] ); + } + } + } else { + for ( var i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && checkSet[i].nodeType === 1 ) { + results.push( set[i] ); + } + } + } + } else { + makeArray( checkSet, results ); + } + + if ( extra ) { + Sizzle( extra, origContext, results, seed ); + Sizzle.uniqueSort( results ); + } + + return results; +}; + +Sizzle.uniqueSort = function(results){ + if ( sortOrder ) { + hasDuplicate = baseHasDuplicate; + results.sort(sortOrder); + + if ( hasDuplicate ) { + for ( var i = 1; i < results.length; i++ ) { + if ( results[i] === results[i-1] ) { + results.splice(i--, 1); + } + } + } + } + + return results; +}; + +Sizzle.matches = function(expr, set){ + return Sizzle(expr, null, null, set); +}; + +Sizzle.find = function(expr, context, isXML){ + var set, match; + + if ( !expr ) { + return []; + } + + for ( var i = 0, l = Expr.order.length; i < l; i++ ) { + var type = Expr.order[i], match; + + if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { + var left = match[1]; + match.splice(1,1); + + if ( left.substr( left.length - 1 ) !== "\\" ) { + match[1] = (match[1] || "").replace(/\\/g, ""); + set = Expr.find[ type ]( match, context, isXML ); + if ( set != null ) { + expr = expr.replace( Expr.match[ type ], "" ); + break; + } + } + } + } + + if ( !set ) { + set = context.getElementsByTagName("*"); + } + + return {set: set, expr: expr}; +}; + +Sizzle.filter = function(expr, set, inplace, not){ + var old = expr, result = [], curLoop = set, match, anyFound, + isXMLFilter = set && set[0] && isXML(set[0]); + + while ( expr && set.length ) { + for ( var type in Expr.filter ) { + if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { + var filter = Expr.filter[ type ], found, item, left = match[1]; + anyFound = false; + + match.splice(1,1); + + if ( left.substr( left.length - 1 ) === "\\" ) { + continue; + } + + if ( curLoop === result ) { + result = []; + } + + if ( Expr.preFilter[ type ] ) { + match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); + + if ( !match ) { + anyFound = found = true; + } else if ( match === true ) { + continue; + } + } + + if ( match ) { + for ( var i = 0; (item = curLoop[i]) != null; i++ ) { + if ( item ) { + found = filter( item, match, i, curLoop ); + var pass = not ^ !!found; + + if ( inplace && found != null ) { + if ( pass ) { + anyFound = true; + } else { + curLoop[i] = false; + } + } else if ( pass ) { + result.push( item ); + anyFound = true; + } + } + } + } + + if ( found !== undefined ) { + if ( !inplace ) { + curLoop = result; + } + + expr = expr.replace( Expr.match[ type ], "" ); + + if ( !anyFound ) { + return []; + } + + break; + } + } + } + + // Improper expression + if ( expr === old ) { + if ( anyFound == null ) { + Sizzle.error( expr ); + } else { + break; + } + } + + old = expr; + } + + return curLoop; +}; + +Sizzle.error = function( msg ) { + throw "Syntax error, unrecognized expression: " + msg; +}; + +var Expr = Sizzle.selectors = { + order: [ "ID", "NAME", "TAG" ], + match: { + ID: /#((?:[\w\u00c0-\uFFFF-]|\\.)+)/, + CLASS: /\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/, + NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/, + ATTR: /\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/, + TAG: /^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/, + CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/, + POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/, + PSEUDO: /:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ + }, + leftMatch: {}, + attrMap: { + "class": "className", + "for": "htmlFor" + }, + attrHandle: { + href: function(elem){ + return elem.getAttribute("href"); + } + }, + relative: { + "+": function(checkSet, part){ + var isPartStr = typeof part === "string", + isTag = isPartStr && !/\W/.test(part), + isPartStrNotTag = isPartStr && !isTag; + + if ( isTag ) { + part = part.toLowerCase(); + } + + for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { + if ( (elem = checkSet[i]) ) { + while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} + + checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? + elem || false : + elem === part; + } + } + + if ( isPartStrNotTag ) { + Sizzle.filter( part, checkSet, true ); + } + }, + ">": function(checkSet, part){ + var isPartStr = typeof part === "string"; + + if ( isPartStr && !/\W/.test(part) ) { + part = part.toLowerCase(); + + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + var parent = elem.parentNode; + checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; + } + } + } else { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + checkSet[i] = isPartStr ? + elem.parentNode : + elem.parentNode === part; + } + } + + if ( isPartStr ) { + Sizzle.filter( part, checkSet, true ); + } + } + }, + "": function(checkSet, part, isXML){ + var doneName = done++, checkFn = dirCheck; + + if ( typeof part === "string" && !/\W/.test(part) ) { + var nodeCheck = part = part.toLowerCase(); + checkFn = dirNodeCheck; + } + + checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML); + }, + "~": function(checkSet, part, isXML){ + var doneName = done++, checkFn = dirCheck; + + if ( typeof part === "string" && !/\W/.test(part) ) { + var nodeCheck = part = part.toLowerCase(); + checkFn = dirNodeCheck; + } + + checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML); + } + }, + find: { + ID: function(match, context, isXML){ + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + return m ? [m] : []; + } + }, + NAME: function(match, context){ + if ( typeof context.getElementsByName !== "undefined" ) { + var ret = [], results = context.getElementsByName(match[1]); + + for ( var i = 0, l = results.length; i < l; i++ ) { + if ( results[i].getAttribute("name") === match[1] ) { + ret.push( results[i] ); + } + } + + return ret.length === 0 ? null : ret; + } + }, + TAG: function(match, context){ + return context.getElementsByTagName(match[1]); + } + }, + preFilter: { + CLASS: function(match, curLoop, inplace, result, not, isXML){ + match = " " + match[1].replace(/\\/g, "") + " "; + + if ( isXML ) { + return match; + } + + for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { + if ( elem ) { + if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n]/g, " ").indexOf(match) >= 0) ) { + if ( !inplace ) { + result.push( elem ); + } + } else if ( inplace ) { + curLoop[i] = false; + } + } + } + + return false; + }, + ID: function(match){ + return match[1].replace(/\\/g, ""); + }, + TAG: function(match, curLoop){ + return match[1].toLowerCase(); + }, + CHILD: function(match){ + if ( match[1] === "nth" ) { + // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' + var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec( + match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || + !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); + + // calculate the numbers (first)n+(last) including if they are negative + match[2] = (test[1] + (test[2] || 1)) - 0; + match[3] = test[3] - 0; + } + + // TODO: Move to normal caching system + match[0] = done++; + + return match; + }, + ATTR: function(match, curLoop, inplace, result, not, isXML){ + var name = match[1].replace(/\\/g, ""); + + if ( !isXML && Expr.attrMap[name] ) { + match[1] = Expr.attrMap[name]; + } + + if ( match[2] === "~=" ) { + match[4] = " " + match[4] + " "; + } + + return match; + }, + PSEUDO: function(match, curLoop, inplace, result, not){ + if ( match[1] === "not" ) { + // If we're dealing with a complex expression, or a simple one + if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { + match[3] = Sizzle(match[3], null, null, curLoop); + } else { + var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); + if ( !inplace ) { + result.push.apply( result, ret ); + } + return false; + } + } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { + return true; + } + + return match; + }, + POS: function(match){ + match.unshift( true ); + return match; + } + }, + filters: { + enabled: function(elem){ + return elem.disabled === false && elem.type !== "hidden"; + }, + disabled: function(elem){ + return elem.disabled === true; + }, + checked: function(elem){ + return elem.checked === true; + }, + selected: function(elem){ + // Accessing this property makes selected-by-default + // options in Safari work properly + elem.parentNode.selectedIndex; + return elem.selected === true; + }, + parent: function(elem){ + return !!elem.firstChild; + }, + empty: function(elem){ + return !elem.firstChild; + }, + has: function(elem, i, match){ + return !!Sizzle( match[3], elem ).length; + }, + header: function(elem){ + return /h\d/i.test( elem.nodeName ); + }, + text: function(elem){ + return "text" === elem.type; + }, + radio: function(elem){ + return "radio" === elem.type; + }, + checkbox: function(elem){ + return "checkbox" === elem.type; + }, + file: function(elem){ + return "file" === elem.type; + }, + password: function(elem){ + return "password" === elem.type; + }, + submit: function(elem){ + return "submit" === elem.type; + }, + image: function(elem){ + return "image" === elem.type; + }, + reset: function(elem){ + return "reset" === elem.type; + }, + button: function(elem){ + return "button" === elem.type || elem.nodeName.toLowerCase() === "button"; + }, + input: function(elem){ + return /input|select|textarea|button/i.test(elem.nodeName); + } + }, + setFilters: { + first: function(elem, i){ + return i === 0; + }, + last: function(elem, i, match, array){ + return i === array.length - 1; + }, + even: function(elem, i){ + return i % 2 === 0; + }, + odd: function(elem, i){ + return i % 2 === 1; + }, + lt: function(elem, i, match){ + return i < match[3] - 0; + }, + gt: function(elem, i, match){ + return i > match[3] - 0; + }, + nth: function(elem, i, match){ + return match[3] - 0 === i; + }, + eq: function(elem, i, match){ + return match[3] - 0 === i; + } + }, + filter: { + PSEUDO: function(elem, match, i, array){ + var name = match[1], filter = Expr.filters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } else if ( name === "contains" ) { + return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0; + } else if ( name === "not" ) { + var not = match[3]; + + for ( var i = 0, l = not.length; i < l; i++ ) { + if ( not[i] === elem ) { + return false; + } + } + + return true; + } else { + Sizzle.error( "Syntax error, unrecognized expression: " + name ); + } + }, + CHILD: function(elem, match){ + var type = match[1], node = elem; + switch (type) { + case 'only': + case 'first': + while ( (node = node.previousSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + if ( type === "first" ) { + return true; + } + node = elem; + case 'last': + while ( (node = node.nextSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + return true; + case 'nth': + var first = match[2], last = match[3]; + + if ( first === 1 && last === 0 ) { + return true; + } + + var doneName = match[0], + parent = elem.parentNode; + + if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) { + var count = 0; + for ( node = parent.firstChild; node; node = node.nextSibling ) { + if ( node.nodeType === 1 ) { + node.nodeIndex = ++count; + } + } + parent.sizcache = doneName; + } + + var diff = elem.nodeIndex - last; + if ( first === 0 ) { + return diff === 0; + } else { + return ( diff % first === 0 && diff / first >= 0 ); + } + } + }, + ID: function(elem, match){ + return elem.nodeType === 1 && elem.getAttribute("id") === match; + }, + TAG: function(elem, match){ + return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match; + }, + CLASS: function(elem, match){ + return (" " + (elem.className || elem.getAttribute("class")) + " ") + .indexOf( match ) > -1; + }, + ATTR: function(elem, match){ + var name = match[1], + result = Expr.attrHandle[ name ] ? + Expr.attrHandle[ name ]( elem ) : + elem[ name ] != null ? + elem[ name ] : + elem.getAttribute( name ), + value = result + "", + type = match[2], + check = match[4]; + + return result == null ? + type === "!=" : + type === "=" ? + value === check : + type === "*=" ? + value.indexOf(check) >= 0 : + type === "~=" ? + (" " + value + " ").indexOf(check) >= 0 : + !check ? + value && result !== false : + type === "!=" ? + value !== check : + type === "^=" ? + value.indexOf(check) === 0 : + type === "$=" ? + value.substr(value.length - check.length) === check : + type === "|=" ? + value === check || value.substr(0, check.length + 1) === check + "-" : + false; + }, + POS: function(elem, match, i, array){ + var name = match[2], filter = Expr.setFilters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } + } + } +}; + +var origPOS = Expr.match.POS; + +for ( var type in Expr.match ) { + Expr.match[ type ] = new RegExp( Expr.match[ type ].source + /(?![^\[]*\])(?![^\(]*\))/.source ); + Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, function(all, num){ + return "\\" + (num - 0 + 1); + })); +} + +var makeArray = function(array, results) { + array = Array.prototype.slice.call( array, 0 ); + + if ( results ) { + results.push.apply( results, array ); + return results; + } + + return array; +}; + +// Perform a simple check to determine if the browser is capable of +// converting a NodeList to an array using builtin methods. +try { + Array.prototype.slice.call( document.documentElement.childNodes, 0 ); + +// Provide a fallback method if it does not work +} catch(e){ + makeArray = function(array, results) { + var ret = results || []; + + if ( toString.call(array) === "[object Array]" ) { + Array.prototype.push.apply( ret, array ); + } else { + if ( typeof array.length === "number" ) { + for ( var i = 0, l = array.length; i < l; i++ ) { + ret.push( array[i] ); + } + } else { + for ( var i = 0; array[i]; i++ ) { + ret.push( array[i] ); + } + } + } + + return ret; + }; +} + +var sortOrder; + +if ( document.documentElement.compareDocumentPosition ) { + sortOrder = function( a, b ) { + if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { + if ( a == b ) { + hasDuplicate = true; + } + return a.compareDocumentPosition ? -1 : 1; + } + + var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1; + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} else if ( "sourceIndex" in document.documentElement ) { + sortOrder = function( a, b ) { + if ( !a.sourceIndex || !b.sourceIndex ) { + if ( a == b ) { + hasDuplicate = true; + } + return a.sourceIndex ? -1 : 1; + } + + var ret = a.sourceIndex - b.sourceIndex; + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} else if ( document.createRange ) { + sortOrder = function( a, b ) { + if ( !a.ownerDocument || !b.ownerDocument ) { + if ( a == b ) { + hasDuplicate = true; + } + return a.ownerDocument ? -1 : 1; + } + + var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange(); + aRange.setStart(a, 0); + aRange.setEnd(a, 0); + bRange.setStart(b, 0); + bRange.setEnd(b, 0); + var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange); + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} + +// Utility function for retreiving the text value of an array of DOM nodes +function getText( elems ) { + var ret = "", elem; + + for ( var i = 0; elems[i]; i++ ) { + elem = elems[i]; + + // Get the text from text nodes and CDATA nodes + if ( elem.nodeType === 3 || elem.nodeType === 4 ) { + ret += elem.nodeValue; + + // Traverse everything else, except comment nodes + } else if ( elem.nodeType !== 8 ) { + ret += getText( elem.childNodes ); + } + } + + return ret; +} + +// Check to see if the browser returns elements by name when +// querying by getElementById (and provide a workaround) +(function(){ + // We're going to inject a fake input element with a specified name + var form = document.createElement("div"), + id = "script" + (new Date).getTime(); + form.innerHTML = ""; + + // Inject it into the root element, check its status, and remove it quickly + var root = document.documentElement; + root.insertBefore( form, root.firstChild ); + + // The workaround has to do additional checks after a getElementById + // Which slows things down for other browsers (hence the branching) + if ( document.getElementById( id ) ) { + Expr.find.ID = function(match, context, isXML){ + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : []; + } + }; + + Expr.filter.ID = function(elem, match){ + var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); + return elem.nodeType === 1 && node && node.nodeValue === match; + }; + } + + root.removeChild( form ); + root = form = null; // release memory in IE +})(); + +(function(){ + // Check to see if the browser returns only elements + // when doing getElementsByTagName("*") + + // Create a fake element + var div = document.createElement("div"); + div.appendChild( document.createComment("") ); + + // Make sure no comments are found + if ( div.getElementsByTagName("*").length > 0 ) { + Expr.find.TAG = function(match, context){ + var results = context.getElementsByTagName(match[1]); + + // Filter out possible comments + if ( match[1] === "*" ) { + var tmp = []; + + for ( var i = 0; results[i]; i++ ) { + if ( results[i].nodeType === 1 ) { + tmp.push( results[i] ); + } + } + + results = tmp; + } + + return results; + }; + } + + // Check to see if an attribute returns normalized href attributes + div.innerHTML = ""; + if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && + div.firstChild.getAttribute("href") !== "#" ) { + Expr.attrHandle.href = function(elem){ + return elem.getAttribute("href", 2); + }; + } + + div = null; // release memory in IE +})(); + +if ( document.querySelectorAll ) { + (function(){ + var oldSizzle = Sizzle, div = document.createElement("div"); + div.innerHTML = "

    "; + + // Safari can't handle uppercase or unicode characters when + // in quirks mode. + if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { + return; + } + + Sizzle = function(query, context, extra, seed){ + context = context || document; + + // Only use querySelectorAll on non-XML documents + // (ID selectors don't work in non-HTML documents) + if ( !seed && context.nodeType === 9 && !isXML(context) ) { + try { + return makeArray( context.querySelectorAll(query), extra ); + } catch(e){} + } + + return oldSizzle(query, context, extra, seed); + }; + + for ( var prop in oldSizzle ) { + Sizzle[ prop ] = oldSizzle[ prop ]; + } + + div = null; // release memory in IE + })(); +} + +(function(){ + var div = document.createElement("div"); + + div.innerHTML = "
    "; + + // Opera can't find a second classname (in 9.6) + // Also, make sure that getElementsByClassName actually exists + if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) { + return; + } + + // Safari caches class attributes, doesn't catch changes (in 3.2) + div.lastChild.className = "e"; + + if ( div.getElementsByClassName("e").length === 1 ) { + return; + } + + Expr.order.splice(1, 0, "CLASS"); + Expr.find.CLASS = function(match, context, isXML) { + if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { + return context.getElementsByClassName(match[1]); + } + }; + + div = null; // release memory in IE +})(); + +function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + elem = elem[dir]; + var match = false; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 && !isXML ){ + elem.sizcache = doneName; + elem.sizset = i; + } + + if ( elem.nodeName.toLowerCase() === cur ) { + match = elem; + break; + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + elem = elem[dir]; + var match = false; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 ) { + if ( !isXML ) { + elem.sizcache = doneName; + elem.sizset = i; + } + if ( typeof cur !== "string" ) { + if ( elem === cur ) { + match = true; + break; + } + + } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { + match = elem; + break; + } + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +var contains = document.compareDocumentPosition ? function(a, b){ + return a.compareDocumentPosition(b) & 16; +} : function(a, b){ + return a !== b && (a.contains ? a.contains(b) : true); +}; + +var isXML = function(elem){ + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; + return documentElement ? documentElement.nodeName !== "HTML" : false; +}; + +var posProcess = function(selector, context){ + var tmpSet = [], later = "", match, + root = context.nodeType ? [context] : context; + + // Position selectors must be done after the filter + // And so must :not(positional) so we move all PSEUDOs to the end + while ( (match = Expr.match.PSEUDO.exec( selector )) ) { + later += match[0]; + selector = selector.replace( Expr.match.PSEUDO, "" ); + } + + selector = Expr.relative[selector] ? selector + "*" : selector; + + for ( var i = 0, l = root.length; i < l; i++ ) { + Sizzle( selector, root[i], tmpSet ); + } + + return Sizzle.filter( later, tmpSet ); +}; + +// EXPOSE +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; +jQuery.expr[":"] = jQuery.expr.filters; +jQuery.unique = Sizzle.uniqueSort; +jQuery.getText = getText; +jQuery.isXMLDoc = isXML; +jQuery.contains = contains; + +return; + +window.Sizzle = Sizzle; + +})(); +var runtil = /Until$/, + rparentsprev = /^(?:parents|prevUntil|prevAll)/, + // Note: This RegExp should be improved, or likely pulled from Sizzle + rmultiselector = /,/, + slice = Array.prototype.slice; + +// Implement the identical functionality for filter and not +var winnow = function( elements, qualifier, keep ) { + if ( jQuery.isFunction( qualifier ) ) { + return jQuery.grep(elements, function( elem, i ) { + return !!qualifier.call( elem, i, elem ) === keep; + }); + + } else if ( qualifier.nodeType ) { + return jQuery.grep(elements, function( elem, i ) { + return (elem === qualifier) === keep; + }); + + } else if ( typeof qualifier === "string" ) { + var filtered = jQuery.grep(elements, function( elem ) { + return elem.nodeType === 1; + }); + + if ( isSimple.test( qualifier ) ) { + return jQuery.filter(qualifier, filtered, !keep); + } else { + qualifier = jQuery.filter( qualifier, filtered ); + } + } + + return jQuery.grep(elements, function( elem, i ) { + return (jQuery.inArray( elem, qualifier ) >= 0) === keep; + }); +}; + +jQuery.fn.extend({ + find: function( selector ) { + var ret = this.pushStack( "", "find", selector ), length = 0; + + for ( var i = 0, l = this.length; i < l; i++ ) { + length = ret.length; + jQuery.find( selector, this[i], ret ); + + if ( i > 0 ) { + // Make sure that the results are unique + for ( var n = length; n < ret.length; n++ ) { + for ( var r = 0; r < length; r++ ) { + if ( ret[r] === ret[n] ) { + ret.splice(n--, 1); + break; + } + } + } + } + } + + return ret; + }, + + has: function( target ) { + var targets = jQuery( target ); + return this.filter(function() { + for ( var i = 0, l = targets.length; i < l; i++ ) { + if ( jQuery.contains( this, targets[i] ) ) { + return true; + } + } + }); + }, + + not: function( selector ) { + return this.pushStack( winnow(this, selector, false), "not", selector); + }, + + filter: function( selector ) { + return this.pushStack( winnow(this, selector, true), "filter", selector ); + }, + + is: function( selector ) { + return !!selector && jQuery.filter( selector, this ).length > 0; + }, + + closest: function( selectors, context ) { + if ( jQuery.isArray( selectors ) ) { + var ret = [], cur = this[0], match, matches = {}, selector; + + if ( cur && selectors.length ) { + for ( var i = 0, l = selectors.length; i < l; i++ ) { + selector = selectors[i]; + + if ( !matches[selector] ) { + matches[selector] = jQuery.expr.match.POS.test( selector ) ? + jQuery( selector, context || this.context ) : + selector; + } + } + + while ( cur && cur.ownerDocument && cur !== context ) { + for ( selector in matches ) { + match = matches[selector]; + + if ( match.jquery ? match.index(cur) > -1 : jQuery(cur).is(match) ) { + ret.push({ selector: selector, elem: cur }); + delete matches[selector]; + } + } + cur = cur.parentNode; + } + } + + return ret; + } + + var pos = jQuery.expr.match.POS.test( selectors ) ? + jQuery( selectors, context || this.context ) : null; + + return this.map(function( i, cur ) { + while ( cur && cur.ownerDocument && cur !== context ) { + if ( pos ? pos.index(cur) > -1 : jQuery(cur).is(selectors) ) { + return cur; + } + cur = cur.parentNode; + } + return null; + }); + }, + + // Determine the position of an element within + // the matched set of elements + index: function( elem ) { + if ( !elem || typeof elem === "string" ) { + return jQuery.inArray( this[0], + // If it receives a string, the selector is used + // If it receives nothing, the siblings are used + elem ? jQuery( elem ) : this.parent().children() ); + } + // Locate the position of the desired element + return jQuery.inArray( + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[0] : elem, this ); + }, + + add: function( selector, context ) { + var set = typeof selector === "string" ? + jQuery( selector, context || this.context ) : + jQuery.makeArray( selector ), + all = jQuery.merge( this.get(), set ); + + return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ? + all : + jQuery.unique( all ) ); + }, + + andSelf: function() { + return this.add( this.prevObject ); + } +}); + +// A painfully simple check to see if an element is disconnected +// from a document (should be improved, where feasible). +function isDisconnected( node ) { + return !node || !node.parentNode || node.parentNode.nodeType === 11; +} + +jQuery.each({ + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return jQuery.dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, i, until ) { + return jQuery.dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return jQuery.nth( elem, 2, "nextSibling" ); + }, + prev: function( elem ) { + return jQuery.nth( elem, 2, "previousSibling" ); + }, + nextAll: function( elem ) { + return jQuery.dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return jQuery.dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, i, until ) { + return jQuery.dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, i, until ) { + return jQuery.dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return jQuery.sibling( elem.parentNode.firstChild, elem ); + }, + children: function( elem ) { + return jQuery.sibling( elem.firstChild ); + }, + contents: function( elem ) { + return jQuery.nodeName( elem, "iframe" ) ? + elem.contentDocument || elem.contentWindow.document : + jQuery.makeArray( elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var ret = jQuery.map( this, fn, until ); + + if ( !runtil.test( name ) ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + ret = jQuery.filter( selector, ret ); + } + + ret = this.length > 1 ? jQuery.unique( ret ) : ret; + + if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) { + ret = ret.reverse(); + } + + return this.pushStack( ret, name, slice.call(arguments).join(",") ); + }; +}); + +jQuery.extend({ + filter: function( expr, elems, not ) { + if ( not ) { + expr = ":not(" + expr + ")"; + } + + return jQuery.find.matches(expr, elems); + }, + + dir: function( elem, dir, until ) { + var matched = [], cur = elem[dir]; + while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { + if ( cur.nodeType === 1 ) { + matched.push( cur ); + } + cur = cur[dir]; + } + return matched; + }, + + nth: function( cur, result, dir, elem ) { + result = result || 1; + var num = 0; + + for ( ; cur; cur = cur[dir] ) { + if ( cur.nodeType === 1 && ++num === result ) { + break; + } + } + + return cur; + }, + + sibling: function( n, elem ) { + var r = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + r.push( n ); + } + } + + return r; + } +}); +var rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g, + rleadingWhitespace = /^\s+/, + rxhtmlTag = /(<([\w:]+)[^>]*?)\/>/g, + rselfClosing = /^(?:area|br|col|embed|hr|img|input|link|meta|param)$/i, + rtagName = /<([\w:]+)/, + rtbody = /"; + }, + wrapMap = { + option: [ 1, "" ], + legend: [ 1, "
    ", "
    " ], + thead: [ 1, "", "
    " ], + tr: [ 2, "", "
    " ], + td: [ 3, "", "
    " ], + col: [ 2, "", "
    " ], + area: [ 1, "", "" ], + _default: [ 0, "", "" ] + }; + +wrapMap.optgroup = wrapMap.option; +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +// IE can't serialize and ' + + '' + + '' + + WYMeditor.DIALOG_BODY + + '', + + dialogLinkHtml: String() + + '' + + '
    ' + + '
    ' + + '' + + '{Link}' + + '
    ' + + '' + + '' + + '
    ' + + '
    ' + + '' + + '' + + '
    ' + + '
    ' + + '' + + '' + + '
    ' + + '
    ' + + '' + + '' + + '
    ' + + '
    ' + + '
    ' + + '', + + dialogImageHtml: String() + + '' + + '
    ' + + '
    ' + + '' + + '{Image}' + + '
    ' + + '' + + '' + + '
    ' + + '
    ' + + '' + + '' + + '
    ' + + '
    ' + + '' + + '' + + '
    ' + + '
    ' + + '' + + '' + + '
    ' + + '
    ' + + '
    ' + + '', + + dialogTableHtml: String() + + '' + + '
    ' + + '
    ' + + '' + + '{Table}' + + '
    ' + + '' + + '' + + '
    ' + + '
    ' + + '' + + '' + + '
    ' + + '
    ' + + '' + + '' + + '
    ' + + '
    ' + + '' + + '' + + '
    ' + + '
    ' + + '' + + '' + + '
    ' + + '
    ' + + '
    ' + + '', + + dialogPasteHtml: String() + + '' + + '
    ' + + '' + + '
    ' + + '{Paste_From_Word}' + + '
    ' + + '' + + '
    ' + + '
    ' + + '' + + '' + + '
    ' + + '
    ' + + '
    ' + + '', + + dialogPreviewHtml: String() + + '', + + dialogStyles: [], + + stringDelimiterLeft: "{", + stringDelimiterRight: "}", + + preInit: null, + preBind: null, + postInit: null, + + preInitDialog: null, + postInitDialog: null + + }, options); + + return this.each(function () { + // Assigning to _editor because the return value from new isn't + // actually used, but we need to use new to properly change the + // prototype + var _editor = new WYMeditor.editor(jQuery(this), options); + }); +}; + +// Enable accessing of wymeditor instances via jQuery.wymeditors +jQuery.extend({ + wymeditors: function (i) { + return WYMeditor.INSTANCES[i]; + } +}); + +/** + WYMeditor.computeWymPath + ======================== + + Get the relative path to the WYMeditor core js file for usage as + a src attribute for script inclusion. + + Looks for script tags on the current page and finds the first matching + src attribute matching any of these values: + * jquery.wymeditor.pack.js + * jquery.wymeditor.min.js + * jquery.wymeditor.packed.js + * jquery.wymeditor.js + * /core.js +*/ +WYMeditor.computeWymPath = function () { + var script = jQuery( + jQuery.grep( + jQuery('script'), + function (s) { + if (!s.src) { + return null; + } + return ( + s.src.match( + /jquery\.wymeditor(\.pack|\.min|\.packed)?\.js(\?.*)?$/ + ) || + s.src.match( + /\/core\.js(\?.*)?$/ + ) + ); + } + ) + ); + if (script.length > 0) { + return script.attr('src'); + } + // We couldn't locate the base path. This will break language loading, + // dialog boxes and other features. + WYMeditor.console.warn( + "Error determining wymPath. No base WYMeditor file located." + ); + WYMeditor.console.warn("Assuming wymPath to be the current URL"); + WYMeditor.console.warn("Please pass a correct wymPath option"); + + // Guess that the wymPath is the current directory + return ''; +}; + +/** + WYMeditor.computeBasePath + ========================= + + Get the relative path to the WYMeditor directory root based on the path to + the wymeditor base file. This path is used as the basis for loading: + * Language files + * Skins + * +*/ +WYMeditor.computeBasePath = function (wymPath) { + // Strip everything after the last slash to get the base path + var lastSlashIndex = wymPath.lastIndexOf('/'); + return wymPath.substr(0, lastSlashIndex + 1); +}; + +/** + WYMeditor.computeJqueryPath + =========================== + + Get the relative path to the currently-included jquery javascript file. + + Returns the first script src attribute that matches one of the following + patterns: + + * jquery.pack.js + * jquery.min.js + * jquery.packed.js + * Plus the jquery- variants +*/ +WYMeditor.computeJqueryPath = function () { + return jQuery( + jQuery.grep( + jQuery('script'), + function (s) { + return ( + s.src && + s.src.match( + /jquery(-(.*)){0,1}(\.pack|\.min|\.packed)?\.js(\?.*)?$/ + ) + ); + } + ) + ).attr('src'); +}; + +/********** DIALOGS **********/ + +WYMeditor.INIT_DIALOG = function (index) { + + var wym = window.opener.WYMeditor.INSTANCES[index], + doc = window.document, + selected = wym.selected(), + dialogType = jQuery(wym._options.dialogTypeSelector).val(), + sStamp = wym.uniqueStamp(), + styles, + aCss, + tableOnClick; + + if (dialogType === WYMeditor.DIALOG_LINK) { + // ensure that we select the link to populate the fields + if (selected && selected.tagName && + selected.tagName.toLowerCase !== WYMeditor.A) { + selected = jQuery(selected).parentsOrSelf(WYMeditor.A); + } + + // fix MSIE selection if link image has been clicked + if (!selected && wym._selected_image) { + selected = jQuery(wym._selected_image).parentsOrSelf(WYMeditor.A); + } + } + + // pre-init functions + if (jQuery.isFunction(wym._options.preInitDialog)) { + wym._options.preInitDialog(wym, window); + } + + // add css rules from options + styles = doc.styleSheets[0]; + aCss = eval(wym._options.dialogStyles); + + wym.addCssRules(doc, aCss); + + // auto populate fields if selected container (e.g. A) + if (selected) { + jQuery(wym._options.hrefSelector).val(jQuery(selected).attr(WYMeditor.HREF)); + jQuery(wym._options.srcSelector).val(jQuery(selected).attr(WYMeditor.SRC)); + jQuery(wym._options.titleSelector).val(jQuery(selected).attr(WYMeditor.TITLE)); + jQuery(wym._options.relSelector).val(jQuery(selected).attr(WYMeditor.REL)); + jQuery(wym._options.altSelector).val(jQuery(selected).attr(WYMeditor.ALT)); + } + + // auto populate image fields if selected image + if (wym._selected_image) { + jQuery(wym._options.dialogImageSelector + " " + wym._options.srcSelector).val(jQuery(wym._selected_image).attr(WYMeditor.SRC)); + jQuery(wym._options.dialogImageSelector + " " + wym._options.titleSelector).val(jQuery(wym._selected_image).attr(WYMeditor.TITLE)); + jQuery(wym._options.dialogImageSelector + " " + wym._options.altSelector).val(jQuery(wym._selected_image).attr(WYMeditor.ALT)); + } + + jQuery(wym._options.dialogLinkSelector + " " + + wym._options.submitSelector).submit(function () { + + var sUrl = jQuery(wym._options.hrefSelector).val(), + link; + if (sUrl.length > 0) { + + if (selected[0] && selected[0].tagName.toLowerCase() === WYMeditor.A) { + link = selected; + } else { + wym._exec(WYMeditor.CREATE_LINK, sStamp); + link = jQuery("a[href=" + sStamp + "]", wym._doc.body); + } + + link.attr(WYMeditor.HREF, sUrl); + link.attr(WYMeditor.TITLE, jQuery(wym._options.titleSelector).val()); + link.attr(WYMeditor.REL, jQuery(wym._options.relSelector).val()); + } + window.close(); + }); + + jQuery(wym._options.dialogImageSelector + " " + + wym._options.submitSelector).submit(function () { + + var sUrl = jQuery(wym._options.srcSelector).val(), + $img; + if (sUrl.length > 0) { + + wym._exec(WYMeditor.INSERT_IMAGE, sStamp); + + $img = jQuery("img[src$=" + sStamp + "]", wym._doc.body); + $img.attr(WYMeditor.SRC, sUrl); + $img.attr(WYMeditor.TITLE, jQuery(wym._options.titleSelector).val()); + $img.attr(WYMeditor.ALT, jQuery(wym._options.altSelector).val()); + } + window.close(); + }); + + tableOnClick = WYMeditor.MAKE_TABLE_ONCLICK(wym); + jQuery(wym._options.dialogTableSelector + " " + wym._options.submitSelector) + .submit(tableOnClick); + + jQuery(wym._options.dialogPasteSelector + " " + + wym._options.submitSelector).submit(function () { + + var sText = jQuery(wym._options.textSelector).val(); + wym.paste(sText); + window.close(); + }); + + jQuery(wym._options.dialogPreviewSelector + " " + + wym._options.previewSelector).html(wym.xhtml()); + + //cancel button + jQuery(wym._options.cancelSelector).mousedown(function () { + window.close(); + }); + + //pre-init functions + if (jQuery.isFunction(wym._options.postInitDialog)) { + wym._options.postInitDialog(wym, window); + } + +}; + +/********** TABLE DIALOG ONCLICK **********/ + +WYMeditor.MAKE_TABLE_ONCLICK = function (wym) { + var tableOnClick = function () { + var numRows = jQuery(wym._options.rowsSelector).val(), + numColumns = jQuery(wym._options.colsSelector).val(), + caption = jQuery(wym._options.captionSelector).val(), + summary = jQuery(wym._options.summarySelector).val(), + + table = wym.insertTable(numRows, numColumns, caption, summary); + + window.close(); + }; + + return tableOnClick; +}; + + +/********** HELPERS **********/ + +// Returns true if it is a text node with whitespaces only +jQuery.fn.isPhantomNode = function () { + if (this[0].nodeType === 3) { + return !(/[^\t\n\r ]/.test(this[0].data)); + } + + return false; +}; + +/** + jQuery.fn.nextContentsUntil + =========================== + + Acts like jQuery.nextUntil() but includes text nodes and comments and only + works on the first element in the given jQuery collection.. +*/ +jQuery.fn.nextContentsUntil = function (selector, filter) { + var matched = [], + $matched, + cur = this.get(0); + + selector = selector ? selector : ''; + filter = filter ? filter : ''; + + if (!cur) { + // Called on an empty selector. The sibling of nothing is nothing + return jQuery(); + } + // We don't want to include this element, only its siblings + cur = cur.nextSibling; + + while (cur) { + if (!jQuery(cur).is(selector)) { + matched.push(cur); + cur = cur.nextSibling; + } else { + break; + } + } + + $matched = jQuery(matched); + if (filter) { + return $matched.filter(filter); + } + return $matched; +}; +/** + jQuery.fn.nextAllContents + ========================= + + Acts like jQuery.nextAll() but includes text nodes and comments and only + works on the first element in the given jQuery collection.. + + Mostly cribbed from the jQuery source. +*/ +jQuery.fn.nextAllContents = function () { + return jQuery(this).nextContentsUntil('', ''); +}; + +/** + jQuery.fn.prevContentsUntil + =========================== + + Acts like jQuery.prevUntil() but includes text nodes and comments and only + works on the first element in the given jQuery collection.. +*/ +jQuery.fn.prevContentsUntil = function (selector, filter) { + var matched = [], + $matched, + cur = this.get(0); + + selector = selector ? selector : ''; + filter = filter ? filter : ''; + + if (!cur) { + // Called on an empty selector. The sibling of nothing is nothing + return jQuery(); + } + // We don't want to include this element, only its siblings + cur = cur.previousSibling; + + while (cur) { + if (!jQuery(cur).is(selector)) { + matched.push(cur); + cur = cur.previousSibling; + } else { + break; + } + } + + $matched = jQuery(matched); + if (filter) { + return $matched.filter(filter); + } + return $matched; +}; + +/** + jQuery.fn.prevAllContents + ========================= + + Acts like jQuery.prevAll() but includes text nodes and comments and only + works on the first element in the given jQuery collection.. + + Mostly cribbed from the jQuery source. +*/ +jQuery.fn.prevAllContents = function () { + return jQuery(this).prevContentsUntil('', ''); +}; + +WYMeditor.isPhantomNode = function (n) { + if (n.nodeType === 3) { + return !(/[^\t\n\r ]/.test(n.data)); + } + + return false; +}; + +WYMeditor.isPhantomString = function (str) { + return !(/[^\t\n\r ]/.test(str)); +}; + +// Returns the Parents or the node itself +// jqexpr = a jQuery expression +jQuery.fn.parentsOrSelf = function (jqexpr) { + var n = this; + + if (n[0].nodeType === 3) { + n = n.parents().slice(0, 1); + } + +// if (n.is(jqexpr)) // XXX should work, but doesn't (probably a jQuery bug) + if (n.filter(jqexpr).size() === 1) { + return n; + } else { + return n.parents(jqexpr).slice(0, 1); + } +}; + +/* + WYMeditor.changeNodeType + ======================== + + Change the type (tagName) of the given node, while retaining all content, + properties and attributes. +*/ +WYMeditor.changeNodeType = function (node, newTag) { + var newNode, + i, + attributes = node.attributes; + + // In ie6, have to create the node as part of wrapInner before we can copy + // over attributes + jQuery(node).wrapInner('<' + newTag + '>'); + newNode = jQuery(node).children().get(0); + + // Copy attributes + for (i = 0; i < attributes.length; i++) { + if (attributes[i].specified) { + // We only care about specified attributes + newNode.setAttribute(attributes[i].nodeName, attributes[i].nodeValue); + } + } + + // Not copying inline CSS or properties/events + + jQuery(node).contents().unwrap(); + return newNode; +}; + +// String & array helpers + +WYMeditor.Helper = { + + //replace all instances of 'old' by 'rep' in 'str' string + replaceAll: function (str, old, rep) { + var rExp = new RegExp(old, "g"); + return str.replace(rExp, rep); + }, + + //insert 'inserted' at position 'pos' in 'str' string + insertAt: function (str, inserted, pos) { + return str.substr(0, pos) + inserted + str.substring(pos); + }, + + //trim 'str' string + trim: function (str) { + return str.replace(/^(\s*)|(\s*)$/gm, ''); + }, + + //return true if 'arr' array contains 'elem', or false + contains: function (arr, elem) { + var i; + for (i = 0; i < arr.length; i += 1) { + if (arr[i] === elem) { + return true; + } + } + return false; + }, + + //return 'item' position in 'arr' array, or -1 + indexOf: function (arr, item) { + var ret = -1, i; + for (i = 0; i < arr.length; i += 1) { + if (arr[i] === item) { + ret = i; + break; + } + } + return ret; + }, + + //return 'item' object in 'arr' array, checking its 'name' property, or null + findByName: function (arr, name) { + var i, item; + for (i = 0; i < arr.length; i += 1) { + item = arr[i]; + if (item.name === name) { + return item; + } + } + return null; + } +}; + + +/** + * @license Rangy, a cross-browser JavaScript range and selection library + * http://code.google.com/p/rangy/ + * + * Copyright 2011, Tim Down + * Licensed under the MIT license. + * Version: 1.2.2 + * Build date: 13 November 2011 + */ +window['rangy'] = (function() { + + + var OBJECT = "object", FUNCTION = "function", UNDEFINED = "undefined"; + + var domRangeProperties = ["startContainer", "startOffset", "endContainer", "endOffset", "collapsed", + "commonAncestorContainer", "START_TO_START", "START_TO_END", "END_TO_START", "END_TO_END"]; + + var domRangeMethods = ["setStart", "setStartBefore", "setStartAfter", "setEnd", "setEndBefore", + "setEndAfter", "collapse", "selectNode", "selectNodeContents", "compareBoundaryPoints", "deleteContents", + "extractContents", "cloneContents", "insertNode", "surroundContents", "cloneRange", "toString", "detach"]; + + var textRangeProperties = ["boundingHeight", "boundingLeft", "boundingTop", "boundingWidth", "htmlText", "text"]; + + // Subset of TextRange's full set of methods that we're interested in + var textRangeMethods = ["collapse", "compareEndPoints", "duplicate", "getBookmark", "moveToBookmark", + "moveToElementText", "parentElement", "pasteHTML", "select", "setEndPoint", "getBoundingClientRect"]; + + /*----------------------------------------------------------------------------------------------------------------*/ + + // Trio of functions taken from Peter Michaux's article: + // http://peter.michaux.ca/articles/feature-detection-state-of-the-art-browser-scripting + function isHostMethod(o, p) { + var t = typeof o[p]; + return t == FUNCTION || (!!(t == OBJECT && o[p])) || t == "unknown"; + } + + function isHostObject(o, p) { + return !!(typeof o[p] == OBJECT && o[p]); + } + + function isHostProperty(o, p) { + return typeof o[p] != UNDEFINED; + } + + // Creates a convenience function to save verbose repeated calls to tests functions + function createMultiplePropertyTest(testFunc) { + return function(o, props) { + var i = props.length; + while (i--) { + if (!testFunc(o, props[i])) { + return false; + } + } + return true; + }; + } + + // Next trio of functions are a convenience to save verbose repeated calls to previous two functions + var areHostMethods = createMultiplePropertyTest(isHostMethod); + var areHostObjects = createMultiplePropertyTest(isHostObject); + var areHostProperties = createMultiplePropertyTest(isHostProperty); + + function isTextRange(range) { + return range && areHostMethods(range, textRangeMethods) && areHostProperties(range, textRangeProperties); + } + + var api = { + version: "1.2.2", + initialized: false, + supported: true, + + util: { + isHostMethod: isHostMethod, + isHostObject: isHostObject, + isHostProperty: isHostProperty, + areHostMethods: areHostMethods, + areHostObjects: areHostObjects, + areHostProperties: areHostProperties, + isTextRange: isTextRange + }, + + features: {}, + + modules: {}, + config: { + alertOnWarn: false, + preferTextRange: false + } + }; + + function fail(reason) { + window.alert("Rangy not supported in your browser. Reason: " + reason); + api.initialized = true; + api.supported = false; + } + + api.fail = fail; + + function warn(msg) { + var warningMessage = "Rangy warning: " + msg; + if (api.config.alertOnWarn) { + window.alert(warningMessage); + } else if (typeof window.console != UNDEFINED && typeof window.console.log != UNDEFINED) { + window.console.log(warningMessage); + } + } + + api.warn = warn; + + if ({}.hasOwnProperty) { + api.util.extend = function(o, props) { + for (var i in props) { + if (props.hasOwnProperty(i)) { + o[i] = props[i]; + } + } + }; + } else { + fail("hasOwnProperty not supported"); + } + + var initListeners = []; + var moduleInitializers = []; + + // Initialization + function init() { + if (api.initialized) { + return; + } + var testRange; + var implementsDomRange = false, implementsTextRange = false; + + // First, perform basic feature tests + + if (isHostMethod(document, "createRange")) { + testRange = document.createRange(); + if (areHostMethods(testRange, domRangeMethods) && areHostProperties(testRange, domRangeProperties)) { + implementsDomRange = true; + } + testRange.detach(); + } + + var body = isHostObject(document, "body") ? document.body : document.getElementsByTagName("body")[0]; + + if (body && isHostMethod(body, "createTextRange")) { + testRange = body.createTextRange(); + if (isTextRange(testRange)) { + implementsTextRange = true; + } + } + + if (!implementsDomRange && !implementsTextRange) { + fail("Neither Range nor TextRange are implemented"); + } + + api.initialized = true; + api.features = { + implementsDomRange: implementsDomRange, + implementsTextRange: implementsTextRange + }; + + // Initialize modules and call init listeners + var allListeners = moduleInitializers.concat(initListeners); + for (var i = 0, len = allListeners.length; i < len; ++i) { + try { + allListeners[i](api); + } catch (ex) { + if (isHostObject(window, "console") && isHostMethod(window.console, "log")) { + window.console.log("Init listener threw an exception. Continuing.", ex); + } + + } + } + } + + // Allow external scripts to initialize this library in case it's loaded after the document has loaded + api.init = init; + + // Execute listener immediately if already initialized + api.addInitListener = function(listener) { + if (api.initialized) { + listener(api); + } else { + initListeners.push(listener); + } + }; + + var createMissingNativeApiListeners = []; + + api.addCreateMissingNativeApiListener = function(listener) { + createMissingNativeApiListeners.push(listener); + }; + + function createMissingNativeApi(win) { + win = win || window; + init(); + + // Notify listeners + for (var i = 0, len = createMissingNativeApiListeners.length; i < len; ++i) { + createMissingNativeApiListeners[i](win); + } + } + + api.createMissingNativeApi = createMissingNativeApi; + + /** + * @constructor + */ + function Module(name) { + this.name = name; + this.initialized = false; + this.supported = false; + } + + Module.prototype.fail = function(reason) { + this.initialized = true; + this.supported = false; + + throw new Error("Module '" + this.name + "' failed to load: " + reason); + }; + + Module.prototype.warn = function(msg) { + api.warn("Module " + this.name + ": " + msg); + }; + + Module.prototype.createError = function(msg) { + return new Error("Error in Rangy " + this.name + " module: " + msg); + }; + + api.createModule = function(name, initFunc) { + var module = new Module(name); + api.modules[name] = module; + + moduleInitializers.push(function(api) { + initFunc(api, module); + module.initialized = true; + module.supported = true; + }); + }; + + api.requireModules = function(modules) { + for (var i = 0, len = modules.length, module, moduleName; i < len; ++i) { + moduleName = modules[i]; + module = api.modules[moduleName]; + if (!module || !(module instanceof Module)) { + throw new Error("Module '" + moduleName + "' not found"); + } + if (!module.supported) { + throw new Error("Module '" + moduleName + "' not supported"); + } + } + }; + + /*----------------------------------------------------------------------------------------------------------------*/ + + // Wait for document to load before running tests + + var docReady = false; + + var loadHandler = function(e) { + + if (!docReady) { + docReady = true; + if (!api.initialized) { + init(); + } + } + }; + + // Test whether we have window and document objects that we will need + if (typeof window == UNDEFINED) { + fail("No window found"); + return; + } + if (typeof document == UNDEFINED) { + fail("No document found"); + return; + } + + if (isHostMethod(document, "addEventListener")) { + document.addEventListener("DOMContentLoaded", loadHandler, false); + } + + // Add a fallback in case the DOMContentLoaded event isn't supported + if (isHostMethod(window, "addEventListener")) { + window.addEventListener("load", loadHandler, false); + } else if (isHostMethod(window, "attachEvent")) { + window.attachEvent("onload", loadHandler); + } else { + fail("Window does not have required addEventListener or attachEvent method"); + } + + return api; +})(); +rangy.createModule("DomUtil", function(api, module) { + + var UNDEF = "undefined"; + var util = api.util; + + // Perform feature tests + if (!util.areHostMethods(document, ["createDocumentFragment", "createElement", "createTextNode"])) { + module.fail("document missing a Node creation method"); + } + + if (!util.isHostMethod(document, "getElementsByTagName")) { + module.fail("document missing getElementsByTagName method"); + } + + var el = document.createElement("div"); + if (!util.areHostMethods(el, ["insertBefore", "appendChild", "cloneNode"] || + !util.areHostObjects(el, ["previousSibling", "nextSibling", "childNodes", "parentNode"]))) { + module.fail("Incomplete Element implementation"); + } + + // innerHTML is required for Range's createContextualFragment method + if (!util.isHostProperty(el, "innerHTML")) { + module.fail("Element is missing innerHTML property"); + } + + var textNode = document.createTextNode("test"); + if (!util.areHostMethods(textNode, ["splitText", "deleteData", "insertData", "appendData", "cloneNode"] || + !util.areHostObjects(el, ["previousSibling", "nextSibling", "childNodes", "parentNode"]) || + !util.areHostProperties(textNode, ["data"]))) { + module.fail("Incomplete Text Node implementation"); + } + + /*----------------------------------------------------------------------------------------------------------------*/ + + // Removed use of indexOf because of a bizarre bug in Opera that is thrown in one of the Acid3 tests. I haven't been + // able to replicate it outside of the test. The bug is that indexOf returns -1 when called on an Array that + // contains just the document as a single element and the value searched for is the document. + var arrayContains = /*Array.prototype.indexOf ? + function(arr, val) { + return arr.indexOf(val) > -1; + }:*/ + + function(arr, val) { + var i = arr.length; + while (i--) { + if (arr[i] === val) { + return true; + } + } + return false; + }; + + // Opera 11 puts HTML elements in the null namespace, it seems, and IE 7 has undefined namespaceURI + function isHtmlNamespace(node) { + var ns; + return typeof node.namespaceURI == UNDEF || ((ns = node.namespaceURI) === null || ns == "http://www.w3.org/1999/xhtml"); + } + + function parentElement(node) { + var parent = node.parentNode; + return (parent.nodeType == 1) ? parent : null; + } + + function getNodeIndex(node) { + var i = 0; + while( (node = node.previousSibling) ) { + i++; + } + return i; + } + + function getNodeLength(node) { + var childNodes; + return isCharacterDataNode(node) ? node.length : ((childNodes = node.childNodes) ? childNodes.length : 0); + } + + function getCommonAncestor(node1, node2) { + var ancestors = [], n; + for (n = node1; n; n = n.parentNode) { + ancestors.push(n); + } + + for (n = node2; n; n = n.parentNode) { + if (arrayContains(ancestors, n)) { + return n; + } + } + + return null; + } + + function isAncestorOf(ancestor, descendant, selfIsAncestor) { + var n = selfIsAncestor ? descendant : descendant.parentNode; + while (n) { + if (n === ancestor) { + return true; + } else { + n = n.parentNode; + } + } + return false; + } + + function getClosestAncestorIn(node, ancestor, selfIsAncestor) { + var p, n = selfIsAncestor ? node : node.parentNode; + while (n) { + p = n.parentNode; + if (p === ancestor) { + return n; + } + n = p; + } + return null; + } + + function isCharacterDataNode(node) { + var t = node.nodeType; + return t == 3 || t == 4 || t == 8 ; // Text, CDataSection or Comment + } + + function insertAfter(node, precedingNode) { + var nextNode = precedingNode.nextSibling, parent = precedingNode.parentNode; + if (nextNode) { + parent.insertBefore(node, nextNode); + } else { + parent.appendChild(node); + } + return node; + } + + // Note that we cannot use splitText() because it is bugridden in IE 9. + function splitDataNode(node, index) { + var newNode = node.cloneNode(false); + newNode.deleteData(0, index); + node.deleteData(index, node.length - index); + insertAfter(newNode, node); + return newNode; + } + + function getDocument(node) { + if (node.nodeType == 9) { + return node; + } else if (typeof node.ownerDocument != UNDEF) { + return node.ownerDocument; + } else if (typeof node.document != UNDEF) { + return node.document; + } else if (node.parentNode) { + return getDocument(node.parentNode); + } else { + throw new Error("getDocument: no document found for node"); + } + } + + function getWindow(node) { + var doc = getDocument(node); + if (typeof doc.defaultView != UNDEF) { + return doc.defaultView; + } else if (typeof doc.parentWindow != UNDEF) { + return doc.parentWindow; + } else { + throw new Error("Cannot get a window object for node"); + } + } + + function getIframeDocument(iframeEl) { + if (typeof iframeEl.contentDocument != UNDEF) { + return iframeEl.contentDocument; + } else if (typeof iframeEl.contentWindow != UNDEF) { + return iframeEl.contentWindow.document; + } else { + throw new Error("getIframeWindow: No Document object found for iframe element"); + } + } + + function getIframeWindow(iframeEl) { + if (typeof iframeEl.contentWindow != UNDEF) { + return iframeEl.contentWindow; + } else if (typeof iframeEl.contentDocument != UNDEF) { + return iframeEl.contentDocument.defaultView; + } else { + throw new Error("getIframeWindow: No Window object found for iframe element"); + } + } + + function getBody(doc) { + return util.isHostObject(doc, "body") ? doc.body : doc.getElementsByTagName("body")[0]; + } + + function getRootContainer(node) { + var parent; + while ( (parent = node.parentNode) ) { + node = parent; + } + return node; + } + + function comparePoints(nodeA, offsetA, nodeB, offsetB) { + // See http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-Comparing + var nodeC, root, childA, childB, n; + if (nodeA == nodeB) { + + // Case 1: nodes are the same + return offsetA === offsetB ? 0 : (offsetA < offsetB) ? -1 : 1; + } else if ( (nodeC = getClosestAncestorIn(nodeB, nodeA, true)) ) { + + // Case 2: node C (container B or an ancestor) is a child node of A + return offsetA <= getNodeIndex(nodeC) ? -1 : 1; + } else if ( (nodeC = getClosestAncestorIn(nodeA, nodeB, true)) ) { + + // Case 3: node C (container A or an ancestor) is a child node of B + return getNodeIndex(nodeC) < offsetB ? -1 : 1; + } else { + + // Case 4: containers are siblings or descendants of siblings + root = getCommonAncestor(nodeA, nodeB); + childA = (nodeA === root) ? root : getClosestAncestorIn(nodeA, root, true); + childB = (nodeB === root) ? root : getClosestAncestorIn(nodeB, root, true); + + if (childA === childB) { + // This shouldn't be possible + + throw new Error("comparePoints got to case 4 and childA and childB are the same!"); + } else { + n = root.firstChild; + while (n) { + if (n === childA) { + return -1; + } else if (n === childB) { + return 1; + } + n = n.nextSibling; + } + throw new Error("Should not be here!"); + } + } + } + + function fragmentFromNodeChildren(node) { + var fragment = getDocument(node).createDocumentFragment(), child; + while ( (child = node.firstChild) ) { + fragment.appendChild(child); + } + return fragment; + } + + function inspectNode(node) { + if (!node) { + return "[No node]"; + } + if (isCharacterDataNode(node)) { + return '"' + node.data + '"'; + } else if (node.nodeType == 1) { + var idAttr = node.id ? ' id="' + node.id + '"' : ""; + return "<" + node.nodeName + idAttr + ">[" + node.childNodes.length + "]"; + } else { + return node.nodeName; + } + } + + /** + * @constructor + */ + function NodeIterator(root) { + this.root = root; + this._next = root; + } + + NodeIterator.prototype = { + _current: null, + + hasNext: function() { + return !!this._next; + }, + + next: function() { + var n = this._current = this._next; + var child, next; + if (this._current) { + child = n.firstChild; + if (child) { + this._next = child; + } else { + next = null; + while ((n !== this.root) && !(next = n.nextSibling)) { + n = n.parentNode; + } + this._next = next; + } + } + return this._current; + }, + + detach: function() { + this._current = this._next = this.root = null; + } + }; + + function createIterator(root) { + return new NodeIterator(root); + } + + /** + * @constructor + */ + function DomPosition(node, offset) { + this.node = node; + this.offset = offset; + } + + DomPosition.prototype = { + equals: function(pos) { + return this.node === pos.node & this.offset == pos.offset; + }, + + inspect: function() { + return "[DomPosition(" + inspectNode(this.node) + ":" + this.offset + ")]"; + } + }; + + /** + * @constructor + */ + function DOMException(codeName) { + this.code = this[codeName]; + this.codeName = codeName; + this.message = "DOMException: " + this.codeName; + } + + DOMException.prototype = { + INDEX_SIZE_ERR: 1, + HIERARCHY_REQUEST_ERR: 3, + WRONG_DOCUMENT_ERR: 4, + NO_MODIFICATION_ALLOWED_ERR: 7, + NOT_FOUND_ERR: 8, + NOT_SUPPORTED_ERR: 9, + INVALID_STATE_ERR: 11 + }; + + DOMException.prototype.toString = function() { + return this.message; + }; + + api.dom = { + arrayContains: arrayContains, + isHtmlNamespace: isHtmlNamespace, + parentElement: parentElement, + getNodeIndex: getNodeIndex, + getNodeLength: getNodeLength, + getCommonAncestor: getCommonAncestor, + isAncestorOf: isAncestorOf, + getClosestAncestorIn: getClosestAncestorIn, + isCharacterDataNode: isCharacterDataNode, + insertAfter: insertAfter, + splitDataNode: splitDataNode, + getDocument: getDocument, + getWindow: getWindow, + getIframeWindow: getIframeWindow, + getIframeDocument: getIframeDocument, + getBody: getBody, + getRootContainer: getRootContainer, + comparePoints: comparePoints, + inspectNode: inspectNode, + fragmentFromNodeChildren: fragmentFromNodeChildren, + createIterator: createIterator, + DomPosition: DomPosition + }; + + api.DOMException = DOMException; +});rangy.createModule("DomRange", function(api, module) { + api.requireModules( ["DomUtil"] ); + + + var dom = api.dom; + var DomPosition = dom.DomPosition; + var DOMException = api.DOMException; + + /*----------------------------------------------------------------------------------------------------------------*/ + + // Utility functions + + function isNonTextPartiallySelected(node, range) { + return (node.nodeType != 3) && + (dom.isAncestorOf(node, range.startContainer, true) || dom.isAncestorOf(node, range.endContainer, true)); + } + + function getRangeDocument(range) { + return dom.getDocument(range.startContainer); + } + + function dispatchEvent(range, type, args) { + var listeners = range._listeners[type]; + if (listeners) { + for (var i = 0, len = listeners.length; i < len; ++i) { + listeners[i].call(range, {target: range, args: args}); + } + } + } + + function getBoundaryBeforeNode(node) { + return new DomPosition(node.parentNode, dom.getNodeIndex(node)); + } + + function getBoundaryAfterNode(node) { + return new DomPosition(node.parentNode, dom.getNodeIndex(node) + 1); + } + + function insertNodeAtPosition(node, n, o) { + var firstNodeInserted = node.nodeType == 11 ? node.firstChild : node; + if (dom.isCharacterDataNode(n)) { + if (o == n.length) { + dom.insertAfter(node, n); + } else { + n.parentNode.insertBefore(node, o == 0 ? n : dom.splitDataNode(n, o)); + } + } else if (o >= n.childNodes.length) { + n.appendChild(node); + } else { + n.insertBefore(node, n.childNodes[o]); + } + return firstNodeInserted; + } + + function cloneSubtree(iterator) { + var partiallySelected; + for (var node, frag = getRangeDocument(iterator.range).createDocumentFragment(), subIterator; node = iterator.next(); ) { + partiallySelected = iterator.isPartiallySelectedSubtree(); + + node = node.cloneNode(!partiallySelected); + if (partiallySelected) { + subIterator = iterator.getSubtreeIterator(); + node.appendChild(cloneSubtree(subIterator)); + subIterator.detach(true); + } + + if (node.nodeType == 10) { // DocumentType + throw new DOMException("HIERARCHY_REQUEST_ERR"); + } + frag.appendChild(node); + } + return frag; + } + + function iterateSubtree(rangeIterator, func, iteratorState) { + var it, n; + iteratorState = iteratorState || { stop: false }; + for (var node, subRangeIterator; node = rangeIterator.next(); ) { + //log.debug("iterateSubtree, partially selected: " + rangeIterator.isPartiallySelectedSubtree(), nodeToString(node)); + if (rangeIterator.isPartiallySelectedSubtree()) { + // The node is partially selected by the Range, so we can use a new RangeIterator on the portion of the + // node selected by the Range. + if (func(node) === false) { + iteratorState.stop = true; + return; + } else { + subRangeIterator = rangeIterator.getSubtreeIterator(); + iterateSubtree(subRangeIterator, func, iteratorState); + subRangeIterator.detach(true); + if (iteratorState.stop) { + return; + } + } + } else { + // The whole node is selected, so we can use efficient DOM iteration to iterate over the node and its + // descendant + it = dom.createIterator(node); + while ( (n = it.next()) ) { + if (func(n) === false) { + iteratorState.stop = true; + return; + } + } + } + } + } + + function deleteSubtree(iterator) { + var subIterator; + while (iterator.next()) { + if (iterator.isPartiallySelectedSubtree()) { + subIterator = iterator.getSubtreeIterator(); + deleteSubtree(subIterator); + subIterator.detach(true); + } else { + iterator.remove(); + } + } + } + + function extractSubtree(iterator) { + + for (var node, frag = getRangeDocument(iterator.range).createDocumentFragment(), subIterator; node = iterator.next(); ) { + + + if (iterator.isPartiallySelectedSubtree()) { + node = node.cloneNode(false); + subIterator = iterator.getSubtreeIterator(); + node.appendChild(extractSubtree(subIterator)); + subIterator.detach(true); + } else { + iterator.remove(); + } + if (node.nodeType == 10) { // DocumentType + throw new DOMException("HIERARCHY_REQUEST_ERR"); + } + frag.appendChild(node); + } + return frag; + } + + function getNodesInRange(range, nodeTypes, filter) { + //log.info("getNodesInRange, " + nodeTypes.join(",")); + var filterNodeTypes = !!(nodeTypes && nodeTypes.length), regex; + var filterExists = !!filter; + if (filterNodeTypes) { + regex = new RegExp("^(" + nodeTypes.join("|") + ")$"); + } + + var nodes = []; + iterateSubtree(new RangeIterator(range, false), function(node) { + if ((!filterNodeTypes || regex.test(node.nodeType)) && (!filterExists || filter(node))) { + nodes.push(node); + } + }); + return nodes; + } + + function inspect(range) { + var name = (typeof range.getName == "undefined") ? "Range" : range.getName(); + return "[" + name + "(" + dom.inspectNode(range.startContainer) + ":" + range.startOffset + ", " + + dom.inspectNode(range.endContainer) + ":" + range.endOffset + ")]"; + } + + /*----------------------------------------------------------------------------------------------------------------*/ + + // RangeIterator code partially borrows from IERange by Tim Ryan (http://github.com/timcameronryan/IERange) + + /** + * @constructor + */ + function RangeIterator(range, clonePartiallySelectedTextNodes) { + this.range = range; + this.clonePartiallySelectedTextNodes = clonePartiallySelectedTextNodes; + + + + if (!range.collapsed) { + this.sc = range.startContainer; + this.so = range.startOffset; + this.ec = range.endContainer; + this.eo = range.endOffset; + var root = range.commonAncestorContainer; + + if (this.sc === this.ec && dom.isCharacterDataNode(this.sc)) { + this.isSingleCharacterDataNode = true; + this._first = this._last = this._next = this.sc; + } else { + this._first = this._next = (this.sc === root && !dom.isCharacterDataNode(this.sc)) ? + this.sc.childNodes[this.so] : dom.getClosestAncestorIn(this.sc, root, true); + this._last = (this.ec === root && !dom.isCharacterDataNode(this.ec)) ? + this.ec.childNodes[this.eo - 1] : dom.getClosestAncestorIn(this.ec, root, true); + } + + } + } + + RangeIterator.prototype = { + _current: null, + _next: null, + _first: null, + _last: null, + isSingleCharacterDataNode: false, + + reset: function() { + this._current = null; + this._next = this._first; + }, + + hasNext: function() { + return !!this._next; + }, + + next: function() { + // Move to next node + var current = this._current = this._next; + if (current) { + this._next = (current !== this._last) ? current.nextSibling : null; + + // Check for partially selected text nodes + if (dom.isCharacterDataNode(current) && this.clonePartiallySelectedTextNodes) { + if (current === this.ec) { + + (current = current.cloneNode(true)).deleteData(this.eo, current.length - this.eo); + } + if (this._current === this.sc) { + + (current = current.cloneNode(true)).deleteData(0, this.so); + } + } + } + + return current; + }, + + remove: function() { + var current = this._current, start, end; + + if (dom.isCharacterDataNode(current) && (current === this.sc || current === this.ec)) { + start = (current === this.sc) ? this.so : 0; + end = (current === this.ec) ? this.eo : current.length; + if (start != end) { + current.deleteData(start, end - start); + } + } else { + if (current.parentNode) { + current.parentNode.removeChild(current); + } else { + + } + } + }, + + // Checks if the current node is partially selected + isPartiallySelectedSubtree: function() { + var current = this._current; + return isNonTextPartiallySelected(current, this.range); + }, + + getSubtreeIterator: function() { + var subRange; + if (this.isSingleCharacterDataNode) { + subRange = this.range.cloneRange(); + subRange.collapse(); + } else { + subRange = new Range(getRangeDocument(this.range)); + var current = this._current; + var startContainer = current, startOffset = 0, endContainer = current, endOffset = dom.getNodeLength(current); + + if (dom.isAncestorOf(current, this.sc, true)) { + startContainer = this.sc; + startOffset = this.so; + } + if (dom.isAncestorOf(current, this.ec, true)) { + endContainer = this.ec; + endOffset = this.eo; + } + + updateBoundaries(subRange, startContainer, startOffset, endContainer, endOffset); + } + return new RangeIterator(subRange, this.clonePartiallySelectedTextNodes); + }, + + detach: function(detachRange) { + if (detachRange) { + this.range.detach(); + } + this.range = this._current = this._next = this._first = this._last = this.sc = this.so = this.ec = this.eo = null; + } + }; + + /*----------------------------------------------------------------------------------------------------------------*/ + + // Exceptions + + /** + * @constructor + */ + function RangeException(codeName) { + this.code = this[codeName]; + this.codeName = codeName; + this.message = "RangeException: " + this.codeName; + } + + RangeException.prototype = { + BAD_BOUNDARYPOINTS_ERR: 1, + INVALID_NODE_TYPE_ERR: 2 + }; + + RangeException.prototype.toString = function() { + return this.message; + }; + + /*----------------------------------------------------------------------------------------------------------------*/ + + /** + * Currently iterates through all nodes in the range on creation until I think of a decent way to do it + * TODO: Look into making this a proper iterator, not requiring preloading everything first + * @constructor + */ + function RangeNodeIterator(range, nodeTypes, filter) { + this.nodes = getNodesInRange(range, nodeTypes, filter); + this._next = this.nodes[0]; + this._position = 0; + } + + RangeNodeIterator.prototype = { + _current: null, + + hasNext: function() { + return !!this._next; + }, + + next: function() { + this._current = this._next; + this._next = this.nodes[ ++this._position ]; + return this._current; + }, + + detach: function() { + this._current = this._next = this.nodes = null; + } + }; + + var beforeAfterNodeTypes = [1, 3, 4, 5, 7, 8, 10]; + var rootContainerNodeTypes = [2, 9, 11]; + var readonlyNodeTypes = [5, 6, 10, 12]; + var insertableNodeTypes = [1, 3, 4, 5, 7, 8, 10, 11]; + var surroundNodeTypes = [1, 3, 4, 5, 7, 8]; + + function createAncestorFinder(nodeTypes) { + return function(node, selfIsAncestor) { + var t, n = selfIsAncestor ? node : node.parentNode; + while (n) { + t = n.nodeType; + if (dom.arrayContains(nodeTypes, t)) { + return n; + } + n = n.parentNode; + } + return null; + }; + } + + var getRootContainer = dom.getRootContainer; + var getDocumentOrFragmentContainer = createAncestorFinder( [9, 11] ); + var getReadonlyAncestor = createAncestorFinder(readonlyNodeTypes); + var getDocTypeNotationEntityAncestor = createAncestorFinder( [6, 10, 12] ); + + function assertNoDocTypeNotationEntityAncestor(node, allowSelf) { + if (getDocTypeNotationEntityAncestor(node, allowSelf)) { + throw new RangeException("INVALID_NODE_TYPE_ERR"); + } + } + + function assertNotDetached(range) { + if (!range.startContainer) { + throw new DOMException("INVALID_STATE_ERR"); + } + } + + function assertValidNodeType(node, invalidTypes) { + if (!dom.arrayContains(invalidTypes, node.nodeType)) { + throw new RangeException("INVALID_NODE_TYPE_ERR"); + } + } + + function assertValidOffset(node, offset) { + if (offset < 0 || offset > (dom.isCharacterDataNode(node) ? node.length : node.childNodes.length)) { + throw new DOMException("INDEX_SIZE_ERR"); + } + } + + function assertSameDocumentOrFragment(node1, node2) { + if (getDocumentOrFragmentContainer(node1, true) !== getDocumentOrFragmentContainer(node2, true)) { + throw new DOMException("WRONG_DOCUMENT_ERR"); + } + } + + function assertNodeNotReadOnly(node) { + if (getReadonlyAncestor(node, true)) { + throw new DOMException("NO_MODIFICATION_ALLOWED_ERR"); + } + } + + function assertNode(node, codeName) { + if (!node) { + throw new DOMException(codeName); + } + } + + function isOrphan(node) { + return !dom.arrayContains(rootContainerNodeTypes, node.nodeType) && !getDocumentOrFragmentContainer(node, true); + } + + function isValidOffset(node, offset) { + return offset <= (dom.isCharacterDataNode(node) ? node.length : node.childNodes.length); + } + + function assertRangeValid(range) { + assertNotDetached(range); + if (isOrphan(range.startContainer) || isOrphan(range.endContainer) || + !isValidOffset(range.startContainer, range.startOffset) || + !isValidOffset(range.endContainer, range.endOffset)) { + throw new Error("Range error: Range is no longer valid after DOM mutation (" + range.inspect() + ")"); + } + } + + /*----------------------------------------------------------------------------------------------------------------*/ + + // Test the browser's innerHTML support to decide how to implement createContextualFragment + var styleEl = document.createElement("style"); + var htmlParsingConforms = false; + try { + styleEl.innerHTML = "x"; + htmlParsingConforms = (styleEl.firstChild.nodeType == 3); // Opera incorrectly creates an element node + } catch (e) { + // IE 6 and 7 throw + } + + api.features.htmlParsingConforms = htmlParsingConforms; + + var createContextualFragment = htmlParsingConforms ? + + // Implementation as per HTML parsing spec, trusting in the browser's implementation of innerHTML. See + // discussion and base code for this implementation at issue 67. + // Spec: http://html5.org/specs/dom-parsing.html#extensions-to-the-range-interface + // Thanks to Aleks Williams. + function(fragmentStr) { + // "Let node the context object's start's node." + var node = this.startContainer; + var doc = dom.getDocument(node); + + // "If the context object's start's node is null, raise an INVALID_STATE_ERR + // exception and abort these steps." + if (!node) { + throw new DOMException("INVALID_STATE_ERR"); + } + + // "Let element be as follows, depending on node's interface:" + // Document, Document Fragment: null + var el = null; + + // "Element: node" + if (node.nodeType == 1) { + el = node; + + // "Text, Comment: node's parentElement" + } else if (dom.isCharacterDataNode(node)) { + el = dom.parentElement(node); + } + + // "If either element is null or element's ownerDocument is an HTML document + // and element's local name is "html" and element's namespace is the HTML + // namespace" + if (el === null || ( + el.nodeName == "HTML" + && dom.isHtmlNamespace(dom.getDocument(el).documentElement) + && dom.isHtmlNamespace(el) + )) { + + // "let element be a new Element with "body" as its local name and the HTML + // namespace as its namespace."" + el = doc.createElement("body"); + } else { + el = el.cloneNode(false); + } + + // "If the node's document is an HTML document: Invoke the HTML fragment parsing algorithm." + // "If the node's document is an XML document: Invoke the XML fragment parsing algorithm." + // "In either case, the algorithm must be invoked with fragment as the input + // and element as the context element." + el.innerHTML = fragmentStr; + + // "If this raises an exception, then abort these steps. Otherwise, let new + // children be the nodes returned." + + // "Let fragment be a new DocumentFragment." + // "Append all new children to fragment." + // "Return fragment." + return dom.fragmentFromNodeChildren(el); + } : + + // In this case, innerHTML cannot be trusted, so fall back to a simpler, non-conformant implementation that + // previous versions of Rangy used (with the exception of using a body element rather than a div) + function(fragmentStr) { + assertNotDetached(this); + var doc = getRangeDocument(this); + var el = doc.createElement("body"); + el.innerHTML = fragmentStr; + + return dom.fragmentFromNodeChildren(el); + }; + + /*----------------------------------------------------------------------------------------------------------------*/ + + var rangeProperties = ["startContainer", "startOffset", "endContainer", "endOffset", "collapsed", + "commonAncestorContainer"]; + + var s2s = 0, s2e = 1, e2e = 2, e2s = 3; + var n_b = 0, n_a = 1, n_b_a = 2, n_i = 3; + + function RangePrototype() {} + + RangePrototype.prototype = { + attachListener: function(type, listener) { + this._listeners[type].push(listener); + }, + + compareBoundaryPoints: function(how, range) { + assertRangeValid(this); + assertSameDocumentOrFragment(this.startContainer, range.startContainer); + + var nodeA, offsetA, nodeB, offsetB; + var prefixA = (how == e2s || how == s2s) ? "start" : "end"; + var prefixB = (how == s2e || how == s2s) ? "start" : "end"; + nodeA = this[prefixA + "Container"]; + offsetA = this[prefixA + "Offset"]; + nodeB = range[prefixB + "Container"]; + offsetB = range[prefixB + "Offset"]; + return dom.comparePoints(nodeA, offsetA, nodeB, offsetB); + }, + + insertNode: function(node) { + assertRangeValid(this); + assertValidNodeType(node, insertableNodeTypes); + assertNodeNotReadOnly(this.startContainer); + + if (dom.isAncestorOf(node, this.startContainer, true)) { + throw new DOMException("HIERARCHY_REQUEST_ERR"); + } + + // No check for whether the container of the start of the Range is of a type that does not allow + // children of the type of node: the browser's DOM implementation should do this for us when we attempt + // to add the node + + var firstNodeInserted = insertNodeAtPosition(node, this.startContainer, this.startOffset); + this.setStartBefore(firstNodeInserted); + }, + + cloneContents: function() { + assertRangeValid(this); + + var clone, frag; + if (this.collapsed) { + return getRangeDocument(this).createDocumentFragment(); + } else { + if (this.startContainer === this.endContainer && dom.isCharacterDataNode(this.startContainer)) { + clone = this.startContainer.cloneNode(true); + clone.data = clone.data.slice(this.startOffset, this.endOffset); + frag = getRangeDocument(this).createDocumentFragment(); + frag.appendChild(clone); + return frag; + } else { + var iterator = new RangeIterator(this, true); + clone = cloneSubtree(iterator); + iterator.detach(); + } + return clone; + } + }, + + canSurroundContents: function() { + assertRangeValid(this); + assertNodeNotReadOnly(this.startContainer); + assertNodeNotReadOnly(this.endContainer); + + // Check if the contents can be surrounded. Specifically, this means whether the range partially selects + // no non-text nodes. + var iterator = new RangeIterator(this, true); + var boundariesInvalid = (iterator._first && (isNonTextPartiallySelected(iterator._first, this)) || + (iterator._last && isNonTextPartiallySelected(iterator._last, this))); + iterator.detach(); + return !boundariesInvalid; + }, + + surroundContents: function(node) { + assertValidNodeType(node, surroundNodeTypes); + + if (!this.canSurroundContents()) { + throw new RangeException("BAD_BOUNDARYPOINTS_ERR"); + } + + // Extract the contents + var content = this.extractContents(); + + // Clear the children of the node + if (node.hasChildNodes()) { + while (node.lastChild) { + node.removeChild(node.lastChild); + } + } + + // Insert the new node and add the extracted contents + insertNodeAtPosition(node, this.startContainer, this.startOffset); + node.appendChild(content); + + this.selectNode(node); + }, + + cloneRange: function() { + assertRangeValid(this); + var range = new Range(getRangeDocument(this)); + var i = rangeProperties.length, prop; + while (i--) { + prop = rangeProperties[i]; + range[prop] = this[prop]; + } + return range; + }, + + toString: function() { + assertRangeValid(this); + var sc = this.startContainer; + if (sc === this.endContainer && dom.isCharacterDataNode(sc)) { + return (sc.nodeType == 3 || sc.nodeType == 4) ? sc.data.slice(this.startOffset, this.endOffset) : ""; + } else { + var textBits = [], iterator = new RangeIterator(this, true); + + iterateSubtree(iterator, function(node) { + // Accept only text or CDATA nodes, not comments + + if (node.nodeType == 3 || node.nodeType == 4) { + textBits.push(node.data); + } + }); + iterator.detach(); + return textBits.join(""); + } + }, + + // The methods below are all non-standard. The following batch were introduced by Mozilla but have since + // been removed from Mozilla. + + compareNode: function(node) { + assertRangeValid(this); + + var parent = node.parentNode; + var nodeIndex = dom.getNodeIndex(node); + + if (!parent) { + throw new DOMException("NOT_FOUND_ERR"); + } + + var startComparison = this.comparePoint(parent, nodeIndex), + endComparison = this.comparePoint(parent, nodeIndex + 1); + + if (startComparison < 0) { // Node starts before + return (endComparison > 0) ? n_b_a : n_b; + } else { + return (endComparison > 0) ? n_a : n_i; + } + }, + + comparePoint: function(node, offset) { + assertRangeValid(this); + assertNode(node, "HIERARCHY_REQUEST_ERR"); + assertSameDocumentOrFragment(node, this.startContainer); + + if (dom.comparePoints(node, offset, this.startContainer, this.startOffset) < 0) { + return -1; + } else if (dom.comparePoints(node, offset, this.endContainer, this.endOffset) > 0) { + return 1; + } + return 0; + }, + + createContextualFragment: createContextualFragment, + + toHtml: function() { + assertRangeValid(this); + var container = getRangeDocument(this).createElement("div"); + container.appendChild(this.cloneContents()); + return container.innerHTML; + }, + + // touchingIsIntersecting determines whether this method considers a node that borders a range intersects + // with it (as in WebKit) or not (as in Gecko pre-1.9, and the default) + intersectsNode: function(node, touchingIsIntersecting) { + assertRangeValid(this); + assertNode(node, "NOT_FOUND_ERR"); + if (dom.getDocument(node) !== getRangeDocument(this)) { + return false; + } + + var parent = node.parentNode, offset = dom.getNodeIndex(node); + assertNode(parent, "NOT_FOUND_ERR"); + + var startComparison = dom.comparePoints(parent, offset, this.endContainer, this.endOffset), + endComparison = dom.comparePoints(parent, offset + 1, this.startContainer, this.startOffset); + + return touchingIsIntersecting ? startComparison <= 0 && endComparison >= 0 : startComparison < 0 && endComparison > 0; + }, + + + isPointInRange: function(node, offset) { + assertRangeValid(this); + assertNode(node, "HIERARCHY_REQUEST_ERR"); + assertSameDocumentOrFragment(node, this.startContainer); + + return (dom.comparePoints(node, offset, this.startContainer, this.startOffset) >= 0) && + (dom.comparePoints(node, offset, this.endContainer, this.endOffset) <= 0); + }, + + // The methods below are non-standard and invented by me. + + // Sharing a boundary start-to-end or end-to-start does not count as intersection. + intersectsRange: function(range, touchingIsIntersecting) { + assertRangeValid(this); + + if (getRangeDocument(range) != getRangeDocument(this)) { + throw new DOMException("WRONG_DOCUMENT_ERR"); + } + + var startComparison = dom.comparePoints(this.startContainer, this.startOffset, range.endContainer, range.endOffset), + endComparison = dom.comparePoints(this.endContainer, this.endOffset, range.startContainer, range.startOffset); + + return touchingIsIntersecting ? startComparison <= 0 && endComparison >= 0 : startComparison < 0 && endComparison > 0; + }, + + intersection: function(range) { + if (this.intersectsRange(range)) { + var startComparison = dom.comparePoints(this.startContainer, this.startOffset, range.startContainer, range.startOffset), + endComparison = dom.comparePoints(this.endContainer, this.endOffset, range.endContainer, range.endOffset); + + var intersectionRange = this.cloneRange(); + + if (startComparison == -1) { + intersectionRange.setStart(range.startContainer, range.startOffset); + } + if (endComparison == 1) { + intersectionRange.setEnd(range.endContainer, range.endOffset); + } + return intersectionRange; + } + return null; + }, + + union: function(range) { + if (this.intersectsRange(range, true)) { + var unionRange = this.cloneRange(); + if (dom.comparePoints(range.startContainer, range.startOffset, this.startContainer, this.startOffset) == -1) { + unionRange.setStart(range.startContainer, range.startOffset); + } + if (dom.comparePoints(range.endContainer, range.endOffset, this.endContainer, this.endOffset) == 1) { + unionRange.setEnd(range.endContainer, range.endOffset); + } + return unionRange; + } else { + throw new RangeException("Ranges do not intersect"); + } + }, + + containsNode: function(node, allowPartial) { + if (allowPartial) { + return this.intersectsNode(node, false); + } else { + return this.compareNode(node) == n_i; + } + }, + + containsNodeContents: function(node) { + return this.comparePoint(node, 0) >= 0 && this.comparePoint(node, dom.getNodeLength(node)) <= 0; + }, + + containsRange: function(range) { + return this.intersection(range).equals(range); + }, + + containsNodeText: function(node) { + var nodeRange = this.cloneRange(); + nodeRange.selectNode(node); + var textNodes = nodeRange.getNodes([3]); + if (textNodes.length > 0) { + nodeRange.setStart(textNodes[0], 0); + var lastTextNode = textNodes.pop(); + nodeRange.setEnd(lastTextNode, lastTextNode.length); + var contains = this.containsRange(nodeRange); + nodeRange.detach(); + return contains; + } else { + return this.containsNodeContents(node); + } + }, + + createNodeIterator: function(nodeTypes, filter) { + assertRangeValid(this); + return new RangeNodeIterator(this, nodeTypes, filter); + }, + + getNodes: function(nodeTypes, filter) { + assertRangeValid(this); + return getNodesInRange(this, nodeTypes, filter); + }, + + getDocument: function() { + return getRangeDocument(this); + }, + + collapseBefore: function(node) { + assertNotDetached(this); + + this.setEndBefore(node); + this.collapse(false); + }, + + collapseAfter: function(node) { + assertNotDetached(this); + + this.setStartAfter(node); + this.collapse(true); + }, + + getName: function() { + return "DomRange"; + }, + + equals: function(range) { + return Range.rangesEqual(this, range); + }, + + inspect: function() { + return inspect(this); + } + }; + + function copyComparisonConstantsToObject(obj) { + obj.START_TO_START = s2s; + obj.START_TO_END = s2e; + obj.END_TO_END = e2e; + obj.END_TO_START = e2s; + + obj.NODE_BEFORE = n_b; + obj.NODE_AFTER = n_a; + obj.NODE_BEFORE_AND_AFTER = n_b_a; + obj.NODE_INSIDE = n_i; + } + + function copyComparisonConstants(constructor) { + copyComparisonConstantsToObject(constructor); + copyComparisonConstantsToObject(constructor.prototype); + } + + function createRangeContentRemover(remover, boundaryUpdater) { + return function() { + assertRangeValid(this); + + var sc = this.startContainer, so = this.startOffset, root = this.commonAncestorContainer; + + var iterator = new RangeIterator(this, true); + + // Work out where to position the range after content removal + var node, boundary; + if (sc !== root) { + node = dom.getClosestAncestorIn(sc, root, true); + boundary = getBoundaryAfterNode(node); + sc = boundary.node; + so = boundary.offset; + } + + // Check none of the range is read-only + iterateSubtree(iterator, assertNodeNotReadOnly); + + iterator.reset(); + + // Remove the content + var returnValue = remover(iterator); + iterator.detach(); + + // Move to the new position + boundaryUpdater(this, sc, so, sc, so); + + return returnValue; + }; + } + + function createPrototypeRange(constructor, boundaryUpdater, detacher) { + function createBeforeAfterNodeSetter(isBefore, isStart) { + return function(node) { + assertNotDetached(this); + assertValidNodeType(node, beforeAfterNodeTypes); + assertValidNodeType(getRootContainer(node), rootContainerNodeTypes); + + var boundary = (isBefore ? getBoundaryBeforeNode : getBoundaryAfterNode)(node); + (isStart ? setRangeStart : setRangeEnd)(this, boundary.node, boundary.offset); + }; + } + + function setRangeStart(range, node, offset) { + var ec = range.endContainer, eo = range.endOffset; + if (node !== range.startContainer || offset !== range.startOffset) { + // Check the root containers of the range and the new boundary, and also check whether the new boundary + // is after the current end. In either case, collapse the range to the new position + if (getRootContainer(node) != getRootContainer(ec) || dom.comparePoints(node, offset, ec, eo) == 1) { + ec = node; + eo = offset; + } + boundaryUpdater(range, node, offset, ec, eo); + } + } + + function setRangeEnd(range, node, offset) { + var sc = range.startContainer, so = range.startOffset; + if (node !== range.endContainer || offset !== range.endOffset) { + // Check the root containers of the range and the new boundary, and also check whether the new boundary + // is after the current end. In either case, collapse the range to the new position + if (getRootContainer(node) != getRootContainer(sc) || dom.comparePoints(node, offset, sc, so) == -1) { + sc = node; + so = offset; + } + boundaryUpdater(range, sc, so, node, offset); + } + } + + function setRangeStartAndEnd(range, node, offset) { + if (node !== range.startContainer || offset !== range.startOffset || node !== range.endContainer || offset !== range.endOffset) { + boundaryUpdater(range, node, offset, node, offset); + } + } + + constructor.prototype = new RangePrototype(); + + api.util.extend(constructor.prototype, { + setStart: function(node, offset) { + assertNotDetached(this); + assertNoDocTypeNotationEntityAncestor(node, true); + assertValidOffset(node, offset); + + setRangeStart(this, node, offset); + }, + + setEnd: function(node, offset) { + assertNotDetached(this); + assertNoDocTypeNotationEntityAncestor(node, true); + assertValidOffset(node, offset); + + setRangeEnd(this, node, offset); + }, + + setStartBefore: createBeforeAfterNodeSetter(true, true), + setStartAfter: createBeforeAfterNodeSetter(false, true), + setEndBefore: createBeforeAfterNodeSetter(true, false), + setEndAfter: createBeforeAfterNodeSetter(false, false), + + collapse: function(isStart) { + assertRangeValid(this); + if (isStart) { + boundaryUpdater(this, this.startContainer, this.startOffset, this.startContainer, this.startOffset); + } else { + boundaryUpdater(this, this.endContainer, this.endOffset, this.endContainer, this.endOffset); + } + }, + + selectNodeContents: function(node) { + // This doesn't seem well specified: the spec talks only about selecting the node's contents, which + // could be taken to mean only its children. However, browsers implement this the same as selectNode for + // text nodes, so I shall do likewise + assertNotDetached(this); + assertNoDocTypeNotationEntityAncestor(node, true); + + boundaryUpdater(this, node, 0, node, dom.getNodeLength(node)); + }, + + selectNode: function(node) { + assertNotDetached(this); + assertNoDocTypeNotationEntityAncestor(node, false); + assertValidNodeType(node, beforeAfterNodeTypes); + + var start = getBoundaryBeforeNode(node), end = getBoundaryAfterNode(node); + boundaryUpdater(this, start.node, start.offset, end.node, end.offset); + }, + + extractContents: createRangeContentRemover(extractSubtree, boundaryUpdater), + + deleteContents: createRangeContentRemover(deleteSubtree, boundaryUpdater), + + canSurroundContents: function() { + assertRangeValid(this); + assertNodeNotReadOnly(this.startContainer); + assertNodeNotReadOnly(this.endContainer); + + // Check if the contents can be surrounded. Specifically, this means whether the range partially selects + // no non-text nodes. + var iterator = new RangeIterator(this, true); + var boundariesInvalid = (iterator._first && (isNonTextPartiallySelected(iterator._first, this)) || + (iterator._last && isNonTextPartiallySelected(iterator._last, this))); + iterator.detach(); + return !boundariesInvalid; + }, + + detach: function() { + detacher(this); + }, + + splitBoundaries: function() { + assertRangeValid(this); + + + var sc = this.startContainer, so = this.startOffset, ec = this.endContainer, eo = this.endOffset; + var startEndSame = (sc === ec); + + if (dom.isCharacterDataNode(ec) && eo > 0 && eo < ec.length) { + dom.splitDataNode(ec, eo); + + } + + if (dom.isCharacterDataNode(sc) && so > 0 && so < sc.length) { + + sc = dom.splitDataNode(sc, so); + if (startEndSame) { + eo -= so; + ec = sc; + } else if (ec == sc.parentNode && eo >= dom.getNodeIndex(sc)) { + eo++; + } + so = 0; + + } + boundaryUpdater(this, sc, so, ec, eo); + }, + + normalizeBoundaries: function() { + assertRangeValid(this); + + var sc = this.startContainer, so = this.startOffset, ec = this.endContainer, eo = this.endOffset; + + var mergeForward = function(node) { + var sibling = node.nextSibling; + if (sibling && sibling.nodeType == node.nodeType) { + ec = node; + eo = node.length; + node.appendData(sibling.data); + sibling.parentNode.removeChild(sibling); + } + }; + + var mergeBackward = function(node) { + var sibling = node.previousSibling; + if (sibling && sibling.nodeType == node.nodeType) { + sc = node; + var nodeLength = node.length; + so = sibling.length; + node.insertData(0, sibling.data); + sibling.parentNode.removeChild(sibling); + if (sc == ec) { + eo += so; + ec = sc; + } else if (ec == node.parentNode) { + var nodeIndex = dom.getNodeIndex(node); + if (eo == nodeIndex) { + ec = node; + eo = nodeLength; + } else if (eo > nodeIndex) { + eo--; + } + } + } + }; + + var normalizeStart = true; + + if (dom.isCharacterDataNode(ec)) { + if (ec.length == eo) { + mergeForward(ec); + } + } else { + if (eo > 0) { + var endNode = ec.childNodes[eo - 1]; + if (endNode && dom.isCharacterDataNode(endNode)) { + mergeForward(endNode); + } + } + normalizeStart = !this.collapsed; + } + + if (normalizeStart) { + if (dom.isCharacterDataNode(sc)) { + if (so == 0) { + mergeBackward(sc); + } + } else { + if (so < sc.childNodes.length) { + var startNode = sc.childNodes[so]; + if (startNode && dom.isCharacterDataNode(startNode)) { + mergeBackward(startNode); + } + } + } + } else { + sc = ec; + so = eo; + } + + boundaryUpdater(this, sc, so, ec, eo); + }, + + collapseToPoint: function(node, offset) { + assertNotDetached(this); + + assertNoDocTypeNotationEntityAncestor(node, true); + assertValidOffset(node, offset); + + setRangeStartAndEnd(this, node, offset); + } + }); + + copyComparisonConstants(constructor); + } + + /*----------------------------------------------------------------------------------------------------------------*/ + + // Updates commonAncestorContainer and collapsed after boundary change + function updateCollapsedAndCommonAncestor(range) { + range.collapsed = (range.startContainer === range.endContainer && range.startOffset === range.endOffset); + range.commonAncestorContainer = range.collapsed ? + range.startContainer : dom.getCommonAncestor(range.startContainer, range.endContainer); + } + + function updateBoundaries(range, startContainer, startOffset, endContainer, endOffset) { + var startMoved = (range.startContainer !== startContainer || range.startOffset !== startOffset); + var endMoved = (range.endContainer !== endContainer || range.endOffset !== endOffset); + + range.startContainer = startContainer; + range.startOffset = startOffset; + range.endContainer = endContainer; + range.endOffset = endOffset; + + updateCollapsedAndCommonAncestor(range); + dispatchEvent(range, "boundarychange", {startMoved: startMoved, endMoved: endMoved}); + } + + function detach(range) { + assertNotDetached(range); + range.startContainer = range.startOffset = range.endContainer = range.endOffset = null; + range.collapsed = range.commonAncestorContainer = null; + dispatchEvent(range, "detach", null); + range._listeners = null; + } + + /** + * @constructor + */ + function Range(doc) { + this.startContainer = doc; + this.startOffset = 0; + this.endContainer = doc; + this.endOffset = 0; + this._listeners = { + boundarychange: [], + detach: [] + }; + updateCollapsedAndCommonAncestor(this); + } + + createPrototypeRange(Range, updateBoundaries, detach); + + api.rangePrototype = RangePrototype.prototype; + + Range.rangeProperties = rangeProperties; + Range.RangeIterator = RangeIterator; + Range.copyComparisonConstants = copyComparisonConstants; + Range.createPrototypeRange = createPrototypeRange; + Range.inspect = inspect; + Range.getRangeDocument = getRangeDocument; + Range.rangesEqual = function(r1, r2) { + return r1.startContainer === r2.startContainer && + r1.startOffset === r2.startOffset && + r1.endContainer === r2.endContainer && + r1.endOffset === r2.endOffset; + }; + + api.DomRange = Range; + api.RangeException = RangeException; +});rangy.createModule("WrappedRange", function(api, module) { + api.requireModules( ["DomUtil", "DomRange"] ); + + /** + * @constructor + */ + var WrappedRange; + var dom = api.dom; + var DomPosition = dom.DomPosition; + var DomRange = api.DomRange; + + + + /*----------------------------------------------------------------------------------------------------------------*/ + + /* + This is a workaround for a bug where IE returns the wrong container element from the TextRange's parentElement() + method. For example, in the following (where pipes denote the selection boundaries): + +
    • | a
    • b |
    + + var range = document.selection.createRange(); + alert(range.parentElement().id); // Should alert "ul" but alerts "b" + + This method returns the common ancestor node of the following: + - the parentElement() of the textRange + - the parentElement() of the textRange after calling collapse(true) + - the parentElement() of the textRange after calling collapse(false) + */ + function getTextRangeContainerElement(textRange) { + var parentEl = textRange.parentElement(); + + var range = textRange.duplicate(); + range.collapse(true); + var startEl = range.parentElement(); + range = textRange.duplicate(); + range.collapse(false); + var endEl = range.parentElement(); + var startEndContainer = (startEl == endEl) ? startEl : dom.getCommonAncestor(startEl, endEl); + + return startEndContainer == parentEl ? startEndContainer : dom.getCommonAncestor(parentEl, startEndContainer); + } + + function textRangeIsCollapsed(textRange) { + return textRange.compareEndPoints("StartToEnd", textRange) == 0; + } + + // Gets the boundary of a TextRange expressed as a node and an offset within that node. This function started out as + // an improved version of code found in Tim Cameron Ryan's IERange (http://code.google.com/p/ierange/) but has + // grown, fixing problems with line breaks in preformatted text, adding workaround for IE TextRange bugs, handling + // for inputs and images, plus optimizations. + function getTextRangeBoundaryPosition(textRange, wholeRangeContainerElement, isStart, isCollapsed) { + var workingRange = textRange.duplicate(); + + workingRange.collapse(isStart); + var containerElement = workingRange.parentElement(); + + // Sometimes collapsing a TextRange that's at the start of a text node can move it into the previous node, so + // check for that + // TODO: Find out when. Workaround for wholeRangeContainerElement may break this + if (!dom.isAncestorOf(wholeRangeContainerElement, containerElement, true)) { + containerElement = wholeRangeContainerElement; + + } + + + + // Deal with nodes that cannot "contain rich HTML markup". In practice, this means form inputs, images and + // similar. See http://msdn.microsoft.com/en-us/library/aa703950%28VS.85%29.aspx + if (!containerElement.canHaveHTML) { + return new DomPosition(containerElement.parentNode, dom.getNodeIndex(containerElement)); + } + + var workingNode = dom.getDocument(containerElement).createElement("span"); + var comparison, workingComparisonType = isStart ? "StartToStart" : "StartToEnd"; + var previousNode, nextNode, boundaryPosition, boundaryNode; + + // Move the working range through the container's children, starting at the end and working backwards, until the + // working range reaches or goes past the boundary we're interested in + do { + containerElement.insertBefore(workingNode, workingNode.previousSibling); + workingRange.moveToElementText(workingNode); + } while ( (comparison = workingRange.compareEndPoints(workingComparisonType, textRange)) > 0 && + workingNode.previousSibling); + + // We've now reached or gone past the boundary of the text range we're interested in + // so have identified the node we want + boundaryNode = workingNode.nextSibling; + + if (comparison == -1 && boundaryNode && dom.isCharacterDataNode(boundaryNode)) { + // This is a character data node (text, comment, cdata). The working range is collapsed at the start of the + // node containing the text range's boundary, so we move the end of the working range to the boundary point + // and measure the length of its text to get the boundary's offset within the node. + workingRange.setEndPoint(isStart ? "EndToStart" : "EndToEnd", textRange); + + + var offset; + + if (/[\r\n]/.test(boundaryNode.data)) { + /* + For the particular case of a boundary within a text node containing line breaks (within a
     element,
    +                for example), we need a slightly complicated approach to get the boundary's offset in IE. The facts:
    +
    +                - Each line break is represented as \r in the text node's data/nodeValue properties
    +                - Each line break is represented as \r\n in the TextRange's 'text' property
    +                - The 'text' property of the TextRange does not contain trailing line breaks
    +
    +                To get round the problem presented by the final fact above, we can use the fact that TextRange's
    +                moveStart() and moveEnd() methods return the actual number of characters moved, which is not necessarily
    +                the same as the number of characters it was instructed to move. The simplest approach is to use this to
    +                store the characters moved when moving both the start and end of the range to the start of the document
    +                body and subtracting the start offset from the end offset (the "move-negative-gazillion" method).
    +                However, this is extremely slow when the document is large and the range is near the end of it. Clearly
    +                doing the mirror image (i.e. moving the range boundaries to the end of the document) has the same
    +                problem.
    +
    +                Another approach that works is to use moveStart() to move the start boundary of the range up to the end
    +                boundary one character at a time and incrementing a counter with the value returned by the moveStart()
    +                call. However, the check for whether the start boundary has reached the end boundary is expensive, so
    +                this method is slow (although unlike "move-negative-gazillion" is largely unaffected by the location of
    +                the range within the document).
    +
    +                The method below is a hybrid of the two methods above. It uses the fact that a string containing the
    +                TextRange's 'text' property with each \r\n converted to a single \r character cannot be longer than the
    +                text of the TextRange, so the start of the range is moved that length initially and then a character at
    +                a time to make up for any trailing line breaks not contained in the 'text' property. This has good
    +                performance in most situations compared to the previous two methods.
    +                */
    +                var tempRange = workingRange.duplicate();
    +                var rangeLength = tempRange.text.replace(/\r\n/g, "\r").length;
    +
    +                offset = tempRange.moveStart("character", rangeLength);
    +                while ( (comparison = tempRange.compareEndPoints("StartToEnd", tempRange)) == -1) {
    +                    offset++;
    +                    tempRange.moveStart("character", 1);
    +                }
    +            } else {
    +                offset = workingRange.text.length;
    +            }
    +            boundaryPosition = new DomPosition(boundaryNode, offset);
    +        } else {
    +
    +
    +            // If the boundary immediately follows a character data node and this is the end boundary, we should favour
    +            // a position within that, and likewise for a start boundary preceding a character data node
    +            previousNode = (isCollapsed || !isStart) && workingNode.previousSibling;
    +            nextNode = (isCollapsed || isStart) && workingNode.nextSibling;
    +
    +
    +
    +            if (nextNode && dom.isCharacterDataNode(nextNode)) {
    +                boundaryPosition = new DomPosition(nextNode, 0);
    +            } else if (previousNode && dom.isCharacterDataNode(previousNode)) {
    +                boundaryPosition = new DomPosition(previousNode, previousNode.length);
    +            } else {
    +                boundaryPosition = new DomPosition(containerElement, dom.getNodeIndex(workingNode));
    +            }
    +        }
    +
    +        // Clean up
    +        workingNode.parentNode.removeChild(workingNode);
    +
    +        return boundaryPosition;
    +    }
    +
    +    // Returns a TextRange representing the boundary of a TextRange expressed as a node and an offset within that node.
    +    // This function started out as an optimized version of code found in Tim Cameron Ryan's IERange
    +    // (http://code.google.com/p/ierange/)
    +    function createBoundaryTextRange(boundaryPosition, isStart) {
    +        var boundaryNode, boundaryParent, boundaryOffset = boundaryPosition.offset;
    +        var doc = dom.getDocument(boundaryPosition.node);
    +        var workingNode, childNodes, workingRange = doc.body.createTextRange();
    +        var nodeIsDataNode = dom.isCharacterDataNode(boundaryPosition.node);
    +
    +        if (nodeIsDataNode) {
    +            boundaryNode = boundaryPosition.node;
    +            boundaryParent = boundaryNode.parentNode;
    +        } else {
    +            childNodes = boundaryPosition.node.childNodes;
    +            boundaryNode = (boundaryOffset < childNodes.length) ? childNodes[boundaryOffset] : null;
    +            boundaryParent = boundaryPosition.node;
    +        }
    +
    +        // Position the range immediately before the node containing the boundary
    +        workingNode = doc.createElement("span");
    +
    +        // Making the working element non-empty element persuades IE to consider the TextRange boundary to be within the
    +        // element rather than immediately before or after it, which is what we want
    +        workingNode.innerHTML = "&#feff;";
    +
    +        // insertBefore is supposed to work like appendChild if the second parameter is null. However, a bug report
    +        // for IERange suggests that it can crash the browser: http://code.google.com/p/ierange/issues/detail?id=12
    +        if (boundaryNode) {
    +            boundaryParent.insertBefore(workingNode, boundaryNode);
    +        } else {
    +            boundaryParent.appendChild(workingNode);
    +        }
    +
    +        workingRange.moveToElementText(workingNode);
    +        workingRange.collapse(!isStart);
    +
    +        // Clean up
    +        boundaryParent.removeChild(workingNode);
    +
    +        // Move the working range to the text offset, if required
    +        if (nodeIsDataNode) {
    +            workingRange[isStart ? "moveStart" : "moveEnd"]("character", boundaryOffset);
    +        }
    +
    +        return workingRange;
    +    }
    +
    +    /*----------------------------------------------------------------------------------------------------------------*/
    +
    +    if (api.features.implementsDomRange && (!api.features.implementsTextRange || !api.config.preferTextRange)) {
    +        // This is a wrapper around the browser's native DOM Range. It has two aims:
    +        // - Provide workarounds for specific browser bugs
    +        // - provide convenient extensions, which are inherited from Rangy's DomRange
    +
    +        (function() {
    +            var rangeProto;
    +            var rangeProperties = DomRange.rangeProperties;
    +            var canSetRangeStartAfterEnd;
    +
    +            function updateRangeProperties(range) {
    +                var i = rangeProperties.length, prop;
    +                while (i--) {
    +                    prop = rangeProperties[i];
    +                    range[prop] = range.nativeRange[prop];
    +                }
    +            }
    +
    +            function updateNativeRange(range, startContainer, startOffset, endContainer,endOffset) {
    +                var startMoved = (range.startContainer !== startContainer || range.startOffset != startOffset);
    +                var endMoved = (range.endContainer !== endContainer || range.endOffset != endOffset);
    +
    +                // Always set both boundaries for the benefit of IE9 (see issue 35)
    +                if (startMoved || endMoved) {
    +                    range.setEnd(endContainer, endOffset);
    +                    range.setStart(startContainer, startOffset);
    +                }
    +            }
    +
    +            function detach(range) {
    +                range.nativeRange.detach();
    +                range.detached = true;
    +                var i = rangeProperties.length, prop;
    +                while (i--) {
    +                    prop = rangeProperties[i];
    +                    range[prop] = null;
    +                }
    +            }
    +
    +            var createBeforeAfterNodeSetter;
    +
    +            WrappedRange = function(range) {
    +                if (!range) {
    +                    throw new Error("Range must be specified");
    +                }
    +                this.nativeRange = range;
    +                updateRangeProperties(this);
    +            };
    +
    +            DomRange.createPrototypeRange(WrappedRange, updateNativeRange, detach);
    +
    +            rangeProto = WrappedRange.prototype;
    +
    +            rangeProto.selectNode = function(node) {
    +                this.nativeRange.selectNode(node);
    +                updateRangeProperties(this);
    +            };
    +
    +            rangeProto.deleteContents = function() {
    +                this.nativeRange.deleteContents();
    +                updateRangeProperties(this);
    +            };
    +
    +            rangeProto.extractContents = function() {
    +                var frag = this.nativeRange.extractContents();
    +                updateRangeProperties(this);
    +                return frag;
    +            };
    +
    +            rangeProto.cloneContents = function() {
    +                return this.nativeRange.cloneContents();
    +            };
    +
    +            // TODO: Until I can find a way to programmatically trigger the Firefox bug (apparently long-standing, still
    +            // present in 3.6.8) that throws "Index or size is negative or greater than the allowed amount" for
    +            // insertNode in some circumstances, all browsers will have to use the Rangy's own implementation of
    +            // insertNode, which works but is almost certainly slower than the native implementation.
    +/*
    +            rangeProto.insertNode = function(node) {
    +                this.nativeRange.insertNode(node);
    +                updateRangeProperties(this);
    +            };
    +*/
    +
    +            rangeProto.surroundContents = function(node) {
    +                this.nativeRange.surroundContents(node);
    +                updateRangeProperties(this);
    +            };
    +
    +            rangeProto.collapse = function(isStart) {
    +                this.nativeRange.collapse(isStart);
    +                updateRangeProperties(this);
    +            };
    +
    +            rangeProto.cloneRange = function() {
    +                return new WrappedRange(this.nativeRange.cloneRange());
    +            };
    +
    +            rangeProto.refresh = function() {
    +                updateRangeProperties(this);
    +            };
    +
    +            rangeProto.toString = function() {
    +                return this.nativeRange.toString();
    +            };
    +
    +            // Create test range and node for feature detection
    +
    +            var testTextNode = document.createTextNode("test");
    +            dom.getBody(document).appendChild(testTextNode);
    +            var range = document.createRange();
    +
    +            /*--------------------------------------------------------------------------------------------------------*/
    +
    +            // Test for Firefox 2 bug that prevents moving the start of a Range to a point after its current end and
    +            // correct for it
    +
    +            range.setStart(testTextNode, 0);
    +            range.setEnd(testTextNode, 0);
    +
    +            try {
    +                range.setStart(testTextNode, 1);
    +                canSetRangeStartAfterEnd = true;
    +
    +                rangeProto.setStart = function(node, offset) {
    +                    this.nativeRange.setStart(node, offset);
    +                    updateRangeProperties(this);
    +                };
    +
    +                rangeProto.setEnd = function(node, offset) {
    +                    this.nativeRange.setEnd(node, offset);
    +                    updateRangeProperties(this);
    +                };
    +
    +                createBeforeAfterNodeSetter = function(name) {
    +                    return function(node) {
    +                        this.nativeRange[name](node);
    +                        updateRangeProperties(this);
    +                    };
    +                };
    +
    +            } catch(ex) {
    +
    +
    +                canSetRangeStartAfterEnd = false;
    +
    +                rangeProto.setStart = function(node, offset) {
    +                    try {
    +                        this.nativeRange.setStart(node, offset);
    +                    } catch (ex) {
    +                        this.nativeRange.setEnd(node, offset);
    +                        this.nativeRange.setStart(node, offset);
    +                    }
    +                    updateRangeProperties(this);
    +                };
    +
    +                rangeProto.setEnd = function(node, offset) {
    +                    try {
    +                        this.nativeRange.setEnd(node, offset);
    +                    } catch (ex) {
    +                        this.nativeRange.setStart(node, offset);
    +                        this.nativeRange.setEnd(node, offset);
    +                    }
    +                    updateRangeProperties(this);
    +                };
    +
    +                createBeforeAfterNodeSetter = function(name, oppositeName) {
    +                    return function(node) {
    +                        try {
    +                            this.nativeRange[name](node);
    +                        } catch (ex) {
    +                            this.nativeRange[oppositeName](node);
    +                            this.nativeRange[name](node);
    +                        }
    +                        updateRangeProperties(this);
    +                    };
    +                };
    +            }
    +
    +            rangeProto.setStartBefore = createBeforeAfterNodeSetter("setStartBefore", "setEndBefore");
    +            rangeProto.setStartAfter = createBeforeAfterNodeSetter("setStartAfter", "setEndAfter");
    +            rangeProto.setEndBefore = createBeforeAfterNodeSetter("setEndBefore", "setStartBefore");
    +            rangeProto.setEndAfter = createBeforeAfterNodeSetter("setEndAfter", "setStartAfter");
    +
    +            /*--------------------------------------------------------------------------------------------------------*/
    +
    +            // Test for and correct Firefox 2 behaviour with selectNodeContents on text nodes: it collapses the range to
    +            // the 0th character of the text node
    +            range.selectNodeContents(testTextNode);
    +            if (range.startContainer == testTextNode && range.endContainer == testTextNode &&
    +                    range.startOffset == 0 && range.endOffset == testTextNode.length) {
    +                rangeProto.selectNodeContents = function(node) {
    +                    this.nativeRange.selectNodeContents(node);
    +                    updateRangeProperties(this);
    +                };
    +            } else {
    +                rangeProto.selectNodeContents = function(node) {
    +                    this.setStart(node, 0);
    +                    this.setEnd(node, DomRange.getEndOffset(node));
    +                };
    +            }
    +
    +            /*--------------------------------------------------------------------------------------------------------*/
    +
    +            // Test for WebKit bug that has the beahviour of compareBoundaryPoints round the wrong way for constants
    +            // START_TO_END and END_TO_START: https://bugs.webkit.org/show_bug.cgi?id=20738
    +
    +            range.selectNodeContents(testTextNode);
    +            range.setEnd(testTextNode, 3);
    +
    +            var range2 = document.createRange();
    +            range2.selectNodeContents(testTextNode);
    +            range2.setEnd(testTextNode, 4);
    +            range2.setStart(testTextNode, 2);
    +
    +            if (range.compareBoundaryPoints(range.START_TO_END, range2) == -1 &
    +                    range.compareBoundaryPoints(range.END_TO_START, range2) == 1) {
    +                // This is the wrong way round, so correct for it
    +
    +
    +                rangeProto.compareBoundaryPoints = function(type, range) {
    +                    range = range.nativeRange || range;
    +                    if (type == range.START_TO_END) {
    +                        type = range.END_TO_START;
    +                    } else if (type == range.END_TO_START) {
    +                        type = range.START_TO_END;
    +                    }
    +                    return this.nativeRange.compareBoundaryPoints(type, range);
    +                };
    +            } else {
    +                rangeProto.compareBoundaryPoints = function(type, range) {
    +                    return this.nativeRange.compareBoundaryPoints(type, range.nativeRange || range);
    +                };
    +            }
    +
    +            /*--------------------------------------------------------------------------------------------------------*/
    +
    +            // Test for existence of createContextualFragment and delegate to it if it exists
    +            if (api.util.isHostMethod(range, "createContextualFragment")) {
    +                rangeProto.createContextualFragment = function(fragmentStr) {
    +                    return this.nativeRange.createContextualFragment(fragmentStr);
    +                };
    +            }
    +
    +            /*--------------------------------------------------------------------------------------------------------*/
    +
    +            // Clean up
    +            dom.getBody(document).removeChild(testTextNode);
    +            range.detach();
    +            range2.detach();
    +        })();
    +
    +        api.createNativeRange = function(doc) {
    +            doc = doc || document;
    +            return doc.createRange();
    +        };
    +    } else if (api.features.implementsTextRange) {
    +        // This is a wrapper around a TextRange, providing full DOM Range functionality using rangy's DomRange as a
    +        // prototype
    +
    +        WrappedRange = function(textRange) {
    +            this.textRange = textRange;
    +            this.refresh();
    +        };
    +
    +        WrappedRange.prototype = new DomRange(document);
    +
    +        WrappedRange.prototype.refresh = function() {
    +            var start, end;
    +
    +            // TextRange's parentElement() method cannot be trusted. getTextRangeContainerElement() works around that.
    +            var rangeContainerElement = getTextRangeContainerElement(this.textRange);
    +
    +            if (textRangeIsCollapsed(this.textRange)) {
    +                end = start = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, true, true);
    +            } else {
    +
    +                start = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, true, false);
    +                end = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, false, false);
    +            }
    +
    +            this.setStart(start.node, start.offset);
    +            this.setEnd(end.node, end.offset);
    +        };
    +
    +        DomRange.copyComparisonConstants(WrappedRange);
    +
    +        // Add WrappedRange as the Range property of the global object to allow expression like Range.END_TO_END to work
    +        var globalObj = (function() { return this; })();
    +        if (typeof globalObj.Range == "undefined") {
    +            globalObj.Range = WrappedRange;
    +        }
    +
    +        api.createNativeRange = function(doc) {
    +            doc = doc || document;
    +            return doc.body.createTextRange();
    +        };
    +    }
    +
    +    if (api.features.implementsTextRange) {
    +        WrappedRange.rangeToTextRange = function(range) {
    +            if (range.collapsed) {
    +                var tr = createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true);
    +
    +
    +
    +                return tr;
    +
    +                //return createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true);
    +            } else {
    +                var startRange = createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true);
    +                var endRange = createBoundaryTextRange(new DomPosition(range.endContainer, range.endOffset), false);
    +                var textRange = dom.getDocument(range.startContainer).body.createTextRange();
    +                textRange.setEndPoint("StartToStart", startRange);
    +                textRange.setEndPoint("EndToEnd", endRange);
    +                return textRange;
    +            }
    +        };
    +    }
    +
    +    WrappedRange.prototype.getName = function() {
    +        return "WrappedRange";
    +    };
    +
    +    api.WrappedRange = WrappedRange;
    +
    +    api.createRange = function(doc) {
    +        doc = doc || document;
    +        return new WrappedRange(api.createNativeRange(doc));
    +    };
    +
    +    api.createRangyRange = function(doc) {
    +        doc = doc || document;
    +        return new DomRange(doc);
    +    };
    +
    +    api.createIframeRange = function(iframeEl) {
    +        return api.createRange(dom.getIframeDocument(iframeEl));
    +    };
    +
    +    api.createIframeRangyRange = function(iframeEl) {
    +        return api.createRangyRange(dom.getIframeDocument(iframeEl));
    +    };
    +
    +    api.addCreateMissingNativeApiListener(function(win) {
    +        var doc = win.document;
    +        if (typeof doc.createRange == "undefined") {
    +            doc.createRange = function() {
    +                return api.createRange(this);
    +            };
    +        }
    +        doc = win = null;
    +    });
    +});rangy.createModule("WrappedSelection", function(api, module) {
    +    // This will create a selection object wrapper that follows the Selection object found in the WHATWG draft DOM Range
    +    // spec (http://html5.org/specs/dom-range.html)
    +
    +    api.requireModules( ["DomUtil", "DomRange", "WrappedRange"] );
    +
    +    api.config.checkSelectionRanges = true;
    +
    +    var BOOLEAN = "boolean",
    +        windowPropertyName = "_rangySelection",
    +        dom = api.dom,
    +        util = api.util,
    +        DomRange = api.DomRange,
    +        WrappedRange = api.WrappedRange,
    +        DOMException = api.DOMException,
    +        DomPosition = dom.DomPosition,
    +        getSelection,
    +        selectionIsCollapsed,
    +        CONTROL = "Control";
    +
    +
    +
    +    function getWinSelection(winParam) {
    +        return (winParam || window).getSelection();
    +    }
    +
    +    function getDocSelection(winParam) {
    +        return (winParam || window).document.selection;
    +    }
    +
    +    // Test for the Range/TextRange and Selection features required
    +    // Test for ability to retrieve selection
    +    var implementsWinGetSelection = api.util.isHostMethod(window, "getSelection"),
    +        implementsDocSelection = api.util.isHostObject(document, "selection");
    +
    +    var useDocumentSelection = implementsDocSelection && (!implementsWinGetSelection || api.config.preferTextRange);
    +
    +    if (useDocumentSelection) {
    +        getSelection = getDocSelection;
    +        api.isSelectionValid = function(winParam) {
    +            var doc = (winParam || window).document, nativeSel = doc.selection;
    +
    +            // Check whether the selection TextRange is actually contained within the correct document
    +            return (nativeSel.type != "None" || dom.getDocument(nativeSel.createRange().parentElement()) == doc);
    +        };
    +    } else if (implementsWinGetSelection) {
    +        getSelection = getWinSelection;
    +        api.isSelectionValid = function() {
    +            return true;
    +        };
    +    } else {
    +        module.fail("Neither document.selection or window.getSelection() detected.");
    +    }
    +
    +    api.getNativeSelection = getSelection;
    +
    +    var testSelection = getSelection();
    +    var testRange = api.createNativeRange(document);
    +    var body = dom.getBody(document);
    +
    +    // Obtaining a range from a selection
    +    var selectionHasAnchorAndFocus = util.areHostObjects(testSelection, ["anchorNode", "focusNode"] &&
    +                                     util.areHostProperties(testSelection, ["anchorOffset", "focusOffset"]));
    +    api.features.selectionHasAnchorAndFocus = selectionHasAnchorAndFocus;
    +
    +    // Test for existence of native selection extend() method
    +    var selectionHasExtend = util.isHostMethod(testSelection, "extend");
    +    api.features.selectionHasExtend = selectionHasExtend;
    +
    +    // Test if rangeCount exists
    +    var selectionHasRangeCount = (typeof testSelection.rangeCount == "number");
    +    api.features.selectionHasRangeCount = selectionHasRangeCount;
    +
    +    var selectionSupportsMultipleRanges = false;
    +    var collapsedNonEditableSelectionsSupported = true;
    +
    +    if (util.areHostMethods(testSelection, ["addRange", "getRangeAt", "removeAllRanges"]) &&
    +            typeof testSelection.rangeCount == "number" && api.features.implementsDomRange) {
    +
    +        (function() {
    +            var iframe = document.createElement("iframe");
    +            body.appendChild(iframe);
    +
    +            var iframeDoc = dom.getIframeDocument(iframe);
    +            iframeDoc.open();
    +            iframeDoc.write("12");
    +            iframeDoc.close();
    +
    +            var sel = dom.getIframeWindow(iframe).getSelection();
    +            var docEl = iframeDoc.documentElement;
    +            var iframeBody = docEl.lastChild, textNode = iframeBody.firstChild;
    +
    +            // Test whether the native selection will allow a collapsed selection within a non-editable element
    +            var r1 = iframeDoc.createRange();
    +            r1.setStart(textNode, 1);
    +            r1.collapse(true);
    +            sel.addRange(r1);
    +            collapsedNonEditableSelectionsSupported = (sel.rangeCount == 1);
    +            sel.removeAllRanges();
    +
    +            // Test whether the native selection is capable of supporting multiple ranges
    +            var r2 = r1.cloneRange();
    +            r1.setStart(textNode, 0);
    +            r2.setEnd(textNode, 2);
    +            sel.addRange(r1);
    +            sel.addRange(r2);
    +
    +            selectionSupportsMultipleRanges = (sel.rangeCount == 2);
    +
    +            // Clean up
    +            r1.detach();
    +            r2.detach();
    +
    +            body.removeChild(iframe);
    +        })();
    +    }
    +
    +    api.features.selectionSupportsMultipleRanges = selectionSupportsMultipleRanges;
    +    api.features.collapsedNonEditableSelectionsSupported = collapsedNonEditableSelectionsSupported;
    +
    +    // ControlRanges
    +    var implementsControlRange = false, testControlRange;
    +
    +    if (body && util.isHostMethod(body, "createControlRange")) {
    +        testControlRange = body.createControlRange();
    +        if (util.areHostProperties(testControlRange, ["item", "add"])) {
    +            implementsControlRange = true;
    +        }
    +    }
    +    api.features.implementsControlRange = implementsControlRange;
    +
    +    // Selection collapsedness
    +    if (selectionHasAnchorAndFocus) {
    +        selectionIsCollapsed = function(sel) {
    +            return sel.anchorNode === sel.focusNode && sel.anchorOffset === sel.focusOffset;
    +        };
    +    } else {
    +        selectionIsCollapsed = function(sel) {
    +            return sel.rangeCount ? sel.getRangeAt(sel.rangeCount - 1).collapsed : false;
    +        };
    +    }
    +
    +    function updateAnchorAndFocusFromRange(sel, range, backwards) {
    +        var anchorPrefix = backwards ? "end" : "start", focusPrefix = backwards ? "start" : "end";
    +        sel.anchorNode = range[anchorPrefix + "Container"];
    +        sel.anchorOffset = range[anchorPrefix + "Offset"];
    +        sel.focusNode = range[focusPrefix + "Container"];
    +        sel.focusOffset = range[focusPrefix + "Offset"];
    +    }
    +
    +    function updateAnchorAndFocusFromNativeSelection(sel) {
    +        var nativeSel = sel.nativeSelection;
    +        sel.anchorNode = nativeSel.anchorNode;
    +        sel.anchorOffset = nativeSel.anchorOffset;
    +        sel.focusNode = nativeSel.focusNode;
    +        sel.focusOffset = nativeSel.focusOffset;
    +    }
    +
    +    function updateEmptySelection(sel) {
    +        sel.anchorNode = sel.focusNode = null;
    +        sel.anchorOffset = sel.focusOffset = 0;
    +        sel.rangeCount = 0;
    +        sel.isCollapsed = true;
    +        sel._ranges.length = 0;
    +    }
    +
    +    function getNativeRange(range) {
    +        var nativeRange;
    +        if (range instanceof DomRange) {
    +            nativeRange = range._selectionNativeRange;
    +            if (!nativeRange) {
    +                nativeRange = api.createNativeRange(dom.getDocument(range.startContainer));
    +                nativeRange.setEnd(range.endContainer, range.endOffset);
    +                nativeRange.setStart(range.startContainer, range.startOffset);
    +                range._selectionNativeRange = nativeRange;
    +                range.attachListener("detach", function() {
    +
    +                    this._selectionNativeRange = null;
    +                });
    +            }
    +        } else if (range instanceof WrappedRange) {
    +            nativeRange = range.nativeRange;
    +        } else if (api.features.implementsDomRange && (range instanceof dom.getWindow(range.startContainer).Range)) {
    +            nativeRange = range;
    +        }
    +        return nativeRange;
    +    }
    +
    +    function rangeContainsSingleElement(rangeNodes) {
    +        if (!rangeNodes.length || rangeNodes[0].nodeType != 1) {
    +            return false;
    +        }
    +        for (var i = 1, len = rangeNodes.length; i < len; ++i) {
    +            if (!dom.isAncestorOf(rangeNodes[0], rangeNodes[i])) {
    +                return false;
    +            }
    +        }
    +        return true;
    +    }
    +
    +    function getSingleElementFromRange(range) {
    +        var nodes = range.getNodes();
    +        if (!rangeContainsSingleElement(nodes)) {
    +            throw new Error("getSingleElementFromRange: range " + range.inspect() + " did not consist of a single element");
    +        }
    +        return nodes[0];
    +    }
    +
    +    function isTextRange(range) {
    +        return !!range && typeof range.text != "undefined";
    +    }
    +
    +    function updateFromTextRange(sel, range) {
    +        // Create a Range from the selected TextRange
    +        var wrappedRange = new WrappedRange(range);
    +        sel._ranges = [wrappedRange];
    +
    +        updateAnchorAndFocusFromRange(sel, wrappedRange, false);
    +        sel.rangeCount = 1;
    +        sel.isCollapsed = wrappedRange.collapsed;
    +    }
    +
    +    function updateControlSelection(sel) {
    +        // Update the wrapped selection based on what's now in the native selection
    +        sel._ranges.length = 0;
    +        if (sel.docSelection.type == "None") {
    +            updateEmptySelection(sel);
    +        } else {
    +            var controlRange = sel.docSelection.createRange();
    +            if (isTextRange(controlRange)) {
    +                // This case (where the selection type is "Control" and calling createRange() on the selection returns
    +                // a TextRange) can happen in IE 9. It happens, for example, when all elements in the selected
    +                // ControlRange have been removed from the ControlRange and removed from the document.
    +                updateFromTextRange(sel, controlRange);
    +            } else {
    +                sel.rangeCount = controlRange.length;
    +                var range, doc = dom.getDocument(controlRange.item(0));
    +                for (var i = 0; i < sel.rangeCount; ++i) {
    +                    range = api.createRange(doc);
    +                    range.selectNode(controlRange.item(i));
    +                    sel._ranges.push(range);
    +                }
    +                sel.isCollapsed = sel.rangeCount == 1 && sel._ranges[0].collapsed;
    +                updateAnchorAndFocusFromRange(sel, sel._ranges[sel.rangeCount - 1], false);
    +            }
    +        }
    +    }
    +
    +    function addRangeToControlSelection(sel, range) {
    +        var controlRange = sel.docSelection.createRange();
    +        var rangeElement = getSingleElementFromRange(range);
    +
    +        // Create a new ControlRange containing all the elements in the selected ControlRange plus the element
    +        // contained by the supplied range
    +        var doc = dom.getDocument(controlRange.item(0));
    +        var newControlRange = dom.getBody(doc).createControlRange();
    +        for (var i = 0, len = controlRange.length; i < len; ++i) {
    +            newControlRange.add(controlRange.item(i));
    +        }
    +        try {
    +            newControlRange.add(rangeElement);
    +        } catch (ex) {
    +            throw new Error("addRange(): Element within the specified Range could not be added to control selection (does it have layout?)");
    +        }
    +        newControlRange.select();
    +
    +        // Update the wrapped selection based on what's now in the native selection
    +        updateControlSelection(sel);
    +    }
    +
    +    var getSelectionRangeAt;
    +
    +    if (util.isHostMethod(testSelection,  "getRangeAt")) {
    +        getSelectionRangeAt = function(sel, index) {
    +            try {
    +                return sel.getRangeAt(index);
    +            } catch(ex) {
    +                return null;
    +            }
    +        };
    +    } else if (selectionHasAnchorAndFocus) {
    +        getSelectionRangeAt = function(sel) {
    +            var doc = dom.getDocument(sel.anchorNode);
    +            var range = api.createRange(doc);
    +            range.setStart(sel.anchorNode, sel.anchorOffset);
    +            range.setEnd(sel.focusNode, sel.focusOffset);
    +
    +            // Handle the case when the selection was selected backwards (from the end to the start in the
    +            // document)
    +            if (range.collapsed !== this.isCollapsed) {
    +                range.setStart(sel.focusNode, sel.focusOffset);
    +                range.setEnd(sel.anchorNode, sel.anchorOffset);
    +            }
    +
    +            return range;
    +        };
    +    }
    +
    +    /**
    +     * @constructor
    +     */
    +    function WrappedSelection(selection, docSelection, win) {
    +        this.nativeSelection = selection;
    +        this.docSelection = docSelection;
    +        this._ranges = [];
    +        this.win = win;
    +        this.refresh();
    +    }
    +
    +    api.getSelection = function(win) {
    +        win = win || window;
    +        var sel = win[windowPropertyName];
    +        var nativeSel = getSelection(win), docSel = implementsDocSelection ? getDocSelection(win) : null;
    +        if (sel) {
    +            sel.nativeSelection = nativeSel;
    +            sel.docSelection = docSel;
    +            sel.refresh(win);
    +        } else {
    +            sel = new WrappedSelection(nativeSel, docSel, win);
    +            win[windowPropertyName] = sel;
    +        }
    +        return sel;
    +    };
    +
    +    api.getIframeSelection = function(iframeEl) {
    +        return api.getSelection(dom.getIframeWindow(iframeEl));
    +    };
    +
    +    var selProto = WrappedSelection.prototype;
    +
    +    function createControlSelection(sel, ranges) {
    +        // Ensure that the selection becomes of type "Control"
    +        var doc = dom.getDocument(ranges[0].startContainer);
    +        var controlRange = dom.getBody(doc).createControlRange();
    +        for (var i = 0, el; i < rangeCount; ++i) {
    +            el = getSingleElementFromRange(ranges[i]);
    +            try {
    +                controlRange.add(el);
    +            } catch (ex) {
    +                throw new Error("setRanges(): Element within the one of the specified Ranges could not be added to control selection (does it have layout?)");
    +            }
    +        }
    +        controlRange.select();
    +
    +        // Update the wrapped selection based on what's now in the native selection
    +        updateControlSelection(sel);
    +    }
    +
    +    // Selecting a range
    +    if (!useDocumentSelection && selectionHasAnchorAndFocus && util.areHostMethods(testSelection, ["removeAllRanges", "addRange"])) {
    +        selProto.removeAllRanges = function() {
    +            this.nativeSelection.removeAllRanges();
    +            updateEmptySelection(this);
    +        };
    +
    +        var addRangeBackwards = function(sel, range) {
    +            var doc = DomRange.getRangeDocument(range);
    +            var endRange = api.createRange(doc);
    +            endRange.collapseToPoint(range.endContainer, range.endOffset);
    +            sel.nativeSelection.addRange(getNativeRange(endRange));
    +            sel.nativeSelection.extend(range.startContainer, range.startOffset);
    +            sel.refresh();
    +        };
    +
    +        if (selectionHasRangeCount) {
    +            selProto.addRange = function(range, backwards) {
    +                if (implementsControlRange && implementsDocSelection && this.docSelection.type == CONTROL) {
    +                    addRangeToControlSelection(this, range);
    +                } else {
    +                    if (backwards && selectionHasExtend) {
    +                        addRangeBackwards(this, range);
    +                    } else {
    +                        var previousRangeCount;
    +                        if (selectionSupportsMultipleRanges) {
    +                            previousRangeCount = this.rangeCount;
    +                        } else {
    +                            this.removeAllRanges();
    +                            previousRangeCount = 0;
    +                        }
    +                        this.nativeSelection.addRange(getNativeRange(range));
    +
    +                        // Check whether adding the range was successful
    +                        this.rangeCount = this.nativeSelection.rangeCount;
    +
    +                        if (this.rangeCount == previousRangeCount + 1) {
    +                            // The range was added successfully
    +
    +                            // Check whether the range that we added to the selection is reflected in the last range extracted from
    +                            // the selection
    +                            if (api.config.checkSelectionRanges) {
    +                                var nativeRange = getSelectionRangeAt(this.nativeSelection, this.rangeCount - 1);
    +                                if (nativeRange && !DomRange.rangesEqual(nativeRange, range)) {
    +                                    // Happens in WebKit with, for example, a selection placed at the start of a text node
    +                                    range = new WrappedRange(nativeRange);
    +                                }
    +                            }
    +                            this._ranges[this.rangeCount - 1] = range;
    +                            updateAnchorAndFocusFromRange(this, range, selectionIsBackwards(this.nativeSelection));
    +                            this.isCollapsed = selectionIsCollapsed(this);
    +                        } else {
    +                            // The range was not added successfully. The simplest thing is to refresh
    +                            this.refresh();
    +                        }
    +                    }
    +                }
    +            };
    +        } else {
    +            selProto.addRange = function(range, backwards) {
    +                if (backwards && selectionHasExtend) {
    +                    addRangeBackwards(this, range);
    +                } else {
    +                    this.nativeSelection.addRange(getNativeRange(range));
    +                    this.refresh();
    +                }
    +            };
    +        }
    +
    +        selProto.setRanges = function(ranges) {
    +            if (implementsControlRange && ranges.length > 1) {
    +                createControlSelection(this, ranges);
    +            } else {
    +                this.removeAllRanges();
    +                for (var i = 0, len = ranges.length; i < len; ++i) {
    +                    this.addRange(ranges[i]);
    +                }
    +            }
    +        };
    +    } else if (util.isHostMethod(testSelection, "empty") && util.isHostMethod(testRange, "select") &&
    +               implementsControlRange && useDocumentSelection) {
    +
    +        selProto.removeAllRanges = function() {
    +            // Added try/catch as fix for issue #21
    +            try {
    +                this.docSelection.empty();
    +
    +                // Check for empty() not working (issue #24)
    +                if (this.docSelection.type != "None") {
    +                    // Work around failure to empty a control selection by instead selecting a TextRange and then
    +                    // calling empty()
    +                    var doc;
    +                    if (this.anchorNode) {
    +                        doc = dom.getDocument(this.anchorNode);
    +                    } else if (this.docSelection.type == CONTROL) {
    +                        var controlRange = this.docSelection.createRange();
    +                        if (controlRange.length) {
    +                            doc = dom.getDocument(controlRange.item(0)).body.createTextRange();
    +                        }
    +                    }
    +                    if (doc) {
    +                        var textRange = doc.body.createTextRange();
    +                        textRange.select();
    +                        this.docSelection.empty();
    +                    }
    +                }
    +            } catch(ex) {}
    +            updateEmptySelection(this);
    +        };
    +
    +        selProto.addRange = function(range) {
    +            if (this.docSelection.type == CONTROL) {
    +                addRangeToControlSelection(this, range);
    +            } else {
    +                WrappedRange.rangeToTextRange(range).select();
    +                this._ranges[0] = range;
    +                this.rangeCount = 1;
    +                this.isCollapsed = this._ranges[0].collapsed;
    +                updateAnchorAndFocusFromRange(this, range, false);
    +            }
    +        };
    +
    +        selProto.setRanges = function(ranges) {
    +            this.removeAllRanges();
    +            var rangeCount = ranges.length;
    +            if (rangeCount > 1) {
    +                createControlSelection(this, ranges);
    +            } else if (rangeCount) {
    +                this.addRange(ranges[0]);
    +            }
    +        };
    +    } else {
    +        module.fail("No means of selecting a Range or TextRange was found");
    +        return false;
    +    }
    +
    +    selProto.getRangeAt = function(index) {
    +        if (index < 0 || index >= this.rangeCount) {
    +            throw new DOMException("INDEX_SIZE_ERR");
    +        } else {
    +            return this._ranges[index];
    +        }
    +    };
    +
    +    var refreshSelection;
    +
    +    if (useDocumentSelection) {
    +        refreshSelection = function(sel) {
    +            var range;
    +            if (api.isSelectionValid(sel.win)) {
    +                range = sel.docSelection.createRange();
    +            } else {
    +                range = dom.getBody(sel.win.document).createTextRange();
    +                range.collapse(true);
    +            }
    +
    +
    +            if (sel.docSelection.type == CONTROL) {
    +                updateControlSelection(sel);
    +            } else if (isTextRange(range)) {
    +                updateFromTextRange(sel, range);
    +            } else {
    +                updateEmptySelection(sel);
    +            }
    +        };
    +    } else if (util.isHostMethod(testSelection, "getRangeAt") && typeof testSelection.rangeCount == "number") {
    +        refreshSelection = function(sel) {
    +            if (implementsControlRange && implementsDocSelection && sel.docSelection.type == CONTROL) {
    +                updateControlSelection(sel);
    +            } else {
    +                sel._ranges.length = sel.rangeCount = sel.nativeSelection.rangeCount;
    +                if (sel.rangeCount) {
    +                    for (var i = 0, len = sel.rangeCount; i < len; ++i) {
    +                        sel._ranges[i] = new api.WrappedRange(sel.nativeSelection.getRangeAt(i));
    +                    }
    +                    updateAnchorAndFocusFromRange(sel, sel._ranges[sel.rangeCount - 1], selectionIsBackwards(sel.nativeSelection));
    +                    sel.isCollapsed = selectionIsCollapsed(sel);
    +                } else {
    +                    updateEmptySelection(sel);
    +                }
    +            }
    +        };
    +    } else if (selectionHasAnchorAndFocus && typeof testSelection.isCollapsed == BOOLEAN && typeof testRange.collapsed == BOOLEAN && api.features.implementsDomRange) {
    +        refreshSelection = function(sel) {
    +            var range, nativeSel = sel.nativeSelection;
    +            if (nativeSel.anchorNode) {
    +                range = getSelectionRangeAt(nativeSel, 0);
    +                sel._ranges = [range];
    +                sel.rangeCount = 1;
    +                updateAnchorAndFocusFromNativeSelection(sel);
    +                sel.isCollapsed = selectionIsCollapsed(sel);
    +            } else {
    +                updateEmptySelection(sel);
    +            }
    +        };
    +    } else {
    +        module.fail("No means of obtaining a Range or TextRange from the user's selection was found");
    +        return false;
    +    }
    +
    +    selProto.refresh = function(checkForChanges) {
    +        var oldRanges = checkForChanges ? this._ranges.slice(0) : null;
    +        refreshSelection(this);
    +        if (checkForChanges) {
    +            var i = oldRanges.length;
    +            if (i != this._ranges.length) {
    +                return false;
    +            }
    +            while (i--) {
    +                if (!DomRange.rangesEqual(oldRanges[i], this._ranges[i])) {
    +                    return false;
    +                }
    +            }
    +            return true;
    +        }
    +    };
    +
    +    // Removal of a single range
    +    var removeRangeManually = function(sel, range) {
    +        var ranges = sel.getAllRanges(), removed = false;
    +        sel.removeAllRanges();
    +        for (var i = 0, len = ranges.length; i < len; ++i) {
    +            if (removed || range !== ranges[i]) {
    +                sel.addRange(ranges[i]);
    +            } else {
    +                // According to the draft WHATWG Range spec, the same range may be added to the selection multiple
    +                // times. removeRange should only remove the first instance, so the following ensures only the first
    +                // instance is removed
    +                removed = true;
    +            }
    +        }
    +        if (!sel.rangeCount) {
    +            updateEmptySelection(sel);
    +        }
    +    };
    +
    +    if (implementsControlRange) {
    +        selProto.removeRange = function(range) {
    +            if (this.docSelection.type == CONTROL) {
    +                var controlRange = this.docSelection.createRange();
    +                var rangeElement = getSingleElementFromRange(range);
    +
    +                // Create a new ControlRange containing all the elements in the selected ControlRange minus the
    +                // element contained by the supplied range
    +                var doc = dom.getDocument(controlRange.item(0));
    +                var newControlRange = dom.getBody(doc).createControlRange();
    +                var el, removed = false;
    +                for (var i = 0, len = controlRange.length; i < len; ++i) {
    +                    el = controlRange.item(i);
    +                    if (el !== rangeElement || removed) {
    +                        newControlRange.add(controlRange.item(i));
    +                    } else {
    +                        removed = true;
    +                    }
    +                }
    +                newControlRange.select();
    +
    +                // Update the wrapped selection based on what's now in the native selection
    +                updateControlSelection(this);
    +            } else {
    +                removeRangeManually(this, range);
    +            }
    +        };
    +    } else {
    +        selProto.removeRange = function(range) {
    +            removeRangeManually(this, range);
    +        };
    +    }
    +
    +    // Detecting if a selection is backwards
    +    var selectionIsBackwards;
    +    if (!useDocumentSelection && selectionHasAnchorAndFocus && api.features.implementsDomRange) {
    +        selectionIsBackwards = function(sel) {
    +            var backwards = false;
    +            if (sel.anchorNode) {
    +                backwards = (dom.comparePoints(sel.anchorNode, sel.anchorOffset, sel.focusNode, sel.focusOffset) == 1);
    +            }
    +            return backwards;
    +        };
    +
    +        selProto.isBackwards = function() {
    +            return selectionIsBackwards(this);
    +        };
    +    } else {
    +        selectionIsBackwards = selProto.isBackwards = function() {
    +            return false;
    +        };
    +    }
    +
    +    // Selection text
    +    // This is conformant to the new WHATWG DOM Range draft spec but differs from WebKit and Mozilla's implementation
    +    selProto.toString = function() {
    +
    +        var rangeTexts = [];
    +        for (var i = 0, len = this.rangeCount; i < len; ++i) {
    +            rangeTexts[i] = "" + this._ranges[i];
    +        }
    +        return rangeTexts.join("");
    +    };
    +
    +    function assertNodeInSameDocument(sel, node) {
    +        if (sel.anchorNode && (dom.getDocument(sel.anchorNode) !== dom.getDocument(node))) {
    +            throw new DOMException("WRONG_DOCUMENT_ERR");
    +        }
    +    }
    +
    +    // No current browsers conform fully to the HTML 5 draft spec for this method, so Rangy's own method is always used
    +    selProto.collapse = function(node, offset) {
    +        assertNodeInSameDocument(this, node);
    +        var range = api.createRange(dom.getDocument(node));
    +        range.collapseToPoint(node, offset);
    +        this.removeAllRanges();
    +        this.addRange(range);
    +        this.isCollapsed = true;
    +    };
    +
    +    selProto.collapseToStart = function() {
    +        if (this.rangeCount) {
    +            var range = this._ranges[0];
    +            this.collapse(range.startContainer, range.startOffset);
    +        } else {
    +            throw new DOMException("INVALID_STATE_ERR");
    +        }
    +    };
    +
    +    selProto.collapseToEnd = function() {
    +        if (this.rangeCount) {
    +            var range = this._ranges[this.rangeCount - 1];
    +            this.collapse(range.endContainer, range.endOffset);
    +        } else {
    +            throw new DOMException("INVALID_STATE_ERR");
    +        }
    +    };
    +
    +    // The HTML 5 spec is very specific on how selectAllChildren should be implemented so the native implementation is
    +    // never used by Rangy.
    +    selProto.selectAllChildren = function(node) {
    +        assertNodeInSameDocument(this, node);
    +        var range = api.createRange(dom.getDocument(node));
    +        range.selectNodeContents(node);
    +        this.removeAllRanges();
    +        this.addRange(range);
    +    };
    +
    +    selProto.deleteFromDocument = function() {
    +        // Sepcial behaviour required for Control selections
    +        if (implementsControlRange && implementsDocSelection && this.docSelection.type == CONTROL) {
    +            var controlRange = this.docSelection.createRange();
    +            var element;
    +            while (controlRange.length) {
    +                element = controlRange.item(0);
    +                controlRange.remove(element);
    +                element.parentNode.removeChild(element);
    +            }
    +            this.refresh();
    +        } else if (this.rangeCount) {
    +            var ranges = this.getAllRanges();
    +            this.removeAllRanges();
    +            for (var i = 0, len = ranges.length; i < len; ++i) {
    +                ranges[i].deleteContents();
    +            }
    +            // The HTML5 spec says nothing about what the selection should contain after calling deleteContents on each
    +            // range. Firefox moves the selection to where the final selected range was, so we emulate that
    +            this.addRange(ranges[len - 1]);
    +        }
    +    };
    +
    +    // The following are non-standard extensions
    +    selProto.getAllRanges = function() {
    +        return this._ranges.slice(0);
    +    };
    +
    +    selProto.setSingleRange = function(range) {
    +        this.setRanges( [range] );
    +    };
    +
    +    selProto.containsNode = function(node, allowPartial) {
    +        for (var i = 0, len = this._ranges.length; i < len; ++i) {
    +            if (this._ranges[i].containsNode(node, allowPartial)) {
    +                return true;
    +            }
    +        }
    +        return false;
    +    };
    +
    +    selProto.toHtml = function() {
    +        var html = "";
    +        if (this.rangeCount) {
    +            var container = DomRange.getRangeDocument(this._ranges[0]).createElement("div");
    +            for (var i = 0, len = this._ranges.length; i < len; ++i) {
    +                container.appendChild(this._ranges[i].cloneContents());
    +            }
    +            html = container.innerHTML;
    +        }
    +        return html;
    +    };
    +
    +    function inspect(sel) {
    +        var rangeInspects = [];
    +        var anchor = new DomPosition(sel.anchorNode, sel.anchorOffset);
    +        var focus = new DomPosition(sel.focusNode, sel.focusOffset);
    +        var name = (typeof sel.getName == "function") ? sel.getName() : "Selection";
    +
    +        if (typeof sel.rangeCount != "undefined") {
    +            for (var i = 0, len = sel.rangeCount; i < len; ++i) {
    +                rangeInspects[i] = DomRange.inspect(sel.getRangeAt(i));
    +            }
    +        }
    +        return "[" + name + "(Ranges: " + rangeInspects.join(", ") +
    +                ")(anchor: " + anchor.inspect() + ", focus: " + focus.inspect() + "]";
    +
    +    }
    +
    +    selProto.getName = function() {
    +        return "WrappedSelection";
    +    };
    +
    +    selProto.inspect = function() {
    +        return inspect(this);
    +    };
    +
    +    selProto.detach = function() {
    +        this.win[windowPropertyName] = null;
    +        this.win = this.anchorNode = this.focusNode = null;
    +    };
    +
    +    WrappedSelection.inspect = inspect;
    +
    +    api.Selection = WrappedSelection;
    +
    +    api.selectionPrototype = selProto;
    +
    +    api.addCreateMissingNativeApiListener(function(win) {
    +        if (typeof win.getSelection == "undefined") {
    +            win.getSelection = function() {
    +                return api.getSelection(this);
    +            };
    +        }
    +        win = null;
    +    });
    +});
    +/**
    + * @license Selection save and restore module for Rangy.
    + * Saves and restores user selections using marker invisible elements in the DOM.
    + *
    + * Part of Rangy, a cross-browser JavaScript range and selection library
    + * http://code.google.com/p/rangy/
    + *
    + * Depends on Rangy core.
    + *
    + * Copyright 2011, Tim Down
    + * Licensed under the MIT license.
    + * Version: 1.2.2
    + * Build date: 13 November 2011
    + */
    +rangy.createModule("SaveRestore", function(api, module) {
    +    api.requireModules( ["DomUtil", "DomRange", "WrappedRange"] );
    +
    +    var dom = api.dom;
    +
    +    var markerTextChar = "\ufeff";
    +
    +    function gEBI(id, doc) {
    +        return (doc || document).getElementById(id);
    +    }
    +
    +    function insertRangeBoundaryMarker(range, atStart) {
    +        var markerId = "selectionBoundary_" + (+new Date()) + "_" + ("" + Math.random()).slice(2);
    +        var markerEl;
    +        var doc = dom.getDocument(range.startContainer);
    +
    +        // Clone the Range and collapse to the appropriate boundary point
    +        var boundaryRange = range.cloneRange();
    +        boundaryRange.collapse(atStart);
    +
    +        // Create the marker element containing a single invisible character using DOM methods and insert it
    +        markerEl = doc.createElement("span");
    +        markerEl.id = markerId;
    +        markerEl.style.lineHeight = "0";
    +        markerEl.style.display = "none";
    +        markerEl.className = "rangySelectionBoundary";
    +        markerEl.appendChild(doc.createTextNode(markerTextChar));
    +
    +        boundaryRange.insertNode(markerEl);
    +        boundaryRange.detach();
    +        return markerEl;
    +    }
    +
    +    function setRangeBoundary(doc, range, markerId, atStart) {
    +        var markerEl = gEBI(markerId, doc);
    +        if (markerEl) {
    +            range[atStart ? "setStartBefore" : "setEndBefore"](markerEl);
    +            markerEl.parentNode.removeChild(markerEl);
    +        } else {
    +            module.warn("Marker element has been removed. Cannot restore selection.");
    +        }
    +    }
    +
    +    function compareRanges(r1, r2) {
    +        return r2.compareBoundaryPoints(r1.START_TO_START, r1);
    +    }
    +
    +    function saveSelection(win) {
    +        win = win || window;
    +        var doc = win.document;
    +        if (!api.isSelectionValid(win)) {
    +            module.warn("Cannot save selection. This usually happens when the selection is collapsed and the selection document has lost focus.");
    +            return;
    +        }
    +        var sel = api.getSelection(win);
    +        var ranges = sel.getAllRanges();
    +        var rangeInfos = [], startEl, endEl, range;
    +
    +        // Order the ranges by position within the DOM, latest first
    +        ranges.sort(compareRanges);
    +
    +        for (var i = 0, len = ranges.length; i < len; ++i) {
    +            range = ranges[i];
    +            if (range.collapsed) {
    +                endEl = insertRangeBoundaryMarker(range, false);
    +                rangeInfos.push({
    +                    markerId: endEl.id,
    +                    collapsed: true
    +                });
    +            } else {
    +                endEl = insertRangeBoundaryMarker(range, false);
    +                startEl = insertRangeBoundaryMarker(range, true);
    +
    +                rangeInfos[i] = {
    +                    startMarkerId: startEl.id,
    +                    endMarkerId: endEl.id,
    +                    collapsed: false,
    +                    backwards: ranges.length == 1 && sel.isBackwards()
    +                };
    +            }
    +        }
    +
    +        // Now that all the markers are in place and DOM manipulation over, adjust each range's boundaries to lie
    +        // between its markers
    +        for (i = len - 1; i >= 0; --i) {
    +            range = ranges[i];
    +            if (range.collapsed) {
    +                range.collapseBefore(gEBI(rangeInfos[i].markerId, doc));
    +            } else {
    +                range.setEndBefore(gEBI(rangeInfos[i].endMarkerId, doc));
    +                range.setStartAfter(gEBI(rangeInfos[i].startMarkerId, doc));
    +            }
    +        }
    +
    +        // Ensure current selection is unaffected
    +        sel.setRanges(ranges);
    +        return {
    +            win: win,
    +            doc: doc,
    +            rangeInfos: rangeInfos,
    +            restored: false
    +        };
    +    }
    +
    +    function restoreSelection(savedSelection, preserveDirection) {
    +        if (!savedSelection.restored) {
    +            var rangeInfos = savedSelection.rangeInfos;
    +            var sel = api.getSelection(savedSelection.win);
    +            var ranges = [];
    +
    +            // Ranges are in reverse order of appearance in the DOM. We want to restore earliest first to avoid
    +            // normalization affecting previously restored ranges.
    +            for (var len = rangeInfos.length, i = len - 1, rangeInfo, range; i >= 0; --i) {
    +                rangeInfo = rangeInfos[i];
    +                range = api.createRange(savedSelection.doc);
    +                if (rangeInfo.collapsed) {
    +                    var markerEl = gEBI(rangeInfo.markerId, savedSelection.doc);
    +                    if (markerEl) {
    +                        markerEl.style.display = "inline";
    +                        var previousNode = markerEl.previousSibling;
    +
    +                        // Workaround for issue 17
    +                        if (previousNode && previousNode.nodeType == 3) {
    +                            markerEl.parentNode.removeChild(markerEl);
    +                            range.collapseToPoint(previousNode, previousNode.length);
    +                        } else {
    +                            range.collapseBefore(markerEl);
    +                            markerEl.parentNode.removeChild(markerEl);
    +                        }
    +                    } else {
    +                        module.warn("Marker element has been removed. Cannot restore selection.");
    +                    }
    +                } else {
    +                    setRangeBoundary(savedSelection.doc, range, rangeInfo.startMarkerId, true);
    +                    setRangeBoundary(savedSelection.doc, range, rangeInfo.endMarkerId, false);
    +                }
    +
    +                // Normalizing range boundaries is only viable if the selection contains only one range. For example,
    +                // if the selection contained two ranges that were both contained within the same single text node,
    +                // both would alter the same text node when restoring and break the other range.
    +                if (len == 1) {
    +                    range.normalizeBoundaries();
    +                }
    +                ranges[i] = range;
    +            }
    +            if (len == 1 && preserveDirection && api.features.selectionHasExtend && rangeInfos[0].backwards) {
    +                sel.removeAllRanges();
    +                sel.addRange(ranges[0], true);
    +            } else {
    +                sel.setRanges(ranges);
    +            }
    +
    +            savedSelection.restored = true;
    +        }
    +    }
    +
    +    function removeMarkerElement(doc, markerId) {
    +        var markerEl = gEBI(markerId, doc);
    +        if (markerEl) {
    +            markerEl.parentNode.removeChild(markerEl);
    +        }
    +    }
    +
    +    function removeMarkers(savedSelection) {
    +        var rangeInfos = savedSelection.rangeInfos;
    +        for (var i = 0, len = rangeInfos.length, rangeInfo; i < len; ++i) {
    +            rangeInfo = rangeInfos[i];
    +            if (rangeInfo.collapsed) {
    +                removeMarkerElement(savedSelection.doc, rangeInfo.markerId);
    +            } else {
    +                removeMarkerElement(savedSelection.doc, rangeInfo.startMarkerId);
    +                removeMarkerElement(savedSelection.doc, rangeInfo.endMarkerId);
    +            }
    +        }
    +    }
    +
    +    api.saveSelection = saveSelection;
    +    api.restoreSelection = restoreSelection;
    +    api.removeMarkerElement = removeMarkerElement;
    +    api.removeMarkers = removeMarkers;
    +});
    +/*jshint evil: true */
    +
    +/**
    +    WYMeditor.editor.init
    +    =====================
    +
    +    Initialize a wymeditor instance, including detecting the
    +    current browser and enabling the browser-specific subclass.
    +*/
    +WYMeditor.editor.prototype.init = function () {
    +    // Load the browser-specific subclass
    +    // If this browser isn't supported, do nothing
    +    var WymClass = false,
    +        SaxListener,
    +        prop,
    +        h,
    +        iframeHtml,
    +        boxHtml,
    +        aTools,
    +        sTools,
    +        oTool,
    +        sTool,
    +        i,
    +        aClasses,
    +        sClasses,
    +        oClass,
    +        sClass,
    +        aContainers,
    +        sContainers,
    +        sContainer,
    +        oContainer;
    +
    +    if (jQuery.browser.msie) {
    +        WymClass = new WYMeditor.WymClassExplorer(this);
    +    } else if (jQuery.browser.mozilla) {
    +        WymClass = new WYMeditor.WymClassMozilla(this);
    +    } else if (jQuery.browser.opera) {
    +        WymClass = new WYMeditor.WymClassOpera(this);
    +    } else if (jQuery.browser.safari || jQuery.browser.webkit ||
    +               jQuery.browser.chrome) {
    +        WymClass = new WYMeditor.WymClassSafari(this);
    +    }
    +
    +    if (WymClass === false) {
    +        return;
    +    }
    +
    +    if (jQuery.isFunction(this._options.preInit)) {
    +        this._options.preInit(this);
    +    }
    +
    +    this.parser = null;
    +    this.helper = null;
    +
    +    SaxListener = new WYMeditor.XhtmlSaxListener();
    +    this.parser = new WYMeditor.XhtmlParser(SaxListener);
    +
    +    if (this._options.styles || this._options.stylesheet) {
    +        this.configureEditorUsingRawCss();
    +    }
    +
    +    this.helper = new WYMeditor.XmlHelper();
    +
    +    // Extend the editor object with the browser-specific version.
    +    // We're not using jQuery.extend because we *want* to copy properties via
    +    // the prototype chain
    +    for (prop in WymClass) {
    +        /*jslint forin: false*/
    +        // Explicitly not using hasOwnProperty for the inheritance here
    +        // because we want to go up the prototype chain to get all of the
    +        // browser-specific editor methods. This is kind of a code smell,
    +        // but works just fine.
    +        this[prop] = WymClass[prop];
    +    }
    +
    +    // Load wymbox
    +    this._box = jQuery(this._element).hide().after(this._options.boxHtml).next().addClass('wym_box_' + this._index);
    +
    +    // Store the instance index and replaced element in wymbox
    +    // but keep it compatible with jQuery < 1.2.3, see #122
    +    if (jQuery.isFunction(jQuery.fn.data)) {
    +        jQuery.data(this._box.get(0), WYMeditor.WYM_INDEX, this._index);
    +        jQuery.data(this._element.get(0), WYMeditor.WYM_INDEX, this._index);
    +    }
    +
    +    h = WYMeditor.Helper;
    +
    +    // Construct the iframe
    +    iframeHtml = this._options.iframeHtml;
    +    iframeHtml = h.replaceAll(iframeHtml, WYMeditor.INDEX, this._index);
    +    iframeHtml = h.replaceAll(
    +        iframeHtml,
    +        WYMeditor.IFRAME_BASE_PATH,
    +        this._options.iframeBasePath
    +    );
    +
    +    // Construct wymbox
    +    boxHtml = jQuery(this._box).html();
    +
    +    boxHtml = h.replaceAll(boxHtml, WYMeditor.LOGO, this._options.logoHtml);
    +    boxHtml = h.replaceAll(boxHtml, WYMeditor.TOOLS, this._options.toolsHtml);
    +    boxHtml = h.replaceAll(boxHtml, WYMeditor.CONTAINERS, this._options.containersHtml);
    +    boxHtml = h.replaceAll(boxHtml, WYMeditor.CLASSES, this._options.classesHtml);
    +    boxHtml = h.replaceAll(boxHtml, WYMeditor.HTML, this._options.htmlHtml);
    +    boxHtml = h.replaceAll(boxHtml, WYMeditor.IFRAME, iframeHtml);
    +    boxHtml = h.replaceAll(boxHtml, WYMeditor.STATUS, this._options.statusHtml);
    +
    +    // Construct the tools list
    +    aTools = eval(this._options.toolsItems);
    +    sTools = "";
    +
    +    for (i = 0; i < aTools.length; i += 1) {
    +        oTool = aTools[i];
    +        sTool = '';
    +        if (oTool.name && oTool.title) {
    +            sTool = this._options.toolsItemHtml;
    +        }
    +        sTool = h.replaceAll(sTool, WYMeditor.TOOL_NAME, oTool.name);
    +        sTool = h.replaceAll(
    +            sTool,
    +            WYMeditor.TOOL_TITLE,
    +            this._options.stringDelimiterLeft + oTool.title + this._options.stringDelimiterRight
    +        );
    +        sTool = h.replaceAll(sTool, WYMeditor.TOOL_CLASS, oTool.css);
    +        sTools += sTool;
    +    }
    +
    +    boxHtml = h.replaceAll(boxHtml, WYMeditor.TOOLS_ITEMS, sTools);
    +
    +    // Construct the classes list
    +    aClasses = eval(this._options.classesItems);
    +    sClasses = "";
    +
    +    for (i = 0; i < aClasses.length; i += 1) {
    +        oClass = aClasses[i];
    +        sClass = '';
    +        if (oClass.name && oClass.title) {
    +            sClass = this._options.classesItemHtml;
    +        }
    +        sClass = h.replaceAll(sClass, WYMeditor.CLASS_NAME, oClass.name);
    +        sClass = h.replaceAll(sClass, WYMeditor.CLASS_TITLE, oClass.title);
    +        sClasses += sClass;
    +    }
    +
    +    boxHtml = h.replaceAll(boxHtml, WYMeditor.CLASSES_ITEMS, sClasses);
    +
    +    // Construct the containers list
    +    aContainers = eval(this._options.containersItems);
    +    sContainers = "";
    +
    +    for (i = 0; i < aContainers.length; i += 1) {
    +        oContainer = aContainers[i];
    +        sContainer = '';
    +        if (oContainer.name && oContainer.title) {
    +            sContainer = this._options.containersItemHtml;
    +        }
    +        sContainer = h.replaceAll(
    +            sContainer,
    +            WYMeditor.CONTAINER_NAME,
    +            oContainer.name
    +        );
    +        sContainer = h.replaceAll(sContainer, WYMeditor.CONTAINER_TITLE,
    +            this._options.stringDelimiterLeft +
    +            oContainer.title +
    +            this._options.stringDelimiterRight);
    +        sContainer = h.replaceAll(
    +            sContainer,
    +            WYMeditor.CONTAINER_CLASS,
    +            oContainer.css
    +        );
    +        sContainers += sContainer;
    +    }
    +
    +    boxHtml = h.replaceAll(boxHtml, WYMeditor.CONTAINERS_ITEMS, sContainers);
    +
    +    // I10n
    +    boxHtml = this.replaceStrings(boxHtml);
    +
    +    // Load the html in wymbox
    +    jQuery(this._box).html(boxHtml);
    +
    +    // Hide the html value
    +    jQuery(this._box).find(this._options.htmlSelector).hide();
    +
    +    this.documentStructureManager = new WYMeditor.DocumentStructureManager(
    +        this,
    +        this._options.structureRules.defaultRootContainer
    +    );
    +
    +    this.loadSkin();
    +};
    +
    +/**
    +    WYMeditor.editor.bindEvents
    +    ===========================
    +
    +    Bind all event handlers including tool/container clicks, focus events
    +    and change events.
    +*/
    +WYMeditor.editor.prototype.bindEvents = function () {
    +    var wym = this,
    +        $html_val;
    +
    +    // Handle click events on tools buttons
    +    jQuery(this._box).find(this._options.toolSelector).click(function () {
    +        wym._iframe.contentWindow.focus(); //See #154
    +        wym.exec(jQuery(this).attr(WYMeditor.NAME));
    +        return false;
    +    });
    +
    +    // Handle click events on containers buttons
    +    jQuery(this._box).find(this._options.containerSelector).click(function () {
    +        wym.container(jQuery(this).attr(WYMeditor.NAME));
    +        return false;
    +    });
    +
    +    // Handle keyup event on html value: set the editor value
    +    // Handle focus/blur events to check if the element has focus, see #147
    +    $html_val = jQuery(this._box).find(this._options.htmlValSelector);
    +    $html_val.keyup(function () {
    +        jQuery(wym._doc.body).html(jQuery(this).val());
    +    });
    +    $html_val.focus(function () {
    +        jQuery(this).toggleClass('hasfocus');
    +    });
    +    $html_val.blur(function () {
    +        jQuery(this).toggleClass('hasfocus');
    +    });
    +
    +    // Handle click events on classes buttons
    +    jQuery(this._box).find(this._options.classSelector).click(function () {
    +        var aClasses = eval(wym._options.classesItems),
    +            sName = jQuery(this).attr(WYMeditor.NAME),
    +
    +            oClass = WYMeditor.Helper.findByName(aClasses, sName),
    +            jqexpr;
    +
    +        if (oClass) {
    +            jqexpr = oClass.expr;
    +            wym.toggleClass(sName, jqexpr);
    +        }
    +        wym._iframe.contentWindow.focus(); //See #154
    +        return false;
    +    });
    +
    +    // Handle update event on update element
    +    jQuery(this._options.updateSelector).bind(this._options.updateEvent, function () {
    +        wym.update();
    +    });
    +};
    +
    +WYMeditor.editor.prototype.ready = function () {
    +    return this._doc !== null;
    +};
    +
    +/**
    +    WYMeditor.editor.box
    +    ====================
    +
    +    Get the wymbox container.
    +*/
    +WYMeditor.editor.prototype.box = function () {
    +    return this._box;
    +};
    +
    +/**
    +    WYMeditor.editor._html
    +    ======================
    +
    +    Get or set the wymbox html value. If you want to get the wymbox html, you
    +    should use WYMeditor.editor.xhtml() instead of this so that the html is
    +    parsed and receives cross-browser cleanup. Only use this if you have a
    +    specific reason not to use WYMeditor.editor.xhtml().
    +*/
    +WYMeditor.editor.prototype._html = function (html) {
    +    if (typeof html === 'string') {
    +        jQuery(this._doc.body).html(html);
    +        this.update();
    +    } else {
    +        return jQuery(this._doc.body).html();
    +    }
    +};
    +
    +/**
    +    WYMeditor.editor.html
    +    =====================
    +
    +    Deprecated. Use WYMeditor.editor.xhtml or WYMeditor.editor._html instead.
    +    Calling this function will give a console warning.
    +*/
    +WYMeditor.editor.prototype.html = function (html) {
    +    WYMeditor.console.warn("The function WYMeditor.editor.html() is deprecated. " +
    +                           "Use either WYMeditor.editor.xhtml() or " +
    +                           "WYMeditor.editor._html() instead.");
    +    if (typeof html === 'string') {
    +        this._html(html);
    +    } else {
    +        return this._html();
    +    }
    +};
    +
    +/**
    +    WYMeditor.editor.xhtml
    +    ======================
    +
    +    Take the current editor's DOM and apply strict xhtml nesting rules to
    +    enforce a valid, well-formed, semantic xhtml result.
    +*/
    +WYMeditor.editor.prototype.xhtml = function () {
    +    return this.parser.parse(this._html());
    +};
    +
    +/**
    +    WYMeditor.editor.exec
    +    =====================
    +
    +    Execute a button command on the currently-selected container. The command
    +    can be anything from "indent this element" to "open a dialog to create a
    +    table."
    +
    +    `cmd` is a string corresponding to the command that should be run, roughly
    +    matching the designMode execCommand strategy (and falling through to
    +    execCommand in some cases).
    +*/
    +WYMeditor.editor.prototype.exec = function (cmd) {
    +    var container, custom_run, _this = this;
    +    switch (cmd) {
    +
    +    case WYMeditor.CREATE_LINK:
    +        container = this.container();
    +        if (container || this._selected_image) {
    +            this.dialog(WYMeditor.DIALOG_LINK);
    +        }
    +        break;
    +
    +    case WYMeditor.INSERT_IMAGE:
    +        this.dialog(WYMeditor.DIALOG_IMAGE);
    +        break;
    +
    +    case WYMeditor.INSERT_TABLE:
    +        this.dialog(WYMeditor.DIALOG_TABLE);
    +        break;
    +
    +    case WYMeditor.PASTE:
    +        this.dialog(WYMeditor.DIALOG_PASTE);
    +        break;
    +
    +    case WYMeditor.TOGGLE_HTML:
    +        this.update();
    +        this.toggleHtml();
    +        break;
    +
    +    case WYMeditor.PREVIEW:
    +        this.dialog(WYMeditor.PREVIEW, this._options.dialogFeaturesPreview);
    +        break;
    +
    +    case WYMeditor.INSERT_ORDEREDLIST:
    +        this.insertOrderedlist();
    +        break;
    +
    +    case WYMeditor.INSERT_UNORDEREDLIST:
    +        this.insertUnorderedlist();
    +        break;
    +
    +    case WYMeditor.INDENT:
    +        this.indent();
    +        break;
    +
    +    case WYMeditor.OUTDENT:
    +        this.outdent();
    +        break;
    +
    +
    +    default:
    +        custom_run = false;
    +        jQuery.each(this._options.customCommands, function () {
    +            if (cmd === this.name) {
    +                custom_run = true;
    +                this.run.apply(_this);
    +                return false;
    +            }
    +        });
    +        if (!custom_run) {
    +            this._exec(cmd);
    +        }
    +        break;
    +    }
    +};
    +
    +/**
    +    WYMeditor.editor.selection
    +    ==========================
    +
    +    Override the default selection function to use rangy.
    +*/
    +WYMeditor.editor.prototype.selection = function () {
    +    if (window.rangy && !rangy.initialized) {
    +        rangy.init();
    +    }
    +
    +    var iframe = this._iframe,
    +        sel = rangy.getIframeSelection(iframe);
    +
    +    return sel;
    +};
    +
    +/**
    +    WYMeditor.editor.selection
    +    ==========================
    +
    +    Return the selected node.
    +*/
    +WYMeditor.editor.prototype.selected = function () {
    +    var sel = this.selection(),
    +        node = sel.focusNode,
    +        caretPos;
    +
    +    if (node) {
    +        if (jQuery.browser.msie) {
    +            // For collapsed selections, we have to use the ghetto "caretPos"
    +            // hack to find the selection, otherwise it always says that the
    +            // body element is selected
    +            var isBodyTag = node.tagName && node.tagName.toLowerCase() === "body";
    +            var isTextNode = node.nodeName === "#text";
    +
    +            if (sel.isCollapsed && (isBodyTag || isTextNode)) {
    +                caretPos = this._iframe.contentWindow.document.caretPos;
    +                if (caretPos && caretPos.parentElement) {
    +                    node = caretPos.parentElement();
    +                }
    +            }
    +        }
    +        if (node.nodeName === "#text") {
    +            return node.parentNode;
    +        } else {
    +            return node;
    +        }
    +    } else {
    +        return null;
    +    }
    +};
    +
    +/**
    +    WYMeditor.editor.selection_collapsed
    +    ====================================
    +
    +    Return true if all selections are collapsed, false otherwise.
    +*/
    +WYMeditor.editor.prototype.selection_collapsed = function () {
    +    var sel = this.selection(),
    +        collapsed = false;
    +
    +    jQuery.each(sel.getAllRanges(), function () {
    +        if (this.collapsed) {
    +            collapsed = true;
    +            //break
    +            return false;
    +        }
    +    });
    +
    +    return collapsed;
    +};
    +
    +/**
    +    WYMeditor.editor.selected_contains
    +    ==================================
    +
    +    Return an array of nodes that match a jQuery selector
    +    within the current selection.
    +*/
    +WYMeditor.editor.prototype.selected_contains = function (selector) {
    +    var sel = this.selection(),
    +        matches = [];
    +
    +    jQuery.each(sel.getAllRanges(), function () {
    +        jQuery.each(this.getNodes(), function () {
    +            if (jQuery(this).is(selector)) {
    +                matches.push(this);
    +            }
    +        });
    +    });
    +
    +    return matches;
    +};
    +
    +/**
    +    WYMeditor.editor.selected_parents_contains
    +    ==================================
    +
    +    Return an array of nodes that match the selector within
    +    the selection's parents.
    +*/
    +WYMeditor.editor.prototype.selected_parents_contains = function (selector) {
    +    var $matches = jQuery([]),
    +        $selected = jQuery(this.selected());
    +    if ($selected.is(selector)) {
    +        $matches = $matches.add($selected);
    +    }
    +    $matches = $matches.add($selected.parents(selector));
    +    return $matches;
    +};
    +
    +/**
    +    WYMeditor.editor.container
    +    ==========================
    +
    +    Get or set the selected container.
    +*/
    +WYMeditor.editor.prototype.container = function (sType) {
    +    if (typeof (sType) === 'undefined') {
    +        return this.selected();
    +    }
    +
    +    var container = null,
    +        validContainers,
    +        newNode,
    +        blockquote,
    +        nodes,
    +        lgt,
    +        firstNode,
    +        x;
    +
    +    if (sType.toLowerCase() === WYMeditor.TH) {
    +        container = this.container();
    +
    +        // Find the TD or TH container
    +        switch (container.tagName.toLowerCase()) {
    +
    +        case WYMeditor.TD:
    +        case WYMeditor.TH:
    +            break;
    +        default:
    +            aTypes = [WYMeditor.TD, WYMeditor.TH];
    +            container = this.findUp(this.container(), aTypes);
    +            break;
    +        }
    +
    +        // If it exists, switch
    +        if (container !== null) {
    +            sType = WYMeditor.TD;
    +            if (container.tagName.toLowerCase() === WYMeditor.TD) {
    +                sType = WYMeditor.TH;
    +            }
    +            this.switchTo(container, sType, false);
    +            this.update();
    +        }
    +    } else {
    +        // Set the container type
    +        aTypes = [
    +            WYMeditor.P,
    +            WYMeditor.DIV,
    +            WYMeditor.H1,
    +            WYMeditor.H2,
    +            WYMeditor.H3,
    +            WYMeditor.H4,
    +            WYMeditor.H5,
    +            WYMeditor.H6,
    +            WYMeditor.PRE,
    +            WYMeditor.BLOCKQUOTE
    +        ];
    +        container = this.findUp(this.container(), aTypes);
    +
    +        if (container) {
    +            if (sType.toLowerCase() === WYMeditor.BLOCKQUOTE) {
    +                // Blockquotes must contain a block level element
    +                blockquote = this.findUp(
    +                    this.container(),
    +                    WYMeditor.BLOCKQUOTE
    +                );
    +                if (blockquote === null) {
    +                    newNode = this._doc.createElement(sType);
    +                    container.parentNode.insertBefore(newNode, container);
    +                    newNode.appendChild(container);
    +                    this.setFocusToNode(newNode.firstChild);
    +                } else {
    +                    nodes = blockquote.childNodes;
    +                    lgt = nodes.length;
    +
    +                    if (lgt > 0) {
    +                        firstNode = nodes.item(0);
    +                    }
    +                    for (x = 0; x < lgt; x += 1) {
    +                        blockquote.parentNode.insertBefore(
    +                            nodes.item(0),
    +                            blockquote
    +                        );
    +                    }
    +                    blockquote.parentNode.removeChild(blockquote);
    +                    if (firstNode) {
    +                        this.setFocusToNode(firstNode);
    +                    }
    +                }
    +            } else {
    +                // Not a blockquote
    +                this.switchTo(container, sType);
    +            }
    +
    +            this.update();
    +        }
    +    }
    +
    +    return false;
    +};
    +
    +/**
    +    WYMeditor.editor.isForbiddenMainContainer
    +    =========================================
    +
    +    Determines whether a container with the passed tagName is allowed to be a
    +    main container (i.e. if it is allowed to be a container in the root of the
    +    document). Returns true if a container with the passed tagName is *not* an
    +    allowable main container, and returns false if otherwise.
    +
    +    @param tagName A string of the tag name to be determined if it can be a
    +                   main container or not
    +*/
    +WYMeditor.editor.prototype.isForbiddenMainContainer = function (tagName) {
    +    return jQuery.inArray(tagName.toLowerCase(),
    +                          WYMeditor.FORBIDDEN_MAIN_CONTAINERS) > -1;
    +};
    +
    +/**
    +    WYMeditor.editor.keyCanCreateBlockElement
    +    =========================================
    +
    +    Determines whether the key represented by the passed keyCode can create a
    +    block element within the editor when inputted. Returns true if the key can
    +    create a block element when inputted, and returns false if otherwise.
    +
    +    @param keyCode A numberic key code representing a key
    +*/
    +WYMeditor.editor.prototype.keyCanCreateBlockElement = function (keyCode) {
    +    return jQuery.inArray(keyCode,
    +                    WYMeditor.POTENTIAL_BLOCK_ELEMENT_CREATION_KEYS) > -1;
    +};
    +
    +/**
    +    WYMeditor.editor.toggleClass
    +    ============================
    +
    +    Toggle a class on the selected element or one of its parents
    +*/
    +WYMeditor.editor.prototype.toggleClass = function (sClass, jqexpr) {
    +    var container = null;
    +    if (this._selected_image) {
    +        container = this._selected_image;
    +    } else {
    +        container = jQuery(this.selected());
    +    }
    +    container = jQuery(container).parentsOrSelf(jqexpr);
    +    jQuery(container).toggleClass(sClass);
    +
    +    if (!jQuery(container).attr(WYMeditor.CLASS)) {
    +        jQuery(container).removeAttr(this._class);
    +    }
    +};
    +
    +/**
    +    WYMeditor.editor.findUp
    +    =======================
    +
    +    Return the first parent or self container, based on its type
    +
    +    `filter` is a string or an array of strings on which to filter the container
    +*/
    +WYMeditor.editor.prototype.findUp = function (node, filter) {
    +    if (typeof (node) === 'undefined' || node === null) {
    +        return null;
    +    }
    +
    +    if (node.nodeName === "#text") {
    +        // For text nodes, we need to look from the nodes container object
    +        node = node.parentNode;
    +    }
    +    var tagname = node.tagName.toLowerCase(),
    +        bFound,
    +        i;
    +    if (typeof (filter) === WYMeditor.STRING) {
    +        while (tagname !== filter && tagname !== WYMeditor.BODY) {
    +            node = node.parentNode;
    +            tagname = node.tagName.toLowerCase();
    +        }
    +    } else {
    +        bFound = false;
    +        while (!bFound && tagname !== WYMeditor.BODY) {
    +            for (i = 0; i < filter.length; i += 1) {
    +                if (tagname === filter[i]) {
    +                    bFound = true;
    +                    break;
    +                }
    +            }
    +            if (!bFound) {
    +                node = node.parentNode;
    +                if (node === null) {
    +                    // No parentNode, so we're not going to find anything
    +                    // up the ancestry chain that matches
    +                    return null;
    +                }
    +                tagname = node.tagName.toLowerCase();
    +            }
    +        }
    +    }
    +
    +    if (tagname === WYMeditor.BODY) {
    +        return null;
    +    }
    +
    +    return node;
    +};
    +
    +/**
    +    WYMeditor.editor.switchTo
    +    =========================
    +
    +    Switch the type of the given `node` to type `sType`. If stripAttrs is true,
    +    the attributes of node will not be included in the the new type. If
    +    stripAttrs is false (or undefined), the attributes of node will be
    +    preserved through the switch.
    +*/
    +WYMeditor.editor.prototype.switchTo = function (node, sType, stripAttrs) {
    +    var newNode = this._doc.createElement(sType),
    +        html = jQuery(node).html(),
    +        attrs = node.attributes,
    +        i;
    +
    +    if (!stripAttrs) {
    +        for (i = 0; i < attrs.length; ++i) {
    +            newNode.setAttribute(attrs.item(i).nodeName,
    +                                 attrs.item(i).nodeValue);
    +        }
    +    }
    +    newNode.innerHTML = html;
    +    node.parentNode.replaceChild(newNode, node);
    +
    +    this.setFocusToNode(newNode);
    +};
    +
    +WYMeditor.editor.prototype.replaceStrings = function (sVal) {
    +    var key;
    +    // Check if the language file has already been loaded
    +    // if not, get it via a synchronous ajax call
    +    if (!WYMeditor.STRINGS[this._options.lang]) {
    +        try {
    +            eval(jQuery.ajax({url: this._options.langPath +
    +                this._options.lang + '.js', async: false}).responseText);
    +        } catch (e) {
    +            WYMeditor.console.error(
    +                "WYMeditor: error while parsing language file."
    +            );
    +            return sVal;
    +        }
    +    }
    +
    +    // Replace all the strings in sVal and return it
    +    for (key in WYMeditor.STRINGS[this._options.lang]) {
    +        if (WYMeditor.STRINGS[this._options.lang].hasOwnProperty(key)) {
    +            sVal = WYMeditor.Helper.replaceAll(
    +                sVal,
    +                this._options.stringDelimiterLeft + key + this._options.stringDelimiterRight,
    +                WYMeditor.STRINGS[this._options.lang][key]
    +            );
    +        }
    +    }
    +    return sVal;
    +};
    +
    +WYMeditor.editor.prototype.encloseString = function (sVal) {
    +    return this._options.stringDelimiterLeft +
    +        sVal +
    +        this._options.stringDelimiterRight;
    +};
    +
    +/**
    +    editor.status
    +    =============
    +
    +    Print the given string as a status message.
    +*/
    +WYMeditor.editor.prototype.status = function (sMessage) {
    +    // Print status message
    +    jQuery(this._box).find(this._options.statusSelector).html(sMessage);
    +};
    +
    +/**
    +    editor.update
    +    =============
    +
    +    Update the element and textarea values.
    +*/
    +WYMeditor.editor.prototype.update = function () {
    +    var html;
    +
    +    html = this.xhtml();
    +    jQuery(this._element).val(html);
    +    jQuery(this._box).find(this._options.htmlValSelector).not('.hasfocus').val(html); //#147
    +    this.fixBodyHtml();
    +};
    +
    +/**
    +    editor.fixBodyHtml
    +    ==================
    +
    +    Adjust the editor body html to account for editing changes where
    +    perfect HTML is not optimal. For instance, 
    elements are useful between + certain block elements. +*/ +WYMeditor.editor.prototype.fixBodyHtml = function () { + this.spaceBlockingElements(); + this.fixDoubleBr(); +}; + +/** + editor.spaceBlockingElements + ============================ + + Insert
    elements between adjacent blocking elements and + p elements, between block elements or blocking elements and the + start/end of the document. +*/ +WYMeditor.editor.prototype.spaceBlockingElements = function () { + var blockingSelector = + WYMeditor.DocumentStructureManager.CONTAINERS_BLOCKING_NAVIGATION.join(', '), + $body = jQuery(this._doc).find('body.wym_iframe'), + children = $body.children(), + + placeholderNode, + $firstChild, + $lastChild, + blockSepSelector, + blockInListSepSelector, + $blockInList; + + if (jQuery.browser.mozilla) { + placeholderNode = '
    '; + } else { + placeholderNode = '
    '; + } + + // Make sure that we still have a placeholder node at both the begining and + // end + if (children.length > 0) { + $firstChild = jQuery(children[0]); + $lastChild = jQuery(children[children.length - 1]); + + if ($firstChild.is(blockingSelector)) { + $firstChild.before(placeholderNode); + } + + if ($lastChild.is(blockingSelector) && + !(jQuery.browser.msie && jQuery.browser.version < "7.0")) { + + $lastChild.after(placeholderNode); + } + } + + blockSepSelector = this._getBlockSepSelector(); + + // Put placeholder nodes between consecutive blocking elements and between + // blocking elements and normal block-level elements + $body.find(blockSepSelector).before(placeholderNode); + + blockInListSepSelector = this._getBlockInListSepSelector(); + $blockInList = $body.find(blockInListSepSelector); + + // The $blockInList selection must be iterated over to only add placeholder + // nodes after blocking elements at the end of a list item rather than all + // blocking elements in a list. No jQuery selection that is supported on + // all browsers can do this check, so that is why it must be done by using + // `each` to iterate over the selection. Note that the handling of the + // spacing of other blocking elements in a list besides after the last + // blocking element in a list item is already handled by the + // blockSepSelector used before this. + $blockInList.each(function () { + var $block = jQuery(this); + + if(!$block.next(blockingSelector).length && + !$block.next(WYMeditor.BR).length) { + + $block.after(placeholderNode); + } + }); +}; + +/** + editor._getBlockSepSelector + ============================= + + Build a string representing a jquery selector that will find all + elements which need a spacer
    before them. This includes all consecutive + blocking elements and between blocking elements and normal non-blocking + elements. +*/ +WYMeditor.editor.prototype._getBlockSepSelector = function () { + if (typeof (this._blockSpacersSel) !== 'undefined') { + return this._blockSpacersSel; + } + + var wym = this, + blockCombo = [], + containersBlockingNav = + WYMeditor.DocumentStructureManager.CONTAINERS_BLOCKING_NAVIGATION, + containersNotBlockingNav; + + // Generate the list of non-blocking elements by removing the blocking + // elements from the list of validRootContainers + containersNotBlockingNav = jQuery.grep( + wym.documentStructureManager.structureRules.validRootContainers, + function (item) { + return jQuery.inArray(item, containersBlockingNav) === -1; + }); + + // Consecutive blocking elements need separators + jQuery.each( + containersBlockingNav, + function (indexO, elementO) { + jQuery.each( + containersBlockingNav, + function (indexI, elementI) { + blockCombo.push(elementO + ' + ' + elementI); + } + ); + } + ); + + // A blocking element either followed by or preceeded by a not blocking + // element needs separators + jQuery.each( + containersBlockingNav, + function (indexO, elementO) { + jQuery.each( + containersNotBlockingNav, + function (indexI, elementI) { + blockCombo.push(elementO + ' + ' + elementI); + blockCombo.push(elementI + ' + ' + elementO); + } + ); + } + ); + this._blockSpacersSel = blockCombo.join(', '); + return this._blockSpacersSel; +}; + +/* + editor._getBlockInListSepSelector + ================================== + + Returns a selector for getting all of the block elements in lists + or sublists. The block elements at the end of lists or sublists should have + a spacer line break after them in the editor at all times. +*/ +WYMeditor.editor.prototype._getBlockInListSepSelector = function () { + if (typeof (this._blockInListSpacersSel) !== 'undefined') { + return this._blockInListSpacersSel; + } + + var blockCombo = []; + + jQuery.each(WYMeditor.LIST_TYPE_ELEMENTS, function (indexO, elementO) { + jQuery.each(WYMeditor.BLOCKING_ELEMENTS, function (indexI, elementI) { + blockCombo.push(elementO + ' ' + elementI); + }); + }); + + this._blockInListSpacersSel = blockCombo.join(', '); + return this._blockInListSpacersSel; +}; + +/** + editor.fixDoubleBr + ================== + + Remove the

    elements that are inserted between + paragraphs, usually after hitting enter from an existing paragraph. +*/ +WYMeditor.editor.prototype.fixDoubleBr = function () { + var $body = jQuery(this._doc).find('body.wym_iframe'), + $last_br, + + blockingSelector = WYMeditor.BLOCKING_ELEMENTS.join(', '); + + // Strip consecutive brs unless they're in a pre tag + $body.children('br + br').filter(':not(pre br)').remove(); + + // Also remove any brs between two p's + $body.find('p + br').next('p').prev('br').remove(); + + // Remove brs floating at the end after a p + $last_br = $body.find('p + br').slice(-1); + if ($last_br.length > 0) { + if ($last_br.next().length === 0) { + $last_br.remove(); + } + } +}; + +/** + editor.dialog + ============= + + Open a dialog box +*/ +WYMeditor.editor.prototype.dialog = function (dialogType, dialogFeatures, bodyHtml) { + var features = dialogFeatures || this._wym._options.dialogFeatures, + wDialog = window.open('', 'dialog', features), + sBodyHtml, + h = WYMeditor.Helper, + dialogHtml, + doc; + + if (wDialog) { + sBodyHtml = ""; + + switch (dialogType) { + + case (WYMeditor.DIALOG_LINK): + sBodyHtml = this._options.dialogLinkHtml; + break; + case (WYMeditor.DIALOG_IMAGE): + sBodyHtml = this._options.dialogImageHtml; + break; + case (WYMeditor.DIALOG_TABLE): + sBodyHtml = this._options.dialogTableHtml; + break; + case (WYMeditor.DIALOG_PASTE): + sBodyHtml = this._options.dialogPasteHtml; + break; + case (WYMeditor.PREVIEW): + sBodyHtml = this._options.dialogPreviewHtml; + break; + default: + sBodyHtml = bodyHtml; + break; + } + + // Construct the dialog + dialogHtml = this._options.dialogHtml; + dialogHtml = h.replaceAll( + dialogHtml, + WYMeditor.BASE_PATH, + this._options.basePath + ); + dialogHtml = h.replaceAll( + dialogHtml, + WYMeditor.DIRECTION, + this._options.direction + ); + dialogHtml = h.replaceAll( + dialogHtml, + WYMeditor.CSS_PATH, + this._options.skinPath + WYMeditor.SKINS_DEFAULT_CSS + ); + dialogHtml = h.replaceAll( + dialogHtml, + WYMeditor.WYM_PATH, + this._options.wymPath + ); + dialogHtml = h.replaceAll( + dialogHtml, + WYMeditor.JQUERY_PATH, + this._options.jQueryPath + ); + dialogHtml = h.replaceAll( + dialogHtml, + WYMeditor.DIALOG_TITLE, + this.encloseString(dialogType) + ); + dialogHtml = h.replaceAll( + dialogHtml, + WYMeditor.DIALOG_BODY, + sBodyHtml + ); + dialogHtml = h.replaceAll( + dialogHtml, + WYMeditor.INDEX, + this._index + ); + + dialogHtml = this.replaceStrings(dialogHtml); + + doc = wDialog.document; + doc.write(dialogHtml); + doc.close(); + } +}; + +/** + editor.toggleHtml + ================= + + Show/Hide the HTML textarea. +*/ +WYMeditor.editor.prototype.toggleHtml = function () { + jQuery(this._box).find(this._options.htmlSelector).toggle(); +}; + +WYMeditor.editor.prototype.uniqueStamp = function () { + var now = new Date(); + return "wym-" + now.getTime(); +}; + +/** + Paste the given array of paragraph-items at the given range inside the given $container. + + It has already been determined that the paragraph has multiple lines and + that the container we're pasting to is a block container capable of accepting + further nested blocks. +*/ +WYMeditor.editor.prototype._handleMultilineBlockContainerPaste = function (wym, $container, range, paragraphStrings) { + + var i, + blockSplitter, + leftSide, + rightSide, + rangeNodeComparison, + $splitRightParagraph, + firstParagraphString, + firstParagraphHtml, + blockParent, + blockParentType; + + + // Now append all subsequent paragraphs + $insertAfter = jQuery(blockParent); + + // Just need to split the current container and put new block elements + // in between + blockSplitter = 'p'; + if ($container.is('li')) { + // Instead of creating paragraphs on line breaks, we'll need to create li's + blockSplitter = 'li'; + } + // Split the selected element then build and insert the appropriate html + // This accounts for cases where the start selection is at the + // start of a node or in the middle of a text node by splitting the + // text nodes using rangy's splitBoundaries() + range.splitBoundaries(); // Split any partially-select text nodes + blockParent = wym.findUp( + range.startContainer, + ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'li'] + ); + blockParentType = blockParent.tagName; + leftSide = []; + rightSide = []; + jQuery(blockParent).contents().each(function (index, element) { + // Capture all of the dom nodes to the left and right of our + // range. We can't remove them in the same step because that + // loses the selection in webkit + + rangeNodeComparison = range.compareNode(element); + if (rangeNodeComparison === range.NODE_BEFORE || + (rangeNodeComparison === range.NODE_BEFORE_AND_AFTER && + range.startOffset === range.startContainer.length)) { + // Because of the way splitBoundaries() works, the + // collapsed selection might appear in the right-most index + // of the border node, which means it will show up as + // + // eg. | is the selection and <> are text node boundaries + // + // + // We detect that case by counting any + // NODE_BEFORE_AND_AFTER result where the offset is at the + // very end of the node as a member of the left side + leftSide.push(element); + } else { + rightSide.push(element); + } + }); + // Now remove all of the left and right nodes + for (i = 0; i < leftSide.length; i++) { + jQuery(leftSide[i]).remove(); + } + for (i = 0; i < rightSide.length; i++) { + jQuery(rightSide[i]).remove(); + } + + // Rebuild our split nodes and add the inserted content + if (leftSide.length > 0) { + // We have left-of-selection content + // Put the content back inside our blockParent + jQuery(blockParent).prepend(leftSide); + } + if (rightSide.length > 0) { + // We have right-of-selection content. + // Split it off in to a node of the same type after our + // blockParent + $splitRightParagraph = jQuery('<' + blockParentType + '>' + + '', wym._doc); + $splitRightParagraph.insertAfter(jQuery(blockParent)); + $splitRightParagraph.append(rightSide); + } + + // Insert the first paragraph in to the current node, and then + // start appending subsequent paragraphs + firstParagraphString = paragraphStrings.splice( + 0, + 1 + )[0]; + firstParagraphHtml = firstParagraphString.split(WYMeditor.NEWLINE).join('
    '); + jQuery(blockParent).html(jQuery(blockParent).html() + firstParagraphHtml); + + // Now append all subsequent paragraphs + $insertAfter = jQuery(blockParent); + for (i = 0; i < paragraphStrings.length; i++) { + html = '<' + blockSplitter + '>' + + (paragraphStrings[i].split(WYMeditor.NEWLINE).join('
    ')) + + ''; + $insertAfter = jQuery(html, wym._doc).insertAfter($insertAfter); + } +}; + +/** + editor.paste + ============ + + Paste text into the editor below the carret, used for "Paste from Word". + + Takes the string to insert as an argument. Two or more newlines separates + paragraphs. May contain inline HTML. +*/ +WYMeditor.editor.prototype.paste = function (str) { + var container = this.selected(), + $container, + html = '', + paragraphs, + focusNode, + i, + l, + isSingleLine = false, + sel, + textNode, + wym, + range, + insertionNodes; + wym = this; + sel = rangy.getIframeSelection(wym._iframe); + range = sel.getRangeAt(0); + $container = jQuery(container); + + // Start by collapsing the range to the start of the selection. We're + // punting on implementing a paste that also replaces existing content for + // now, + range.collapse(true); // Collapse to the the begining of the selection + + // Split string into paragraphs by two or more newlines + paragraphStrings = str.split(new RegExp(WYMeditor.NEWLINE + '{2,}', 'g')); + + if (paragraphStrings.length === 1) { + // This is a one-line paste, which is an easy case. + // We try not to wrap these in paragraphs + isSingleLine = true; + } + + if (typeof container === 'undefined' || + (container && container.tagName.toLowerCase() === WYMeditor.BODY)) { + // No selection, or body selection. Paste at the end of the document + + if (isSingleLine) { + // Easy case. Wrap the string in p tags + paragraphs = jQuery( + '

    ' + paragraphStrings[0] + '

    ', + this._doc + ).appendTo(this._doc.body); + } else { + // Need to build paragraphs and insert them at the end + blockSplitter = 'p'; + for (i = paragraphStrings.length - 1; i >= 0; i -= 1) { + // Going backwards because rangy.insertNode leaves the + // selection in front of the inserted node + html = '<' + blockSplitter + '>' + + (paragraphStrings[i].split(WYMeditor.NEWLINE).join('
    ')) + + ''; + // Build multiple nodes from the HTML because ie6 chokes + // creating multiple nodes implicitly via jquery + insertionNodes = jQuery(html, wym._doc); + for (j = insertionNodes.length - 1; j >= 0; j--) { + // Loop backwards through all of the nodes because + // insertNode moves that direction + range.insertNode(insertionNodes[j]); + } + } + } + } else { + // Pasting inside an existing element + if (isSingleLine || $container.is('pre')) { + // Easy case. Insert a text node at the current selection + textNode = this._doc.createTextNode(str); + range.insertNode(textNode); + } else if ($container.is('p,h1,h2,h3,h4,h5,h6,li')) { + wym._handleMultilineBlockContainerPaste(wym, $container, range, paragraphStrings); + } else { + // We're in a container that doesn't accept nested paragraphs (eg. td). Use + //
    separators everywhere instead + textNodesToInsert = str.split(WYMeditor.NEWLINE); + for (i = textNodesToInsert.length - 1; i >= 0; i -= 1) { + // Going backwards because rangy.insertNode leaves the + // selection in front of the inserted node + textNode = this._doc.createTextNode(textNodesToInsert[i]); + range.insertNode(textNode); + if (i > 0) { + // Don't insert an opening br + range.insertNode(jQuery('
    ', wym._doc).get(0)); + } + } + } + } +}; + +WYMeditor.editor.prototype.insert = function (html) { + // Do we have a selection? + var selection = this._iframe.contentWindow.getSelection(), + range, + node; + if (selection.focusNode !== null) { + // Overwrite selection with provided html + range = selection.getRangeAt(0); + node = range.createContextualFragment(html); + range.deleteContents(); + range.insertNode(node); + } else { + // Fall back to the internal paste function if there's no selection + this.paste(html); + } +}; + +WYMeditor.editor.prototype.wrap = function (left, right) { + this.insert( + left + this._iframe.contentWindow.getSelection().toString() + right + ); +}; + +WYMeditor.editor.prototype.unwrap = function () { + this.insert(this._iframe.contentWindow.getSelection().toString()); +}; + +WYMeditor.editor.prototype.setFocusToNode = function (node, toStart) { + var range = rangy.createRange(this._doc), + selection = rangy.getIframeSelection(this._iframe); + toStart = toStart || false; + + range.selectNodeContents(node); + range.collapse(toStart); + selection.setSingleRange(range); +}; + +WYMeditor.editor.prototype.addCssRules = function (doc, aCss) { + var styles = doc.styleSheets[0], + i, + oCss; + if (styles) { + for (i = 0; i < aCss.length; i += 1) { + oCss = aCss[i]; + if (oCss.name && oCss.css) { + this.addCssRule(styles, oCss); + } + } + } +}; + +/** + editor.splitListItemContents + ============================= + + Utility + + Splits a list item into the content that should stay with the li as it is + indented/outdent and the things that should stay at their current indent level. + + `itemContents` are what a user would consider that list's contents + `sublistContents` are the sub-items that are nested inside the li + (basically everything after the first ol/ul inclusive). + + The returned object has `itemContents` and `sublistContents` properties. +*/ +WYMeditor.editor.prototype.splitListItemContents = function ($listItem) { + var $allContents, + i, + elmnt, + hitSublist = false, + splitObject = {itemContents: [], sublistContents: []}; + + $allContents = $listItem.contents(); + + for (i = 0; i < $allContents.length; i++) { + elmnt = $allContents.get(i); + if (hitSublist || jQuery(elmnt).is('ol,ul')) { + // We've hit the first sublist. Everything from this point on is + // considered `sublistContents` + hitSublist = true; + splitObject.sublistContents.push(elmnt); + } else { + splitObject.itemContents.push(elmnt); + } + } + + return splitObject; +}; + +/** + editor.joinAdjacentLists + ======================== + + Utility + + Joins two lists if they are adjacent and are of the same type. + + The end result will be `listTwo`s contents being appended to `listOne` +*/ +WYMeditor.editor.prototype.joinAdjacentLists = function (listOne, listTwo) { + var $listTwoContents; + + if (typeof listOne === 'undefined' || + typeof listTwo === 'undefined') { + // Invalid arguments + return; + } + if (listOne.nextSibling !== listTwo || + listOne.tagName.toLowerCase() !== listTwo.tagName.toLowerCase()) { + return; + } + + $listTwoContents = jQuery(listTwo).contents(); + $listTwoContents.unwrap(); // Kill listTwo + $listTwoContents.detach(); + jQuery(listOne).append($listTwoContents); +}; + +/** + editor._indentSingleItem + ======================== + + Indent a single list item via the dom, ensuring that the selected node moves in + exactly one level and all other nodes stay at the same level. + */ +WYMeditor.editor.prototype._indentSingleItem = function (listItem) { + var wym = this, + $liToIndent, + listType, + spacerHtml, + containerHtml, + + splitContent, + $itemContents, + $sublistContents, + + $prevLi, + $prevSublist, + $firstSublist, + + $spacer, + $spacerContents; + + // The algorithm used here is generally: + // 1. Ensure there's a previous li to put the `liToIndent`. + // 2. Move the `liToIndent` into a sublist in the previous li. + // 3. If we added a spacer_li after `liToIndent` remove it and move its + // contents inside `liToIndent`. + + // For step 2, the sublist to use can either be: + // 1. An existing sublist of the correct type at the end of the previous li. + // 2. An existing sublist inside `liToIndent`. + // 3. A new sublist that we create. + + $liToIndent = jQuery(listItem); + listType = $liToIndent.parent()[0].tagName.toLowerCase(); + + // Separate out the contents into things that should stay with the li as it + // moves and things that should stay at their current level + splitContent = wym.splitListItemContents($liToIndent); + $sublistContents = jQuery(splitContent.sublistContents); + $itemContents = jQuery(splitContent.itemContents); + + $prevLi = $liToIndent.prev().filter('li'); + // Ensure we actually have a previous li in which to put the `liToIndent` + if ($prevLi.length === 0) { + spacerHtml = '
  • '; + $liToIndent.before(spacerHtml); + $prevLi = $liToIndent.prev(); + } + + // Move `liToIndent` to a sublist inside its previous sibling li + $prevSublist = $prevLi.contents().last().filter('ol,ul'); + if ($prevSublist.length > 0) { + // Case 1: We have a sublist at the appropriate level as a previous + // sibling. Leave the sublist contents where they are and join the + // previous sublist + + // Join our node at the end of the target sublist + $prevSublist.append($liToIndent); + + // Stick all of the sublist contents at the end of the previous li + $sublistContents.detach(); + $prevLi.append($sublistContents); + + // If we just moved two lists of the same type next to eachother, join + // them + $firstSublist = $sublistContents.first(); + wym.joinAdjacentLists($prevSublist.get(0), $firstSublist.get(0)); + } else { + if ($sublistContents.length > 0) { + // Case 2: We need to move our existing sublist to the previous li + $sublistContents.detach(); + $prevLi.append($sublistContents); + $prevSublist = $sublistContents.first(); + } else { + // Case 3: Create a spacer sublist in the previous li in which to + // place `liToIndent` + containerHtml = '<' + listType + '>'; + $prevLi.append(containerHtml); + $prevSublist = $prevLi.contents().last(); + } + + // Move our li to the start of the sublist + $prevSublist.prepend($liToIndent); + } + + // If we eliminated the need for a spacer_li, remove it + if ($liToIndent.next().is('.spacer_li')) { + $spacer = $liToIndent.next('.spacer_li'); + $spacerContents = $spacer.contents(); + $spacerContents.detach(); + $liToIndent.append($spacerContents); + $spacer.remove(); + } + +}; + +/** + editor._outdentSingleItem + ======================== + + Outdent a single list item via the dom, ensuring that the selected node moves in + exactly one level and all other nodes stay at the same level. + */ +WYMeditor.editor.prototype._outdentSingleItem = function (listItem) { + var wym = this, + $liToOutdent, + listType, + spacerListHtml, + + splitContent, + $itemContents, + $sublistContents, + + $parentLi, + $parentList, + + $subsequentSiblingContent, + $subsequentParentListSiblingContent, + + $sublist; + + // The algorithm used here is generally: + // 1. Gather all subsequent sibling content in `liToIndent`s list along + // with all subsequent sibling content in `liToIndent`s parent list. + // 2. Move `liToIndent` after the li whose sublist it's in. + // 3. Create a sublist of the same type of `liToIndent`s parent list inside + // `liToIndent`. + // 4. If `liToIndent` has a sublist, use a spacer_li and list to hold its + // position inside the new sublist. + // 5. Append all original subsequent siblings inside the created sublist. + // 6. Append all of `liToIndent`s original parent list subsequent sibling + // content after the created sublist. + // 7. Remove `liToOutdent`'s original parent list and parent li if either + // are now empty + + $liToOutdent = jQuery(listItem); + listType = $liToOutdent.parent()[0].tagName.toLowerCase(); + + // If this list item isn't already indented at least one level, don't allow + // outdenting + if (!$liToOutdent.parent().parent().is('ol,ul,li')) { + return; + } + if (!$liToOutdent.parent().parent().is('li')) { + // We have invalid list nesting and we need to fix that + WYMeditor.console.log( + 'Attempting to fix invalid list nesting before outdenting.' + ); + wym.correctInvalidListNesting(listItem); + } + + // Separate out the contents into things that should stay with the li as it + // moves and things that should stay at their current level + splitContent = wym.splitListItemContents($liToOutdent); + $sublistContents = jQuery(splitContent.sublistContents); + $itemContents = jQuery(splitContent.itemContents); + + // Gather subsequent sibling and parent sibling content + $parentLi = $liToOutdent.parent().parent('li'); + // Invalid HTML could cause this selector to fail, which breaks our logic. + // Bail out rather than possibly losing content + if ($parentLi.length === 0) { + WYMeditor.console.error('Invalid list. No parentLi found, so aborting outdent'); + return; + } + $parentList = $liToOutdent.parent(); + $subsequentSiblingContent = $liToOutdent.nextAllContents(); + $subsequentParentListSiblingContent = $parentList.nextAllContents(); + + // Move the li to after the parent li + $liToOutdent.detach(); + $parentLi.after($liToOutdent); + + // If this node has one or more sublists, they will need to be indented + // by one with a fake parent to hold their previous position + if ($sublistContents.length > 0) { + spacerListHtml = '<' + listType + '>' + + '
  • ' + + ''; + $liToOutdent.append(spacerListHtml); + $sublist = $liToOutdent.children().last(); + // Add all of the sublistContents inside our new spacer li + $sublistContents.detach(); + $sublist.children('li').append($sublistContents); + } + + if ($subsequentSiblingContent.length > 0) { + // Nest the previously-subsequent items inside the list to + // retain order and their indent level + if (typeof $sublist === 'undefined') { + // Insert a sublist if we don't already have one + spacerListHtml = '<' + listType + '>'; + $liToOutdent.append(spacerListHtml); + $sublist = $liToOutdent.children().last(); + } + $subsequentSiblingContent.detach(); + $sublist.append($subsequentSiblingContent); + } + + // If we have a parentItem with content after our parent list + // eg.
      + //
    1. one + //
        + // two + //
          + // three + // + //
    + // we'll need to split that parentItem to retain proper content ordering + if ($subsequentParentListSiblingContent.length > 0) { + // Move the subsequent content in to a new list item after our parent li + $subsequentParentListSiblingContent.detach(); + + // If our last content and the first content in the subsequent content + // are both text nodes, insert a
    spacer to avoid crunching the + // text together visually. This maintains the same "visual" structure. + if ($liToOutdent.contents().length > 0 && + $liToOutdent.contents().last()[0].nodeType === WYMeditor.NODE.TEXT && + $subsequentParentListSiblingContent[0].nodeType === WYMeditor.NODE.TEXT) { + $liToOutdent.append('
    '); + } + + $liToOutdent.append($subsequentParentListSiblingContent); + } + + // Remove our parentList if it's empty and if we just removed that, the + // parentLi is likely to be empty also + if ($parentList.contents().length === 0) { + $parentList.remove(); + } + if ($parentLi.contents().length === 0) { + $parentLi.remove(); + } +}; + +/** + editor.correctInvalidListNesting + ================================ + + Take an li/ol/ul and correct any list nesting issues in the entire list + tree. + + Corrected lists have the following properties: + 1. ol and ul nodes *only* allow li children. + 2. All li nodes have either an ol or ul parent. + + The `alreadyCorrected` argument is used to indicate if a correction has + already been made, which means we need to return true even if no further + corrections are made. + + Returns true if any nodes were corrected. + */ +WYMeditor.editor.prototype.correctInvalidListNesting = function (listItem, alreadyCorrected) { + // Travel up the dom until we're at the root ol/ul/li + var currentNode = listItem, + parentNode, + tagName; + if (typeof alreadyCorrected === 'undefined') { + alreadyCorrected = false; + } + if (!currentNode) { + return alreadyCorrected; + } + while (currentNode.parentNode) { + parentNode = currentNode.parentNode; + if (parentNode.nodeType !== WYMeditor.NODE.ELEMENT) { + // Our parent is outside a valid list structure. Stop at this node. + break; + } + tagName = parentNode.tagName.toLowerCase(); + if (tagName !== 'ol' && tagName !== 'ul' && tagName !== 'li') { + // Our parent is outside a valid list structure. Stop at this node. + break; + + } + // We're still traversing up a list structure. Keep going + currentNode = parentNode; + } + // We have the root node. Make sure it's legit + if (jQuery(currentNode).is('li')) { + // We have an li as the "root" because its missing a parent list. + // Correct this problem and then try again to correct the nesting. + WYMeditor.console.log("Correcting orphaned root li before correcting invalid list nesting."); + this._correctOrphanedListItem(currentNode); + return this.correctInvalidListNesting(currentNode, true); + } + if (!jQuery(currentNode).is('ol,ul')) { + WYMeditor.console.error("Can't correct invalid list nesting. No root list found"); + return alreadyCorrected; + } + return this._correctInvalidListNesting(currentNode, alreadyCorrected); +}; +/** + editor._correctOrphanedListItem + =============================== + + Ensure that the given ophaned list item has a proper list parent. + + Uses the sibling nodes to determine what kind of list should be used. Also + wraps sibling adjacent orphaned li nodes in the same list. + */ +WYMeditor.editor.prototype._correctOrphanedListItem = function (listNode) { + var prevAdjacentLis, + nextAdjacentLis, + $adjacentLis = jQuery(), + prevList, + prevNode; + + prevAdjacentLis = jQuery(listNode).prevContentsUntil(':not(li)'); + nextAdjacentLis = jQuery(listNode).nextContentsUntil(':not(li)'); + + // Merge the collections together + $adjacentLis = $adjacentLis.add(prevAdjacentLis); + $adjacentLis = $adjacentLis.add(listNode); + $adjacentLis = $adjacentLis.add(nextAdjacentLis); + + // Determine if we have a list node in which to insert all of our orphaned + // li's + prevNode = $adjacentLis[0].previousSibling; + if (prevNode && jQuery(prevNode).is('ol,ul')) { + prevList = prevNode; + } else { + // No previous existing list to use. Need to create one + $adjacentLis.before('
      '); + prevList = $adjacentLis.prev()[0]; + } + + // Insert all of the adjacent orphaned lists inside the new parent + jQuery(prevList).append($adjacentLis); +}; + +/** + editor._correctInvalidListNesting + ================================ + + This is the function that actually does the list correction. + correctInvalidListNesting is just a helper function that first finds the root + of the list. + + Returns true if any correction was made. + + We use a reverse preorder traversal to navigate the DOM because we might be: + + * Making nodes children of their previous sibling (in the
        1. ...
      case) + * Adding nodes at the current level (wrapping any non-li node inside a ul/ol in a new li node) + + Adapted from code at: Tavs Dokkedahl from + http://www.jslab.dk/articles/non.recursive.preorder.traversal.part3 + */ +WYMeditor.editor.prototype._correctInvalidListNesting = function (listNode, alreadyCorrected) { + var rootNode = listNode, + currentNode = listNode, + wasCorrected = false, + previousSibling, + previousLi, + $currentNode, + tagName, + ancestorNode, + nodesToMove, + targetLi, + lastContentNode, + spacerHtml = '
    1. '; + if (typeof alreadyCorrected !== 'undefined') { + wasCorrected = alreadyCorrected; + } + + while (currentNode) { + if (currentNode._wym_visited) { + // This node has already been visited. + // Remove mark for visited nodes + currentNode._wym_visited = false; + // Once we reach the root element again traversal + // is done and we can break + if (currentNode === rootNode) { + break; + } + if (currentNode.previousSibling) { + currentNode = currentNode.previousSibling; + } else { + currentNode = currentNode.parentNode; + } + } else { + // This is the first visit for this node + + // All non-li nodes must have an + // ancestor li node that's closer than any ol/ul ancestor node. + // Basically, everything needs to be inside an li node. + if (currentNode !== rootNode && !jQuery(currentNode).is('li')) { + // We have a non-li node. + // Ensure that we have an li ancestor before we have any ol/ul + // ancestors + + ancestorNode = currentNode; + while (ancestorNode.parentNode) { + ancestorNode = ancestorNode.parentNode; + if (jQuery(ancestorNode).is('li')) { + // Everything is ok. Hit an li before a ol/ul + break; + } + + if (ancestorNode.nodeType !== WYMeditor.NODE.ELEMENT) { + // Our parent is outside a valid list structure. Stop at this node. + break; + } + tagName = ancestorNode.tagName.toLowerCase(); + if (tagName === 'ol' || tagName === 'ul') { + // We've hit a list container before any list item. + // This isn't valid html and causes editing problems. + // + // Convert the list to a valid structure that closely + // mimics the layout and behavior of invalidly nested + // content inside a list. The browser treats the + // content similar to how it would treat that same + // content separated by a
      within its previous + // sibling list item, so recreate that structure. + // + // The algorithm for performing this is basically: + // 1. Gather this and any previous siblings up until the + // previous li (if one exists) as content to move. + // 2. If we don't have any previous or subsequent li + // sibling, create a spacer li in which to insert all + // the gathered content. + // 3. Move all of the content to the previous li or the + // subsequent li (in that priority). + WYMeditor.console.log("Fixing orphaned list content"); + wasCorrected = true; + + // Gather this and previous sibling until the previous li + nodesToMove = [currentNode]; + previousSibling = currentNode; + targetLi = null; + while (previousSibling.previousSibling) { + previousSibling = previousSibling.previousSibling; + if (jQuery(previousSibling).is('li')) { + targetLi = previousSibling; + // We hit an li. Store it as the target in + // which to move the nodes + break; + } + nodesToMove.push(previousSibling); + } + + // We have our nodes ordered right to left and we need + // them left to right + nodesToMove.reverse(); + + // If there are no previous siblings, we can join the next li instead + if (!targetLi && nodesToMove.length === 1) { + if (jQuery(currentNode.nextSibling).is('li')) { + targetLi = currentNode.nextSibling; + } + } + + // If we still don't have an li in which to move, we + // need to create a spacer li + if (!targetLi) { + jQuery(nodesToMove[0]).before(spacerHtml); + targetLi = jQuery(nodesToMove[0]).prev()[0]; + } + + // Move all of our content inside the li we've chosen + // If the last content node inside the target li is + // text and so is the first content node to move, separate them + // with a
      to preserve the visual layout of them + // being on separate lines + lastContentNode = jQuery(targetLi).contents().last(); + if (lastContentNode.length === 1 && lastContentNode[0].nodeType === WYMeditor.NODE.TEXT) { + if (nodesToMove[0].nodeType === WYMeditor.NODE.TEXT) { + jQuery(targetLi).append('
      '); + } + } + jQuery(targetLi).append(jQuery(nodesToMove)); + + break; + + } + // We're still traversing up a list structure. Keep going + } + } + + if (currentNode.lastChild) { + // Since we have childnodes, mark this as visited because + // we'll return later + currentNode._wym_visited = true; + currentNode = currentNode.lastChild; + } else if (currentNode.previousSibling) { + currentNode = currentNode.previousSibling; + } else { + currentNode = currentNode.parentNode; + } + } + } + + return wasCorrected; +}; + +/** + * Get the common parent ol/ul for the given li nodes. If the closest parent + * ol/ul for each cell isn't the same, returns null. + */ +WYMeditor.editor.prototype.getCommonParentList = function (listItems) { + var firstLi, + parentList, + rootList; + + listItems = jQuery(listItems).filter('li'); + if (listItems.length === 0) { + return null; + } + firstLi = listItems[0]; + parentList = jQuery(firstLi).parent('ol,ul'); + + if (parentList.length === 0) { + return null; + } + rootList = parentList[0]; + + // Ensure that all of the li's have the same parent list + jQuery(listItems).each(function (index, elmnt) { + parentList = jQuery(elmnt).parent('ol,ul'); + if (parentList.length === 0 || parentList[0] !== rootList) { + return null; + } + }); + + return rootList; +}; + +/** + editor._getSelectedListItems + ============================ + + Based on the given selection, determine which li nodes are "selected" from + the user's standpoint. These are the li nodes that they would expect to be + affected by an action with the given selection. + + Generally, this means any li which has at least some of its text content + highlighted will be returned. +*/ +WYMeditor.editor.prototype._getSelectedListItems = function (sel) { + var wym = this, + i, + range, + selectedLi, + nodes = [], + liNodes = [], + containsNodeTextFilter, + parentsToAdd, + node, + $node, + $maybeParentLi; + + // Filter function to remove undesired nodes from what rangy.getNodes() + // gives + containsNodeTextFilter = function (testNode) { + var fullyContainsNodeText; + + // Include any partially-selected textNodes + if (rangy.dom.isCharacterDataNode(testNode)) { + return testNode; + } + + try { + fullyContainsNodeText = range.containsNodeText(testNode); + } catch (e) { + // Rangy throws an exception on an internal + // intersection call on the last node that's + // actually in the selection + return true; + } + + if (fullyContainsNodeText === true) { + // If we fully contain any text in this node, it's definitely + // selected + return true; + } + }; + + // Iterate through all of the selection ranges and include any li nodes + // that are user-selected in each range + for (i = 0; i < sel.rangeCount; i++) { + range = sel.getRangeAt(i); + if (range.collapsed === true) { + // Collapsed ranges don't return the range they're in as part of + // getNodes, so let's find the next list item up + selectedLi = wym.findUp(range.startContainer, 'li'); + if (selectedLi) { + nodes = nodes.concat([selectedLi]); + } + } else { + // getNodes includes the parent list item whenever we have our + // selection in a sublist. We need to make a distinction between + // when the parent list item is actually selected and when it's + // only sort of selected because we're selecting a sub-item + // (meaning it's partially selected). + // In the following case, we don't want `2` as one of our nodes: + // 1 + // 2 + // 2.1 + // 2|.2 + // 3| + nodes = nodes.concat( + range.getNodes( + [], + containsNodeTextFilter + ) + ); + + // We also need to include the parent li if we selected a non-li, non-list node. + // eg. if we select text inside an li, the user is actually + // selecting that entire li + parentsToAdd = []; + for (j = 0; j < nodes.length; j++) { + node = nodes[j]; + if (!jQuery(node).is('li,ol,ul')) { + // Crawl up the dom until we find an li + while (node.parentNode) { + node = node.parentNode; + if (jQuery(node).is('li')) { + parentsToAdd.push(node); + break; + } + } + } + } + // Add in all of the new parents if they're not already included + // (no duplicates) + for (j = 0; j < parentsToAdd.length; j++) { + if (jQuery.inArray(parentsToAdd[j], nodes) === -1) { + nodes.push(parentsToAdd[j]); + } + } + + + } + } + + // Filter out the non-li nodes + for (i = 0; i < nodes.length; i++) { + if (nodes[i].nodeType === WYMeditor.NODE.ELEMENT && + nodes[i].tagName.toLowerCase() === WYMeditor.LI) { + liNodes.push(nodes[i]); + } + } + + return liNodes; +}; + + +/** + editor.indent + ============= + + Indent the selected list items via the dom. + + Only list items that have a common list will be indented. + */ +WYMeditor.editor.prototype.indent = function () { + var wym = this._wym, + sel = rangy.getIframeSelection(this._iframe), + listItems, + rootList, + manipulationFunc; + + // First, make sure this list is properly structured + manipulationFunc = function () { + var selectedBlock = wym.selected(), + potentialListBlock = wym.findUp( + selectedBlock, + ['ol', 'ul', 'li'] + ); + return wym.correctInvalidListNesting(potentialListBlock); + }; + if (wym.restoreSelectionAfterManipulation(manipulationFunc)) { + // We actually made some list correction + // Don't actually perform the action if we've potentially just changed + // the list, and maybe the list appearance as a result. + return true; + } + + // We just changed and restored the selection when possibly correcting the + // lists + sel = rangy.getIframeSelection(this._iframe); + + // Gather the li nodes the user means to affect based on their current + // selection + listItems = wym._getSelectedListItems(sel); + + if (listItems.length === 0) { + return false; + } + + // If the selection is across paragraphs and other items at the root level, + // don't indent + rootList = wym.getCommonParentList(listItems); + if (rootList === null) { + return false; + } + + manipulationFunc = function () { + var domChanged = false; + + for (i = 0; i < listItems.length; i++) { + wym._indentSingleItem(listItems[i]); + domChanged = true; + } + + return domChanged; + }; + return wym.restoreSelectionAfterManipulation(manipulationFunc); +}; + +/** + editor.outdent + ============== + + Outdent a list item, accounting for firefox bugs to ensure consistent + behavior and valid HTML. +*/ +WYMeditor.editor.prototype.outdent = function () { + var wym = this._wym, + sel = rangy.getIframeSelection(this._iframe), + listItems, + rootList, + manipulationFunc; + + // First, make sure this list is properly structured + manipulationFunc = function () { + var selectedBlock = wym.selected(), + potentialListBlock = wym.findUp( + selectedBlock, + ['ol', 'ul', 'li'] + ); + return wym.correctInvalidListNesting(potentialListBlock); + }; + if (wym.restoreSelectionAfterManipulation(manipulationFunc)) { + // We actually made some list correction + // Don't actually perform the action if we've potentially just changed + // the list, and maybe the list appearance as a result. + return true; + } + + // We just changed and restored the selection when possibly correcting the + // lists + sel = rangy.getIframeSelection(this._iframe); + + // Gather the li nodes the user means to affect based on their current + // selection + listItems = wym._getSelectedListItems(sel); + + if (listItems.length === 0) { + return false; + } + + // If the selection is across paragraphs and other items at the root level, + // don't indent + rootList = wym.getCommonParentList(listItems); + if (rootList === null) { + return false; + } + + manipulationFunc = function () { + var domChanged = false; + + for (i = 0; i < listItems.length; i++) { + wym._outdentSingleItem(listItems[i]); + domChanged = true; + } + + return domChanged; + }; + return wym.restoreSelectionAfterManipulation(manipulationFunc); +}; + +/** + editor.restoreSelectionAfterManipulation + ======================================== + + A helper function to ensure that the selection is restored to the same + location after a potentially-complicated dom manipulation is performed. This + also handles the case where the dom manipulation throws and error by cleaning + up any selection markers that were added to the dom. + + `manipulationFunc` is a function that takes no arguments and performs the + manipulation. It should return `true` if changes were made that could have + potentially destroyed the selection. +*/ +WYMeditor.editor.prototype.restoreSelectionAfterManipulation = function (manipulationFunc) { + var sel = rangy.getIframeSelection(this._iframe), + savedSelection = rangy.saveSelection(rangy.dom.getIframeWindow(this._iframe)), + changesMade = true; + + // If something goes wrong, we don't want to leave selection markers + // floating around + try { + changesMade = manipulationFunc(); + if (changesMade) { + rangy.restoreSelection(savedSelection); + } else { + rangy.removeMarkers(savedSelection); + } + } catch (e) { + WYMeditor.console.error("Error during manipulation"); + WYMeditor.console.error(e); + rangy.removeMarkers(savedSelection); + } + + return changesMade; +}; + +/** + editor.insertOrderedlist + ========================= + + Convert the selected block in to an ordered list. + + If the selection is already inside a list, switch the type of the nearest + parent list to an `
        `. If the selection is in a block element that can be a + valid list, place that block element's contents inside an ordered list. + + Pure dom implementation consistently cross-browser implementation of + `execCommand(InsertOrderedList)`. + */ +WYMeditor.editor.prototype.insertOrderedlist = function () { + var wym = this, + manipulationFunc; + + // First, make sure this list is properly structured + manipulationFunc = function () { + var selectedBlock = wym.selected(), + potentialListBlock = wym.findUp( + selectedBlock, + ['ol', 'ul', 'li'] + ); + return wym.correctInvalidListNesting(potentialListBlock); + }; + if (wym.restoreSelectionAfterManipulation(manipulationFunc)) { + // We actually made some list correction + // Don't actually perform the action if we've potentially just changed + // the list, and maybe the list appearance as a result. + return true; + } + + // Actually perform the list insertion + manipulationFunc = function () { + return wym._insertList('ol'); + }; + + return wym.restoreSelectionAfterManipulation(manipulationFunc); +}; + +/** + editor.insertUnorderedlist + ========================= + + Convert the selected block in to an unordered list. + + Exactly the same as `editor.insert_orderedlist` except with `
          ` instead. + + Pure dom implementation consistently cross-browser implementation of + `execCommand(InsertUnorderedList)`. + */ +WYMeditor.editor.prototype.insertUnorderedlist = function () { + var wym = this, + manipulationFunc; + + // First, make sure this list is properly structured + manipulationFunc = function () { + var selectedBlock = wym.selected(), + potentialListBlock = wym.findUp( + selectedBlock, + ['ol', 'ul', 'li'] + ); + return wym.correctInvalidListNesting(potentialListBlock); + }; + if (wym.restoreSelectionAfterManipulation(manipulationFunc)) { + // We actually made some list correction + // Don't actually perform the action if we've potentially just changed + // the list, and maybe the list appearance as a result. + return true; + } + + // Actually perform the list insertion + manipulationFunc = function () { + return wym._insertList('ul'); + }; + + return wym.restoreSelectionAfterManipulation(manipulationFunc); +}; + +/** + editor._insertList + ========================= + + Convert the selected block in to the specified type of list. + + If the selection is already inside a list, switch the type of the nearest + parent list to an `
            `. If the selection is in a block element that can be a + valid list, place that block element's contents inside an ordered list. + + Returns `true` if a change was made, `false` otherwise. + */ +WYMeditor.editor.prototype._insertList = function (listType) { + var wym = this._wym, + sel = rangy.getIframeSelection(this._iframe), + listItems, + rootList, + selectedBlock, + potentialListBlock; + + listItems = wym._getSelectedListItems(sel); + + // If we've selected some list items all in the same list, we want to + // change the type of that list. + if (listItems.length !== 0) { + // If the selection is across paragraphs and other items at the root level, + // don't indent + rootList = wym.getCommonParentList(listItems); + if (rootList) { + this._changeListType(rootList, listType); + return true; + } else { + // We have a selection across multiple root-level lists. Punt on + // this case for now. + // TODO: Handle multiple root-level lists properly + return false; + } + + } + + // If we've selected a block-level item that's appropriate to convert in to a list, + // convert it. + selectedBlock = this.selected(); + // TODO: Use `_containerRules['root']` minus the ol/ul and + // `_containerRules['contentsCanConvertToList'] + potentialListBlock = this.findUp(selectedBlock, WYMeditor.POTENTIAL_LIST_ELEMENTS); + if (potentialListBlock) { + this._convertToList(potentialListBlock, listType); + return true; + } + + // The user has something selected that wouldn't be a valid list + return false; +}; + +WYMeditor.editor.prototype._changeListType = function (list, listType) { + // Wrap the contents in a new list of `listType` and remove the old list + // container. + return WYMeditor.changeNodeType(list, listType); +}; + +WYMeditor.editor.prototype._convertToList = function (blockElement, listType) { + var $blockElement = jQuery(blockElement), + newListHtml, + $newList; + + // ie6 doesn't support calling wrapInner with a dom node. Build html + newListHtml = '<' + listType + '>
          1. '; + + if (this.findUp(blockElement, WYMeditor.MAIN_CONTAINERS) === blockElement) { + // TODO: Handle ol/ul elements, since these are now in the `root` + // containers list + + // This is a main container block, so we can just replace it with the + // list structure + $blockElement.wrapInner(newListHtml); + $newList = $blockElement.children(); + $newList.unwrap(); + + return $newList.get(0); + } + // We're converting a block that's not a main container, so we need to nest + // this list around its contents and NOT remove the container (eg. a td + // node). + $blockElement.wrapInner(newListHtml); + $newList = $blockElement.children(); + + return $newList.get(0); +}; + +/** + editor.insertTable + ================== + + Insert a table at the current selection with the given number of rows + and columns and with the given caption and summary text. +*/ +WYMeditor.editor.prototype.insertTable = function (rows, columns, caption, summary) { + if ((rows <= 0) || (columns <= 0)) { + // We need rows and columns to make a table + + // TODO: It seems to me we should warn the user when zero columns and/or + // rows were entered. + return; + } + + var table = this._doc.createElement(WYMeditor.TABLE), + newRow = null, + newCol = null, + newCaption, + + x, + y, + container, + selectedNode; + + // Create the table caption + newCaption = table.createCaption(); + newCaption.innerHTML = caption; + + // Create the rows and cells + for (x = 0; x < rows; x += 1) { + newRow = table.insertRow(x); + for (y = 0; y < columns; y += 1) { + newRow.insertCell(y); + } + } + + if (summary !== "") { + // Only need to set the summary if we actually have a summary + jQuery(table).attr('summary', summary); + } + + // Find the currently-selected container + container = jQuery( + this.findUp(this.container(), WYMeditor.POTENTIAL_TABLE_INSERT_ELEMENTS) + ).get(0); + + if (!container || !container.parentNode) { + // No valid selected container. Put the table at the end. + jQuery(this._doc.body).append(table); + + } else if (jQuery.inArray(container.nodeName.toLowerCase(), + WYMeditor.INLINE_TABLE_INSERTION_ELEMENTS) > -1) { + // Insert table after selection if container is allowed to have tables + // inserted inline. + selectedNode = this.selection().focusNode; + + // If the selection is within a table, move the selection to the parent + // table to avoid nesting the tables. + if (jQuery.inArray(selectedNode.nodeName.toLowerCase(), + WYMeditor.SELECTABLE_TABLE_ELEMENTS) > -1 || + jQuery.inArray(selectedNode.parentNode.nodeName.toLowerCase(), + WYMeditor.SELECTABLE_TABLE_ELEMENTS) > -1) { + + while (selectedNode.nodeName.toLowerCase() !== WYMeditor.TABLE) { + selectedNode = selectedNode.parentNode; + } + } + + // If the list item itself is selected, append the table to it. If the + // selection is within the list item, put the table after it. Either + // way, this ensures the table will always be inserted within the list + // item. + if (selectedNode.nodeName.toLowerCase() === WYMeditor.LI) { + jQuery(selectedNode).append(table); + } else { + jQuery(selectedNode).after(table); + } + + } else { + // If the table is not allowed to be inserted inline with the + // container, insert it after the container. + jQuery(container).after(table); + } + + // Handle any browser-specific cleanup + this.afterInsertTable(table); + this.fixBodyHtml(); + + return table; +}; + +/** + editor.afterInsertTable + ======================= + + Handle cleanup/normalization after inserting a table. Different browsers + need slightly different tweaks. +*/ +WYMeditor.editor.prototype.afterInsertTable = function (table) {}; + +WYMeditor.editor.prototype.configureEditorUsingRawCss = function () { + var CssParser = new WYMeditor.WymCssParser(); + if (this._options.stylesheet) { + CssParser.parse( + jQuery.ajax({ + url: this._options.stylesheet, + async: false + }).responseText + ); + } else { + CssParser.parse(this._options.styles, false); + } + + if (this._options.classesItems.length === 0) { + this._options.classesItems = CssParser.css_settings.classesItems; + } + if (this._options.editorStyles.length === 0) { + this._options.editorStyles = CssParser.css_settings.editorStyles; + } + if (this._options.dialogStyles.length === 0) { + this._options.dialogStyles = CssParser.css_settings.dialogStyles; + } +}; + +WYMeditor.editor.prototype.listen = function () { + var wym = this; + + // Don't use jQuery.find() on the iframe body + // because of MSIE + jQuery + expando issue (#JQ1143) + + jQuery(this._doc.body).bind("mousedown", function (e) { + wym.mousedown(e); + }); +}; + +WYMeditor.editor.prototype.mousedown = function (evt) { + // Store the selected image if we clicked an tag + this._selected_image = null; + if (evt.target.tagName.toLowerCase() === WYMeditor.IMG) { + this._selected_image = evt.target; + } +}; + +/** + WYMeditor.loadCss + ================= + + Load a stylesheet in the document. + + href - The CSS path. +*/ +WYMeditor.loadCss = function (href) { + var link = document.createElement('link'), + head; + link.rel = 'stylesheet'; + link.href = href; + + head = jQuery('head').get(0); + head.appendChild(link); +}; + +/** + WYMeditor.editor.loadSkin + ========================= + + Load the skin CSS and initialization script (if needed). +*/ +WYMeditor.editor.prototype.loadSkin = function () { + // Does the user want to automatically load the CSS (default: yes)? + // We also test if it hasn't been already loaded by another instance + // see below for a better (second) test + if (this._options.loadSkin && !WYMeditor.SKINS[this._options.skin]) { + // Check if it hasn't been already loaded so we don't load it more + // than once (we check the existing elements) + var found = false, + rExp = new RegExp(this._options.skin + + '\/' + WYMeditor.SKINS_DEFAULT_CSS + '$'); + + jQuery('link').each(function () { + if (this.href.match(rExp)) { + found = true; + } + }); + + // Load it, using the skin path + if (!found) { + WYMeditor.loadCss( + this._options.skinPath + WYMeditor.SKINS_DEFAULT_CSS + ); + } + } + + // Put the classname (ex. wym_skin_default) on wym_box + jQuery(this._box).addClass("wym_skin_" + this._options.skin); + + // Does the user want to use some JS to initialize the skin (default: yes)? + // Also check if it hasn't already been loaded by another instance + if (this._options.initSkin && !WYMeditor.SKINS[this._options.skin]) { + eval(jQuery.ajax({url: this._options.skinPath + + WYMeditor.SKINS_DEFAULT_JS, async: false}).responseText); + } + + // Init the skin, if needed + if (WYMeditor.SKINS[this._options.skin] && WYMeditor.SKINS[this._options.skin].init) { + WYMeditor.SKINS[this._options.skin].init(this); + } +}; + +/** + * WYMeditor.DocumentStructureManager + * ================================== + * + * This manager controls the rules for the subset of HTML that WYMeditor will + * allow the user to create. For technical users, there are justifiable reasons + * to use any in-spec HTML, but for users of WYMeditor (not implementors), the + * goal is make it intuitive for them to create the structured markup that will + * fit their needs. + * + * For example, while it's valid HTML to have a mix + * of DIV and P tags at the top level of a document, in practice, this is + * confusing for non-technical users. The DocumentStructureManager allows the + * WYMeditor implementor to standardize on P tags in the root of the document, + * which will automatically convert DIV tags in the root to P tags. + * + */ +WYMeditor.DocumentStructureManager = function(wym, defaultRootContainer) { + this._wym = wym; + this.structureRules = WYMeditor.DocumentStructureManager.DEFAULTS; + this.setDefaultRootContainer(defaultRootContainer); +}; + +jQuery.extend(WYMeditor.DocumentStructureManager, { + // Only these containers are allowed as valid `defaultRootContainer` + // options. + VALID_DEFAULT_ROOT_CONTAINERS : [ + "p", + "div" + ], + + // Cooresponding titles for use in the containers panel for the valid + // default root containers + DEFAULT_ROOT_CONTAINER_TITLES : { + p: "Paragraph", + div: "Division" + }, + + // These containers prevent the user from using up/down/enter/backspace + // to move above or below them, thus effectively blocking the creation + // of new blocks. We must use temporary spacer elements to correct this + // while the document is being edited. + CONTAINERS_BLOCKING_NAVIGATION : ["table", "blockquote", "pre"], + + + DEFAULTS : { + // By default, this container will be used for all root contents. This + // defines the container used when "enter" is pressed from the root and + // also which container wraps or replaces containers found at the root + // that aren't allowed. Only + // `DocumentStructureManager.VALID_DEFAULT_ROOT_CONTAINERS` are allowed + // here. Whichever you choose, the VALID_DEFAULT_ROOT_CONTAINERS will + // be automatically converted when found at the top level of your + // document. + defaultRootContainer: 'p', + + // These containers cannot be used as root containers. This includes + // any default root containers that are not the chosen default root + // container. By default, this is set to the list of valid root + // containers that are not the defaultRootContainer. + notValidRootContainers: ['div'], + + // Only these containers are allowed as a direct child of the body tag. + // All other containers located there will be wrapped in the + // `defaultRootContainer`, unless they're one of the tags in + // `convertIfRoot`. + validRootContainers: [ + 'p', + 'div', + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6', + 'pre', + 'blockquote', + 'table', + 'ol', + 'ul' + ], + + // If these tags are found in the root, they'll be converted to the + // `defaultRootContainer` container instead of the default of being + // wrapped. + convertIfRootContainers: [ + 'div' + ], + + // The elements that are allowed to be turned in to lists. + validListConversionTargetContainers: [ + "p", + "div", + "h1", + "h2", + "h3", + "h4", + "h5", + "h6", + "pre", + "blockquote", + "td", + "th" + ], + + // For most block elements, the default behavior when a user attempts + // to convert it to a list is to convert that block to a ol/ul and wrap + // the contents in an li. For containers in `wrapContentsInList`, + // instead of converting the container, we should just wrap the + // contents of the container in a ul/ol + li. + wrapContentsInList: [ + 'td', + 'th' + ] + } +}); + +/** + * Set the default container created/used in the root. + * + * @param defaultRootContainer String A string representation of the tag to + * use. + */ +WYMeditor.DocumentStructureManager.prototype.setDefaultRootContainer = function( + defaultRootContainer +) { + var validContainers, + index; + + if (this.structureRules.defaultRootContainer === defaultRootContainer) { + // This is already our current configuration. No need to do the + // work again. + return; + } + + // Make sure the new container is one of the valid options + validContainers = WYMeditor.DocumentStructureManager.VALID_DEFAULT_ROOT_CONTAINERS; + index = jQuery.inArray(defaultRootContainer, validContainers); + if (index === -1) { + throw new Error( + "a defaultRootContainer of '" + + defaultRootContainer + + "' is not supported" + ); + } + + this.structureRules.defaultRootContainer = defaultRootContainer; + + // No other possible option for default root containers is valid expect for + // the one choosen default root container + this.structureRules.notValidRootContainers = + WYMeditor.DocumentStructureManager.VALID_DEFAULT_ROOT_CONTAINERS; + this.structureRules.notValidRootContainers.splice(index, 1); + + this.adjustDefaultRootContainerUI(); + + // TODO: Actually do all of the switching required to move from p to div or + // from div to p for the topLevelContainer +}; + +/** + adjustDefaultRootContainerUI + ============================ + + Adds a new link for the default root container to the containers panel in + the editor if needed, and removes any other links for valid default root + containers form the containers panel besides the link for the chosen + default root container. +*/ +WYMeditor.DocumentStructureManager.prototype.adjustDefaultRootContainerUI = function() { + var wym = this._wym, + defaultRootContainer = this.structureRules.defaultRootContainer, + $containerItems, + $containerLink, + $newContainerItem, + containerName, + newContainerLinkNeeded, + newContainerLinkHtml, + i; + + $containerItems = jQuery(wym._box).find(wym._options.containersSelector) + .find('li'); + newContainerLinkNeeded = true; + + // Remove container links for any other valid default root container from + // the containers panel besides the link for the chosen default root + // container + for (i = 0; i < $containerItems.length; ++i) { + $containerLink = $containerItems.eq(i).find('a'); + containerName = $containerLink.attr('name').toLowerCase(); + if (jQuery.inArray(containerName, + this.structureRules.notValidRootContainers) > -1) { + $containerItems.eq(i).remove(); + } + if (containerName === defaultRootContainer) { + newContainerLinkNeeded = false; + } + } + + // Add new link for the default root container to the containers panel if + // needed + if (newContainerLinkNeeded) { + newContainerLinkHtml = wym._options.containersItemHtml; + newContainerLinkHtml = WYMeditor.Helper.replaceAll( + newContainerLinkHtml, + WYMeditor.CONTAINER_NAME, + defaultRootContainer.toUpperCase() + ); + newContainerLinkHtml = WYMeditor.Helper.replaceAll( + newContainerLinkHtml, + WYMeditor.CONTAINER_TITLE, + WYMeditor.DocumentStructureManager.DEFAULT_ROOT_CONTAINER_TITLES[defaultRootContainer] + ); + newContainerLinkHtml = WYMeditor.Helper.replaceAll( + newContainerLinkHtml, + WYMeditor.CONTAINER_CLASS, + "wym_containers_" + defaultRootContainer + ); + $newContainerItem = jQuery(newContainerLinkHtml); + $containerItems = jQuery(wym._box).find(wym._options.containersSelector) + .find('li'); + $containerItems.eq(0).before($newContainerItem); + + // Bind click event for the new link + $newContainerItem.find('a').click(function () { + wym.container(jQuery(this).attr(WYMeditor.NAME)); + return false; + }); + } +} + +/*jslint evil: true */ +/* + * WYMeditor : what you see is What You Mean web-based editor + * Copyright (c) 2005 - 2009 Jean-Francois Hovinne, http://www.wymeditor.org/ + * Dual licensed under the MIT (MIT-license.txt) + * and GPL (GPL-license.txt) licenses. + * + * For further information visit: + * http://www.wymeditor.org/ + * + * File Name: + * jquery.wymeditor.explorer.js + * MSIE specific class and functions. + * See the documentation for more info. + * + * File Authors: + * Jean-Francois Hovinne (jf.hovinne a-t wymeditor dotorg) + * Bermi Ferrer (wymeditor a-t bermi dotorg) + * Frédéric Palluel-Lafleur (fpalluel a-t gmail dotcom) + * Jonatan Lundin (jonatan.lundin a-t gmail dotcom) + */ + +WYMeditor.WymClassExplorer = function (wym) { + this._wym = wym; + this._class = "className"; +}; + +WYMeditor.WymClassExplorer.prototype.initIframe = function (iframe) { + //This function is executed twice, though it is called once! + //But MSIE needs that, otherwise designMode won't work. + //Weird. + this._iframe = iframe; + this._doc = iframe.contentWindow.document; + + //add css rules from options + var styles = this._doc.styleSheets[0]; + var aCss = eval(this._options.editorStyles); + + this.addCssRules(this._doc, aCss); + + this._doc.title = this._wym._index; + + //set the text direction + jQuery('html', this._doc).attr('dir', this._options.direction); + + //init html value + jQuery(this._doc.body).html(this._wym._options.html); + + //handle events + var wym = this; + + this._doc.body.onfocus = function () { + wym._doc.designMode = "on"; + wym._doc = iframe.contentWindow.document; + }; + this._doc.onbeforedeactivate = function () { + wym.saveCaret(); + }; + jQuery(this._doc).bind('keyup', wym.keyup); + // Workaround for an ie8 => ie7 compatibility mode bug triggered + // intermittently by certain combinations of CSS on the iframe + var ieVersion = parseInt(jQuery.browser.version, 10); + if (ieVersion >= 8 && ieVersion < 9) { + jQuery(this._doc).bind('keydown', function () { + wym.fixBluescreenOfDeath(); + }); + } + this._doc.onkeyup = function () { + wym.saveCaret(); + }; + this._doc.onclick = function () { + wym.saveCaret(); + }; + + this._doc.body.onbeforepaste = function () { + wym._iframe.contentWindow.event.returnValue = false; + }; + + this._doc.body.onpaste = function () { + wym._iframe.contentWindow.event.returnValue = false; + wym.paste(window.clipboardData.getData("Text")); + }; + + //callback can't be executed twice, so we check + if (this._initialized) { + + //pre-bind functions + if (jQuery.isFunction(this._options.preBind)) { + this._options.preBind(this); + } + + + //bind external events + this._wym.bindEvents(); + + //post-init functions + if (jQuery.isFunction(this._options.postInit)) { + this._options.postInit(this); + } + + //add event listeners to doc elements, e.g. images + this.listen(); + } + + this._initialized = true; + + //init designMode + this._doc.designMode = "on"; + try { + // (bermi's note) noticed when running unit tests on IE6 + // Is this really needed, it trigger an unexisting property on IE6 + this._doc = iframe.contentWindow.document; + } catch (e) {} +}; + +(function (editorLoadSkin) { + WYMeditor.WymClassExplorer.prototype.loadSkin = function () { + // Mark container items as unselectable (#203) + // Fix for issue explained: + // http://stackoverflow.com/questions/1470932/ie8-iframe-designmode-loses-selection + jQuery(this._box).find(this._options.containerSelector).attr('unselectable', 'on'); + + editorLoadSkin.call(this); + }; +}(WYMeditor.editor.prototype.loadSkin)); + +/** + fixBluescreenOfDeath + ==================== + + In ie8 when using ie7 compatibility mode, certain combinations of CSS on + the iframe will trigger a bug that causes the rendering engine to give all + block-level editable elements a negative left position that puts them off of + the screen. This results in the editor looking blank (just the blue background) + and requires the user to move the mouse or manipulate the DOM to force a + re-render, which fixes the problem. + + This workaround detects the negative position and then manipulates the DOM + to cause a re-render, which puts the elements back in position. + + A real fix would be greatly appreciated. +*/ +WYMeditor.WymClassExplorer.prototype.fixBluescreenOfDeath = function () { + var position = jQuery(this._doc).find('p').eq(0).position(); + if (position !== null && typeof position !== 'undefined' && position.left < 0) { + jQuery(this._box).append('
            '); + jQuery(this._box).find('#wym-bluescreen-bug-fix').remove(); + } +}; + + +WYMeditor.WymClassExplorer.prototype._exec = function (cmd, param) { + if (param) { + this._doc.execCommand(cmd, false, param); + } else { + this._doc.execCommand(cmd); + } +}; + +WYMeditor.WymClassExplorer.prototype.saveCaret = function () { + this._doc.caretPos = this._doc.selection.createRange(); +}; + +WYMeditor.WymClassExplorer.prototype.addCssRule = function (styles, oCss) { + // IE doesn't handle combined selectors (#196) + var selectors = oCss.name.split(','), + i; + for (i = 0; i < selectors.length; i++) { + styles.addRule(selectors[i], oCss.css); + } +}; + +WYMeditor.WymClassExplorer.prototype.insert = function (html) { + + // Get the current selection + var range = this._doc.selection.createRange(); + + // Check if the current selection is inside the editor + if (jQuery(range.parentElement()).parents().is(this._options.iframeBodySelector)) { + try { + // Overwrite selection with provided html + range.pasteHTML(html); + } catch (e) {} + } else { + // Fall back to the internal paste function if there's no selection + this.paste(html); + } +}; + +WYMeditor.WymClassExplorer.prototype.wrap = function (left, right) { + // Get the current selection + var range = this._doc.selection.createRange(); + + // Check if the current selection is inside the editor + if (jQuery(range.parentElement()).parents().is(this._options.iframeBodySelector)) { + try { + // Overwrite selection with provided html + range.pasteHTML(left + range.text + right); + } catch (e) {} + } +}; + +/** + wrapWithContainer + ================= + + Wraps the passed node in a container of the passed type. Also, restores the + selection to being after the node within its new container. + + @param node A DOM node to be wrapped in a container + @param containerType A string of an HTML tag that specifies the container + type to use for wrapping the node. +*/ +WYMeditor.WymClassExplorer.prototype.wrapWithContainer = function (node, containerType) { + var wym = this._wym, + $wrappedNode, + selection, + range; + + $wrappedNode = jQuery(node).wrap('<' + containerType + ' />'); + selection = rangy.getIframeSelection(wym._iframe); + range = rangy.createRange(wym._doc); + range.selectNodeContents($wrappedNode[0]); + range.collapse(); + selection.setSingleRange(range); +}; + +WYMeditor.WymClassExplorer.prototype.unwrap = function () { + // Get the current selection + var range = this._doc.selection.createRange(); + + // Check if the current selection is inside the editor + if (jQuery(range.parentElement()).parents().is(this._options.iframeBodySelector)) { + try { + // Unwrap selection + var text = range.text; + this._exec('Cut'); + range.pasteHTML(text); + } catch (e) {} + } +}; + +WYMeditor.WymClassExplorer.prototype.keyup = function (evt) { + //'this' is the doc + var wym = WYMeditor.INSTANCES[this.title], + container, + defaultRootContainer, + notValidRootContainers, + name, + parentName, + forbiddenMainContainer = false; + + notValidRootContainers = + wym.documentStructureManager.structureRules.notValidRootContainers; + defaultRootContainer = + wym.documentStructureManager.structureRules.defaultRootContainer; + this._selected_image = null; + + // If the inputted key cannont create a block element and is not a command, + // check to make sure the selection is properly wrapped in a container + if (!wym.keyCanCreateBlockElement(evt.which) && + evt.which !== WYMeditor.KEY.CTRL && + evt.which !== WYMeditor.KEY.COMMAND && + !evt.metaKey && + !evt.ctrlKey) { + + container = wym.selected(); + selectedNode = wym.selection().focusNode; + if (container !== null) { + name = container.tagName.toLowerCase(); + } + if (container.parentNode) { + parentName = container.parentNode.tagName.toLowerCase(); + } + + // Fix forbidden main containers + if (wym.isForbiddenMainContainer(name)) { + name = parentName; + forbiddenMainContainer = true; + } + + // Wrap text nodes and forbidden main containers with default root node + // tags + if (name === WYMeditor.BODY && selectedNode.nodeName === "#text") { + // If we're in a forbidden main container, switch the selected node + // to its parent node so that we wrap the forbidden main container + // itself and not its inner text content + if (forbiddenMainContainer) { + selectedNode = selectedNode.parentNode; + } + wym.wrapWithContainer(selectedNode, defaultRootContainer); + wym.fixBodyHtml(); + } + + if (jQuery.inArray(name, notValidRootContainers) > -1 && + parentName === WYMeditor.BODY) { + wym.switchTo(container, defaultRootContainer); + wym.fixBodyHtml(); + } + } + + // If we potentially created a new block level element or moved to a new + // one, then we should ensure the container is valid and the formatting is + // proper. + if (wym.keyCanCreateBlockElement(evt.which)) { + // If the selected container is a root container, make sure it is not a + // different possible default root container than the chosen one. + container = wym.selected(); + name = container.tagName.toLowerCase(); + if (container.parentNode) { + parentName = container.parentNode.tagName.toLowerCase(); + } + if (jQuery.inArray(name, notValidRootContainers) > -1 && + parentName === WYMeditor.BODY) { + wym.switchTo(container, defaultRootContainer); + } + + // Fix formatting if necessary + wym.fixBodyHtml(); + } +}; + +/*jslint evil: true */ +/* + * WYMeditor : what you see is What You Mean web-based editor + * Copyright (c) 2005 - 2009 Jean-Francois Hovinne, http://www.wymeditor.org/ + * Dual licensed under the MIT (MIT-license.txt) + * and GPL (GPL-license.txt) licenses. + * + * For further information visit: + * http://www.wymeditor.org/ + * + * File Name: + * jquery.wymeditor.mozilla.js + * Gecko specific class and functions. + * See the documentation for more info. + * + * File Authors: + * Jean-Francois Hovinne (jf.hovinne a-t wymeditor dotorg) + * Volker Mische (vmx a-t gmx dotde) + * Bermi Ferrer (wymeditor a-t bermi dotorg) + * Frédéric Palluel-Lafleur (fpalluel a-t gmail dotcom) + * Jonatan Lundin (jonatan.lundin a-t gmail dotcom) + */ + +WYMeditor.WymClassMozilla = function (wym) { + this._wym = wym; + this._class = "class"; +}; + +// Placeholder cell to allow content in TD cells for FF 3.5+ +WYMeditor.WymClassMozilla.CELL_PLACEHOLDER = '
            '; + +// Firefox 3.5 and 3.6 require the CELL_PLACEHOLDER and 4.0 doesn't +WYMeditor.WymClassMozilla.NEEDS_CELL_FIX = parseInt( + jQuery.browser.version, 10) === 1 && + jQuery.browser.version >= '1.9.1' && + jQuery.browser.version < '2.0'; + +WYMeditor.WymClassMozilla.prototype.initIframe = function (iframe) { + var wym = this, + styles, + aCss; + + this._iframe = iframe; + this._doc = iframe.contentDocument; + + //add css rules from options + styles = this._doc.styleSheets[0]; + + aCss = eval(this._options.editorStyles); + + this.addCssRules(this._doc, aCss); + + this._doc.title = this._wym._index; + + //set the text direction + jQuery('html', this._doc).attr('dir', this._options.direction); + + //init html value + this._html(this._wym._options.html); + + //init designMode + this.enableDesignMode(); + + //pre-bind functions + if (jQuery.isFunction(this._options.preBind)) { + this._options.preBind(this); + } + + //bind external events + this._wym.bindEvents(); + + //bind editor keydown events + jQuery(this._doc).bind("keydown", this.keydown); + + //bind editor keyup events + jQuery(this._doc).bind("keyup", this.keyup); + + //bind editor click events + jQuery(this._doc).bind("click", this.click); + + //bind editor focus events (used to reset designmode - Gecko bug) + jQuery(this._doc).bind("focus", function () { + // Fix scope + wym.enableDesignMode.call(wym); + }); + + //post-init functions + if (jQuery.isFunction(this._options.postInit)) { + this._options.postInit(this); + } + + //add event listeners to doc elements, e.g. images + this.listen(); +}; + +/** @name html + * @description Get/Set the html value + */ +WYMeditor.WymClassMozilla.prototype._html = function (html) { + if (typeof html === 'string') { + //disable designMode + try { + this._doc.designMode = "off"; + } catch (e) { + //do nothing + } + + //replace em by i and strong by bold + //(designMode issue) + html = html.replace(/]*)>/gi, ""); + html = html.replace(/<\/em>/gi, ""); + html = html.replace(/]*)>/gi, ""); + html = html.replace(/<\/strong>/gi, ""); + + //update the html body + jQuery(this._doc.body).html(html); + this._wym.fixBodyHtml(); + + //re-init designMode + this.enableDesignMode(); + } else { + return jQuery(this._doc.body).html(); + } + return false; +}; + +WYMeditor.WymClassMozilla.prototype._exec = function (cmd, param) { + if (!this.selected()) { + return false; + } + + if (param) { + this._doc.execCommand(cmd, '', param); + } else { + this._doc.execCommand(cmd, '', null); + } + + //set to P if parent = BODY + var container = this.selected(); + if (container && container.tagName.toLowerCase() === WYMeditor.BODY) { + this._exec(WYMeditor.FORMAT_BLOCK, WYMeditor.P); + this.fixBodyHtml(); + } + + return true; +}; + +WYMeditor.WymClassMozilla.prototype.addCssRule = function (styles, oCss) { + + styles.insertRule(oCss.name + " {" + oCss.css + "}", + styles.cssRules.length); +}; + +//keydown handler, mainly used for keyboard shortcuts +WYMeditor.WymClassMozilla.prototype.keydown = function (evt) { + //'this' is the doc + var wym = WYMeditor.INSTANCES[this.title]; + + if (evt.ctrlKey) { + if (evt.which === 66) { + //CTRL+b => STRONG + wym._exec(WYMeditor.BOLD); + return false; + } + if (evt.which === 73) { + //CTRL+i => EMPHASIS + wym._exec(WYMeditor.ITALIC); + return false; + } + } + + return true; +}; + +// Keyup handler, mainly used for cleanups +WYMeditor.WymClassMozilla.prototype.keyup = function (evt) { + // 'this' is the doc + var wym = WYMeditor.INSTANCES[this.title], + container, + defaultRootContainer, + notValidRootContainers, + name, + parentName; + + notValidRootContainers = + wym.documentStructureManager.structureRules.notValidRootContainers; + defaultRootContainer = + wym.documentStructureManager.structureRules.defaultRootContainer; + wym._selected_image = null; + container = null; + + // If the inputted key cannont create a block element and is not a command, + // check to make sure the selection is properly wrapped in a container + if (!wym.keyCanCreateBlockElement(evt.which) && + evt.which !== WYMeditor.KEY.CTRL && + evt.which !== WYMeditor.KEY.COMMAND && + !evt.metaKey && + !evt.ctrlKey) { + + container = wym.selected(); + name = container.tagName.toLowerCase(); + if (container.parentNode) { + parentName = container.parentNode.tagName.toLowerCase(); + } + + // Fix forbidden main containers + if (wym.isForbiddenMainContainer(name)) { + name = parentName; + } + + // Replace text nodes with default root tags and make sure the + // container is valid if it is a root container + if (name === WYMeditor.BODY || + (jQuery.inArray(name, notValidRootContainers) > -1 && + parentName === WYMeditor.BODY)) { + + wym._exec(WYMeditor.FORMAT_BLOCK, defaultRootContainer); + wym.fixBodyHtml(); + } + } + + // If we potentially created a new block level element or moved to a new + // one, then we should ensure the container is valid and the formatting is + // proper. + if (wym.keyCanCreateBlockElement(evt.which)) { + // If the selected container is a root container, make sure it is not a + // different possible default root container than the chosen one. + container = wym.selected(); + name = container.tagName.toLowerCase(); + if (container.parentNode) { + parentName = container.parentNode.tagName.toLowerCase(); + } + if (jQuery.inArray(name, notValidRootContainers) > -1 && + parentName === WYMeditor.BODY) { + wym._exec(WYMeditor.FORMAT_BLOCK, defaultRootContainer); + } + + // Fix formatting if necessary + wym.fixBodyHtml(); + } +}; + +WYMeditor.WymClassMozilla.prototype.click = function (evt) { + var wym = WYMeditor.INSTANCES[this.title], + container = wym.selected(), + sel; + + if (WYMeditor.WymClassMozilla.NEEDS_CELL_FIX === true) { + if (container && container.tagName.toLowerCase() === WYMeditor.TR) { + // Starting with FF 3.6, inserted tables need some content in their + // cells before they're editable + jQuery(WYMeditor.TD, wym._doc.body). + append(WYMeditor.WymClassMozilla.CELL_PLACEHOLDER); + + // The user is still going to need to move out of and then back in + // to this cell if the table was inserted via an inner_html call + // (like via the manual HTML editor). + // TODO: Use rangy or some other selection library to consistently + // put the users selection out of and then back in this cell + // so that it appears to be instantly editable + // Once accomplished, can remove the afterInsertTable handling + } + } + + if (container && container.tagName.toLowerCase() === WYMeditor.BODY) { + // A click in the body means there is no content at all, so we + // should automatically create a starter paragraph + sel = wym._iframe.contentWindow.getSelection(); + if (sel.isCollapsed === true) { + // If the selection isn't collapsed, we might have a selection that + // drags over the body, but we shouldn't turn everything in to a + // paragraph tag. Otherwise, double-clicking in the space to the + // right of an h2 tag would turn it in to a paragraph + wym._exec(WYMeditor.FORMAT_BLOCK, WYMeditor.P); + } + } +}; + +WYMeditor.WymClassMozilla.prototype.enableDesignMode = function () { + if (this._doc.designMode === "off") { + try { + this._doc.designMode = "on"; + this._doc.execCommand("styleWithCSS", '', false); + this._doc.execCommand("enableObjectResizing", false, true); + } catch (e) {} + } +}; + +/* + * Fix new cell contents and ability to insert content at the front and end of + * the contents. + */ +WYMeditor.WymClassMozilla.prototype.afterInsertTable = function (table) { + if (WYMeditor.WymClassMozilla.NEEDS_CELL_FIX === true) { + // In certain FF versions, inserted tables need some content in their + // cells before they're editable, otherwise the user has to move focus + // in and then out of a cell first, even with our click() hack + jQuery(table).find('td').each(function (index, element) { + jQuery(element).append(WYMeditor.WymClassMozilla.CELL_PLACEHOLDER); + }); + } +}; + +/*jslint evil: true */ +/* + * WYMeditor : what you see is What You Mean web-based editor + * Copyright (c) 2005 - 2009 Jean-Francois Hovinne, http://www.wymeditor.org/ + * Dual licensed under the MIT (MIT-license.txt) + * and GPL (GPL-license.txt) licenses. + * + * For further information visit: + * http://www.wymeditor.org/ + * + * File Name: + * jquery.wymeditor.opera.js + * Opera specific class and functions. + * See the documentation for more info. + * + * File Authors: + * Jean-Francois Hovinne (jf.hovinne a-t wymeditor dotorg) + */ + +WYMeditor.WymClassOpera = function(wym) { + this._wym = wym; + this._class = "class"; +}; + +WYMeditor.WymClassOpera.prototype.initIframe = function(iframe) { + this._iframe = iframe; + this._doc = iframe.contentWindow.document; + + //add css rules from options + var styles = this._doc.styleSheets[0]; + var aCss = eval(this._options.editorStyles); + + this.addCssRules(this._doc, aCss); + + this._doc.title = this._wym._index; + + //set the text direction + jQuery('html', this._doc).attr('dir', this._options.direction); + + //init designMode + this._doc.designMode = "on"; + + //init html value + this._html(this._wym._options.html); + + //pre-bind functions + if (jQuery.isFunction(this._options.preBind)) { + this._options.preBind(this); + } + + //bind external events + this._wym.bindEvents(); + + //bind editor keydown events + jQuery(this._doc).bind("keydown", this.keydown); + + //bind editor events + jQuery(this._doc).bind("keyup", this.keyup); + + //post-init functions + if (jQuery.isFunction(this._options.postInit)) { + this._options.postInit(this); + } + + //add event listeners to doc elements, e.g. images + this.listen(); +}; + +WYMeditor.WymClassOpera.prototype._exec = function(cmd, param) { + if (param) { + this._doc.execCommand(cmd, false, param); + } + else { + this._doc.execCommand(cmd); + } +}; + +WYMeditor.WymClassOpera.prototype.selected = function() { + var sel = this._iframe.contentWindow.getSelection(); + var node = sel.focusNode; + if (node) { + if (node.nodeName === "#text") { + return node.parentNode; + } else { + return node; + } + } else { + return null; + } +}; + +WYMeditor.WymClassOpera.prototype.addCssRule = function(styles, oCss) { + styles.insertRule( + oCss.name + " {" + oCss.css + "}", styles.cssRules.length); +}; + +WYMeditor.WymClassOpera.prototype.keydown = function(evt) { + //'this' is the doc + var wym = WYMeditor.INSTANCES[this.title]; + var sel = wym._iframe.contentWindow.getSelection(); + startNode = sel.getRangeAt(0).startContainer; + + //Get a P instead of no container + if (!jQuery(startNode).parentsOrSelf(WYMeditor.MAIN_CONTAINERS.join(","))[0] && + !jQuery(startNode).parentsOrSelf('li') && + !keyCanCreateBlockElement(evt.which)) { + + wym._exec(WYMeditor.FORMAT_BLOCK, WYMeditor.P); + } +}; + +WYMeditor.WymClassOpera.prototype.keyup = function(evt) { + //'this' is the doc + var wym = WYMeditor.INSTANCES[this.title]; + wym._selected_image = null; +}; +/*jslint evil: true */ +/* + * WYMeditor : what you see is What You Mean web-based editor + * Copyright (c) 2005 - 2009 Jean-Francois Hovinne, http://www.wymeditor.org/ + * Dual licensed under the MIT (MIT-license.txt) + * and GPL (GPL-license.txt) licenses. + * + * For further information visit: + * http://www.wymeditor.org/ + * + * File Name: + * jquery.wymeditor.safari.js + * Safari specific class and functions. + * See the documentation for more info. + * + * File Authors: + * Jean-Francois Hovinne (jf.hovinne a-t wymeditor dotorg) + * Scott Lewis (lewiscot a-t gmail dotcom) + */ + +WYMeditor.WymClassSafari = function (wym) { + this._wym = wym; + this._class = "class"; +}; + +WYMeditor.WymClassSafari.prototype.initIframe = function (iframe) { + var wym = this, + styles, + aCss; + + this._iframe = iframe; + this._doc = iframe.contentDocument; + + //add css rules from options + styles = this._doc.styleSheets[0]; + aCss = eval(this._options.editorStyles); + + this.addCssRules(this._doc, aCss); + + this._doc.title = this._wym._index; + + //set the text direction + jQuery('html', this._doc).attr('dir', this._options.direction); + + //init designMode + this._doc.designMode = "on"; + + //init html value + this._html(this._wym._options.html); + + //pre-bind functions + if (jQuery.isFunction(this._options.preBind)) { + this._options.preBind(this); + } + + //bind external events + this._wym.bindEvents(); + + //bind editor keydown events + jQuery(this._doc).bind("keydown", this.keydown); + + //bind editor keyup events + jQuery(this._doc).bind("keyup", this.keyup); + + //post-init functions + if (jQuery.isFunction(this._options.postInit)) { + this._options.postInit(this); + } + + //add event listeners to doc elements, e.g. images + this.listen(); +}; + +WYMeditor.WymClassSafari.prototype._exec = function (cmd, param) { + if (!this.selected()) { + return false; + } + + var container, + $container, + tagName; + + if (param) { + this._doc.execCommand(cmd, '', param); + } else { + this._doc.execCommand(cmd, '', null); + } + + container = this.selected(); + if (container) { + $container = jQuery(container); + tagName = container.tagName.toLowerCase(); + + // Wrap this content in the default root container if we're in the body + if (tagName === WYMeditor.BODY) { + this._exec(WYMeditor.FORMAT_BLOCK, + this.documentStructureManager.structureRules.defaultRootContainer); + this.fixBodyHtml(); + } + + // If the cmd was FORMAT_BLOCK, check if the block was moved outside + // the body after running the command. If it was moved outside, move it + // back inside the body. This was added because running FORMAT_BLOCK on + // an image inserted outside of a container was causing it to be moved + // outside the body (See issue #400). + if (cmd === WYMeditor.FORMAT_BLOCK && + $container.siblings('body.wym_iframe').length) { + + $container.siblings('body.wym_iframe').append(container); + } + + // If the container is a span, strip it out if it doesn't have a class + // but has an inline style of 'font-weight: normal;'. + if (tagName === 'span' && + (!$container.attr('class') || + $container.attr('class').toLowerCase() === 'apple-style-span') && + $container.attr('style') === 'font-weight: normal;') { + + $container.contents().unwrap(); + } + } + + return true; +}; + +WYMeditor.WymClassSafari.prototype.addCssRule = function (styles, oCss) { + styles.insertRule(oCss.name + " {" + oCss.css + "}", + styles.cssRules.length); +}; + + +//keydown handler, mainly used for keyboard shortcuts +WYMeditor.WymClassSafari.prototype.keydown = function (e) { + //'this' is the doc + var wym = WYMeditor.INSTANCES[this.title]; + + if (e.ctrlKey) { + if (e.which === WYMeditor.KEY.B) { + //CTRL+b => STRONG + wym._exec(WYMeditor.BOLD); + e.preventDefault(); + } + if (e.which === WYMeditor.KEY.I) { + //CTRL+i => EMPHASIS + wym._exec(WYMeditor.ITALIC); + e.preventDefault(); + } + } else if (e.shiftKey && e.which === WYMeditor.KEY.ENTER) { + // Safari 4 and earlier would show a proper linebreak in the editor and + // then strip it upon save with the default action in the case of inserting + // a new line after bold text + wym._exec('InsertLineBreak'); + e.preventDefault(); + } +}; + +// Keyup handler, mainly used for cleanups +WYMeditor.WymClassSafari.prototype.keyup = function (evt) { + //'this' is the doc + var wym = WYMeditor.INSTANCES[this.title], + container, + defaultRootContainer, + notValidRootContainers, + name, + parentName; + + notValidRootContainers = + wym.documentStructureManager.structureRules.notValidRootContainers; + defaultRootContainer = + wym.documentStructureManager.structureRules.defaultRootContainer; + wym._selected_image = null; + + // Fix to allow shift + return to insert a line break in older safari + if (jQuery.browser.version < 534.1) { + // Not needed in AT MAX chrome 6.0. Probably safe earlier + if (evt.which === WYMeditor.KEY.ENTER && evt.shiftKey) { + wym._exec('InsertLineBreak'); + } + } + + // If the inputted key cannont create a block element and is not a command, + // check to make sure the selection is properly wrapped in a container + if (!wym.keyCanCreateBlockElement(evt.which) && + evt.which !== WYMeditor.KEY.CTRL && + evt.which !== WYMeditor.KEY.COMMAND && + !evt.metaKey && + !evt.ctrlKey) { + + container = wym.selected(); + name = container.tagName.toLowerCase(); + if (container.parentNode) { + parentName = container.parentNode.tagName.toLowerCase(); + } + + // Fix forbidden main containers + if (wym.isForbiddenMainContainer(name)) { + name = parentName; + } + + // Replace text nodes with default root tags and make sure the + // container is valid if it is a root container + if (name === WYMeditor.BODY || + (jQuery.inArray(name, notValidRootContainers) > -1 && + parentName === WYMeditor.BODY)) { + + wym._exec(WYMeditor.FORMAT_BLOCK, defaultRootContainer); + wym.fixBodyHtml(); + } + } + + // If we potentially created a new block level element or moved to a new + // one, then we should ensure the container is valid and the formatting is + // proper. + if (wym.keyCanCreateBlockElement(evt.which)) { + // If the selected container is a root container, make sure it is not a + // different possible default root container than the chosen one. + container = wym.selected(); + name = container.tagName.toLowerCase(); + if (container.parentNode) { + parentName = container.parentNode.tagName.toLowerCase(); + } + if (jQuery.inArray(name, notValidRootContainers) > -1 && + parentName === WYMeditor.BODY) { + wym._exec(WYMeditor.FORMAT_BLOCK, defaultRootContainer); + } + + // Fix formatting if necessary + wym.fixBodyHtml(); + } +}; + +/********** XHTML LEXER/PARSER **********/ + +/* +* @name xml +* @description Use these methods to generate XML and XHTML compliant tags and +* escape tag attributes correctly +* @author Bermi Ferrer - http://bermi.org +* @author David Heinemeier Hansson http://loudthinking.com +*/ +WYMeditor.XmlHelper = function() +{ + this._entitiesDiv = document.createElement('div'); + return this; +}; + + +/* +* @name tag +* @description +* Returns an empty HTML tag of type *name* which by default is XHTML +* compliant. Setting *open* to true will create an open tag compatible +* with HTML 4.0 and below. Add HTML attributes by passing an attributes +* array to *options*. For attributes with no value like (disabled and +* readonly), give it a value of true in the *options* array. +* +* Examples: +* +* this.tag('br') +* # =>
            +* this.tag ('br', false, true) +* # =>
            +* this.tag ('input', jQuery({type:'text',disabled:true }) ) +* # => +*/ +WYMeditor.XmlHelper.prototype.tag = function(name, options, open) +{ + options = options || false; + open = open || false; + return '<'+name+(options ? this.tagOptions(options) : '')+(open ? '>' : ' />'); +}; + +/* +* @name contentTag +* @description +* Returns a XML block tag of type *name* surrounding the *content*. Add +* XML attributes by passing an attributes array to *options*. For attributes +* with no value like (disabled and readonly), give it a value of true in +* the *options* array. You can use symbols or strings for the attribute names. +* +* this.contentTag ('p', 'Hello world!' ) +* # =>

            Hello world!

            +* this.contentTag('div', this.contentTag('p', "Hello world!"), jQuery({class : "strong"})) +* # =>

            Hello world!

            +* this.contentTag("select", options, jQuery({multiple : true})) +* # => +*/ +WYMeditor.XmlHelper.prototype.contentTag = function(name, content, options) +{ + options = options || false; + return '<'+name+(options ? this.tagOptions(options) : '')+'>'+content+''; +}; + +/* +* @name cdataSection +* @description +* Returns a CDATA section for the given +content+. CDATA sections +* are used to escape blocks of text containing characters which would +* otherwise be recognized as markup. CDATA sections begin with the string +* <![CDATA[ and } with (and may not contain) the string +* ]]>. +*/ +WYMeditor.XmlHelper.prototype.cdataSection = function(content) +{ + return ''; +}; + + +/* +* @name escapeOnce +* @description +* Returns the escaped +xml+ without affecting existing escaped entities. +* +* this.escapeOnce( "1 > 2 & 3") +* # => "1 > 2 & 3" +*/ +WYMeditor.XmlHelper.prototype.escapeOnce = function(xml) +{ + return this._fixDoubleEscape(this.escapeEntities(xml)); +}; + +/* +* @name _fixDoubleEscape +* @description +* Fix double-escaped entities, such as &amp;, &#123;, etc. +*/ +WYMeditor.XmlHelper.prototype._fixDoubleEscape = function(escaped) +{ + return escaped.replace(/&([a-z]+|(#\d+));/ig, "&$1;"); +}; + +/* +* @name tagOptions +* @description +* Takes an array like the one generated by Tag.parseAttributes +* [["src", "http://www.editam.com/?a=b&c=d&f=g"], ["title", "Editam, CMS"]] +* or an object like {src:"http://www.editam.com/?a=b&c=d&f=g", title:"Editam, CMS"} +* and returns a string properly escaped like +* ' src = "http://www.editam.com/?a=b&c=d&f=g" title = "Editam, <Simplified> CMS"' +* which is valid for strict XHTML +*/ +WYMeditor.XmlHelper.prototype.tagOptions = function(options) +{ + var xml = this; + xml._formated_options = ''; + + for (var key in options) { + var formated_options = ''; + var value = options[key]; + if(typeof value != 'function' && value.length > 0) { + + if(parseInt(key, 10) == key && typeof value == 'object'){ + key = value.shift(); + value = value.pop(); + } + if(key !== '' && value !== ''){ + xml._formated_options += ' '+key+'="'+xml.escapeOnce(value)+'"'; + } +} +} +return xml._formated_options; +}; + +/* +* @name escapeEntities +* @description +* Escapes XML/HTML entities <, >, & and ". If seccond parameter is set to false it +* will not escape ". If set to true it will also escape ' +*/ +WYMeditor.XmlHelper.prototype.escapeEntities = function(string, escape_quotes) +{ + this._entitiesDiv.innerHTML = string; + this._entitiesDiv.textContent = string; + var result = this._entitiesDiv.innerHTML; + if(typeof escape_quotes == 'undefined'){ + if(escape_quotes !== false) result = result.replace('"', '"'); + if(escape_quotes === true) result = result.replace('"', '''); + } + return result; +}; + +/* +* Parses a string conatining tag attributes and values an returns an array formated like +* [["src", "http://www.editam.com"], ["title", "Editam, Simplified CMS"]] +*/ +WYMeditor.XmlHelper.prototype.parseAttributes = function(tag_attributes) +{ + // Use a compounded regex to match single quoted, double quoted and unquoted attribute pairs + var result = []; + var matches = tag_attributes.split(/((=\s*")(")("))|((=\s*\')(\')(\'))|((=\s*[^>\s]*))/g); + if(matches.toString() != tag_attributes){ + for (var k in matches) { + var v = matches[k]; + if(typeof v != 'function' && v.length !== 0){ + var re = new RegExp('(\\w+)\\s*'+v); + var match = tag_attributes.match(re); + if(match) { + var value = v.replace(/^[\s=]+/, ""); + var delimiter = value.charAt(0); + delimiter = delimiter == '"' ? '"' : (delimiter=="'"?"'":''); + if(delimiter !== ''){ + value = delimiter == '"' ? value.replace(/^"|"+$/g, '') : value.replace(/^'|'+$/g, ''); + } + tag_attributes = tag_attributes.replace(match[0],''); + result.push([match[1] , value]); + } + } + } + } + return result; +}; +/** +* XhtmlValidator for validating tag attributes +* +* @author Bermi Ferrer - http://bermi.org +*/ +WYMeditor.XhtmlValidator = { + "_attributes": + { + "core": + { + "except":[ + "base", + "head", + "html", + "meta", + "param", + "script", + "style", + "title" + ], + "attributes":[ + "class", + "id", + "style", + "title", + "accesskey", + "tabindex", + "/^data-.*/" + ] + }, + "language": + { + "except":[ + "base", + "br", + "hr", + "iframe", + "param", + "script" + ], + "attributes": + { + "dir":[ + "ltr", + "rtl" + ], + "0":"lang", + "1":"xml:lang" + } + }, + "keyboard": + { + "attributes": + { + "accesskey":/^(\w){1}$/, + "tabindex":/^(\d)+$/ + } + } + }, + "_events": + { + "window": + { + "only":[ + "body" + ], + "attributes":[ + "onload", + "onunload" + ] + }, + "form": + { + "only":[ + "form", + "input", + "textarea", + "select", + "a", + "label", + "button" + ], + "attributes":[ + "onchange", + "onsubmit", + "onreset", + "onselect", + "onblur", + "onfocus" + ] + }, + "keyboard": + { + "except":[ + "base", + "bdo", + "br", + "frame", + "frameset", + "head", + "html", + "iframe", + "meta", + "param", + "script", + "style", + "title" + ], + "attributes":[ + "onkeydown", + "onkeypress", + "onkeyup" + ] + }, + "mouse": + { + "except":[ + "base", + "bdo", + "br", + "head", + "html", + "meta", + "param", + "script", + "style", + "title" + ], + "attributes":[ + "onclick", + "ondblclick", + "onmousedown", + "onmousemove", + "onmouseover", + "onmouseout", + "onmouseup" + ] + } + }, + "_tags": + { + "a": + { + "attributes": + { + "0":"charset", + "1":"coords", + "2":"href", + "3":"hreflang", + "4":"name", + "5":"rel", + "6":"rev", + "shape":/^(rect|rectangle|circ|circle|poly|polygon)$/, + "7":"type" + } + }, + "0":"abbr", + "1":"acronym", + "2":"address", + "area": + { + "attributes": + { + "0":"alt", + "1":"coords", + "2":"href", + "nohref":/^(true|false)$/, + "shape":/^(rect|rectangle|circ|circle|poly|polygon)$/ + }, + "required":[ + "alt" + ] + }, + "3":"b", + "base": + { + "attributes":[ + "href" + ], + "required":[ + "href" + ] + }, + "bdo": + { + "attributes": + { + "dir":/^(ltr|rtl)$/ + }, + "required":[ + "dir" + ] + }, + "4":"big", + "blockquote": + { + "attributes":[ + "cite" + ] + }, + "5":"body", + "6":"br", + "button": + { + "attributes": + { + "disabled":/^(disabled)$/, + "type":/^(button|reset|submit)$/, + "0":"value" + }, + "inside":"form" + }, + "7":"caption", + "8":"cite", + "9":"code", + "col": + { + "attributes": + { + "align":/^(right|left|center|justify)$/, + "0":"char", + "1":"charoff", + "span":/^(\d)+$/, + "valign":/^(top|middle|bottom|baseline)$/, + "2":"width" + }, + "inside":"colgroup" + }, + "colgroup": + { + "attributes": + { + "align":/^(right|left|center|justify)$/, + "0":"char", + "1":"charoff", + "span":/^(\d)+$/, + "valign":/^(top|middle|bottom|baseline)$/, + "2":"width" + } + }, + "10":"dd", + "del": + { + "attributes": + { + "0":"cite", + "datetime":/^([0-9]){8}/ + } + }, + "11":"div", + "12":"dfn", + "13":"dl", + "14":"dt", + "15":"em", + "fieldset": + { + "inside":"form" + }, + "form": + { + "attributes": + { + "0":"action", + "1":"accept", + "2":"accept-charset", + "3":"enctype", + "method":/^(get|post)$/ + }, + "required":[ + "action" + ] + }, + "head": + { + "attributes":[ + "profile" + ] + }, + "16":"h1", + "17":"h2", + "18":"h3", + "19":"h4", + "20":"h5", + "21":"h6", + "22":"hr", + "html": + { + "attributes":[ + "xmlns" + ] + }, + "23":"i", + "img": + { + "attributes":[ + "alt", + "src", + "height", + "ismap", + "longdesc", + "usemap", + "width" + ], + "required":[ + "alt", + "src" + ] + }, + "input": + { + "attributes": + { + "0":"accept", + "1":"alt", + "checked":/^(checked)$/, + "disabled":/^(disabled)$/, + "maxlength":/^(\d)+$/, + "2":"name", + "readonly":/^(readonly)$/, + "size":/^(\d)+$/, + "3":"src", + "type":/^(button|checkbox|file|hidden|image|password|radio|reset|submit|text)$/, + "4":"value" + }, + "inside":"form" + }, + "ins": + { + "attributes": + { + "0":"cite", + "datetime":/^([0-9]){8}/ + } + }, + "24":"kbd", + "label": + { + "attributes":[ + "for" + ], + "inside":"form" + }, + "25":"legend", + "26":"li", + "link": + { + "attributes": + { + "0":"charset", + "1":"href", + "2":"hreflang", + "media":/^(all|braille|print|projection|screen|speech|,|;| )+$/i, + //next comment line required by Opera! + /*"rel":/^(alternate|appendix|bookmark|chapter|contents|copyright|glossary|help|home|index|next|prev|section|start|stylesheet|subsection| |shortcut|icon)+$/i,*/ + "rel":/^(alternate|appendix|bookmark|chapter|contents|copyright|glossary|help|home|index|next|prev|section|start|stylesheet|subsection| |shortcut|icon)+$/i, + "rev":/^(alternate|appendix|bookmark|chapter|contents|copyright|glossary|help|home|index|next|prev|section|start|stylesheet|subsection| |shortcut|icon)+$/i, + "3":"type" + }, + "inside":"head" + }, + "map": + { + "attributes":[ + "id", + "name" + ], + "required":[ + "id" + ] + }, + "meta": + { + "attributes": + { + "0":"content", + "http-equiv":/^(content\-type|expires|refresh|set\-cookie)$/i, + "1":"name", + "2":"scheme" + }, + "required":[ + "content" + ] + }, + "27":"noscript", + "object": + { + "attributes":[ + "archive", + "classid", + "codebase", + "codetype", + "data", + "declare", + "height", + "name", + "standby", + "type", + "usemap", + "width" + ] + }, + "28":"ol", + "optgroup": + { + "attributes": + { + "0":"label", + "disabled": /^(disabled)$/ + }, + "required":[ + "label" + ] + }, + "option": + { + "attributes": + { + "0":"label", + "disabled":/^(disabled)$/, + "selected":/^(selected)$/, + "1":"value" + }, + "inside":"select" + }, + "29":"p", + "param": + { + "attributes": + { + "0":"type", + "valuetype":/^(data|ref|object)$/, + "1":"valuetype", + "2":"value" + }, + "required":[ + "name" + ] + }, + "30":"pre", + "q": + { + "attributes":[ + "cite" + ] + }, + "31":"samp", + "script": + { + "attributes": + { + "type":/^(text\/ecmascript|text\/javascript|text\/jscript|text\/vbscript|text\/vbs|text\/xml)$/, + "0":"charset", + "defer":/^(defer)$/, + "1":"src" + }, + "required":[ + "type" + ] + }, + "select": + { + "attributes": + { + "disabled":/^(disabled)$/, + "multiple":/^(multiple)$/, + "0":"name", + "1":"size" + }, + "inside":"form" + }, + "32":"small", + "33":"span", + "34":"strong", + "style": + { + "attributes": + { + "0":"type", + "media":/^(screen|tty|tv|projection|handheld|print|braille|aural|all)$/ + }, + "required":[ + "type" + ] + }, + "35":"sub", + "36":"sup", + "table": + { + "attributes": + { + "0":"border", + "1":"cellpadding", + "2":"cellspacing", + "frame":/^(void|above|below|hsides|lhs|rhs|vsides|box|border)$/, + "rules":/^(none|groups|rows|cols|all)$/, + "3":"summary", + "4":"width" + } + }, + "tbody": + { + "attributes": + { + "align":/^(right|left|center|justify)$/, + "0":"char", + "1":"charoff", + "valign":/^(top|middle|bottom|baseline)$/ + } + }, + "td": + { + "attributes": + { + "0":"abbr", + "align":/^(left|right|center|justify|char)$/, + "1":"axis", + "2":"char", + "3":"charoff", + "colspan":/^(\d)+$/, + "4":"headers", + "rowspan":/^(\d)+$/, + "scope":/^(col|colgroup|row|rowgroup)$/, + "valign":/^(top|middle|bottom|baseline)$/ + } + }, + "textarea": + { + "attributes":[ + "cols", + "rows", + "disabled", + "name", + "readonly" + ], + "required":[ + "cols", + "rows" + ], + "inside":"form" + }, + "tfoot": + { + "attributes": + { + "align":/^(right|left|center|justify)$/, + "0":"char", + "1":"charoff", + "valign":/^(top|middle|bottom)$/, + "2":"baseline" + } + }, + "th": + { + "attributes": + { + "0":"abbr", + "align":/^(left|right|center|justify|char)$/, + "1":"axis", + "2":"char", + "3":"charoff", + "colspan":/^(\d)+$/, + "4":"headers", + "rowspan":/^(\d)+$/, + "scope":/^(col|colgroup|row|rowgroup)$/, + "valign":/^(top|middle|bottom|baseline)$/ + } + }, + "thead": + { + "attributes": + { + "align":/^(right|left|center|justify)$/, + "0":"char", + "1":"charoff", + "valign":/^(top|middle|bottom|baseline)$/ + } + }, + "37":"title", + "tr": + { + "attributes": + { + "align":/^(right|left|center|justify|char)$/, + "0":"char", + "1":"charoff", + "valign":/^(top|middle|bottom|baseline)$/ + } + }, + "38":"tt", + "39":"ul", + "40":"var" + }, + + // Temporary skiped attributes + skiped_attributes : [], + skiped_attribute_values : [], + + getValidTagAttributes: function(tag, attributes) + { + var valid_attributes = {}; + var possible_attributes = this.getPossibleTagAttributes(tag); + for(var attribute in attributes) { + var value = attributes[attribute]; + attribute = attribute.toLowerCase(); // ie8 uses colSpan + var h = WYMeditor.Helper; + if(!h.contains(this.skiped_attributes, attribute) && !h.contains(this.skiped_attribute_values, value)){ + if (typeof value != 'function' && h.contains(possible_attributes, attribute)) { + if (this.doesAttributeNeedsValidation(tag, attribute)) { + if(this.validateAttribute(tag, attribute, value)){ + valid_attributes[attribute] = value; + } + }else{ + valid_attributes[attribute] = value; + } + } else { + jQuery.each(possible_attributes, function() { + if(this.match(/\/(.*)\//)) { + regex = new RegExp(this.match(/\/(.*)\//)[1]); + if(regex.test(attribute)) { + valid_attributes[attribute] = value; + } + } + }); + } + } + } + return valid_attributes; + }, + getUniqueAttributesAndEventsForTag : function(tag) + { + var result = []; + + if (this._tags[tag] && this._tags[tag].attributes) { + for (var k in this._tags[tag].attributes) { + result.push(parseInt(k, 10) == k ? this._tags[tag].attributes[k] : k); + } + } + return result; + }, +getDefaultAttributesAndEventsForTags : function() +{ + var result = []; + for (var key in this._events){ + result.push(this._events[key]); + } + for (key in this._attributes){ + result.push(this._attributes[key]); + } + return result; +}, +isValidTag : function(tag) +{ + if(this._tags[tag]){ + return true; + } + for(var key in this._tags){ + if(this._tags[key] == tag){ + return true; + } + } + return false; +}, +getDefaultAttributesAndEventsForTag : function(tag) +{ + var default_attributes = []; + if (this.isValidTag(tag)) { + var default_attributes_and_events = this.getDefaultAttributesAndEventsForTags(); + + for(var key in default_attributes_and_events) { + var defaults = default_attributes_and_events[key]; + if(typeof defaults == 'object'){ + var h = WYMeditor.Helper; + if ((defaults['except'] && h.contains(defaults['except'], tag)) || (defaults['only'] && !h.contains(defaults['only'], tag))) { + continue; + } + + var tag_defaults = defaults['attributes'] ? defaults['attributes'] : defaults['events']; + for(var k in tag_defaults) { + default_attributes.push(typeof tag_defaults[k] != 'string' ? k : tag_defaults[k]); + } +} +} +} +return default_attributes; +}, +doesAttributeNeedsValidation: function(tag, attribute) +{ + return this._tags[tag] && ((this._tags[tag]['attributes'] && this._tags[tag]['attributes'][attribute]) || (this._tags[tag]['required'] && + WYMeditor.Helper.contains(this._tags[tag]['required'], attribute))); +}, +validateAttribute : function(tag, attribute, value) +{ + if ( this._tags[tag] && + (this._tags[tag]['attributes'] && this._tags[tag]['attributes'][attribute] && value.length > 0 && !value.match(this._tags[tag]['attributes'][attribute])) || // invalid format + (this._tags[tag] && this._tags[tag]['required'] && WYMeditor.Helper.contains(this._tags[tag]['required'], attribute) && value.length === 0)) // required attribute + { + return false; + } + return typeof this._tags[tag] != 'undefined'; +}, +getPossibleTagAttributes : function(tag) +{ + if (!this._possible_tag_attributes) { + this._possible_tag_attributes = {}; + } + if (!this._possible_tag_attributes[tag]) { + this._possible_tag_attributes[tag] = this.getUniqueAttributesAndEventsForTag(tag).concat(this.getDefaultAttributesAndEventsForTag(tag)); + } + return this._possible_tag_attributes[tag]; +} +}; + +/** +* Compounded regular expression. Any of +* the contained patterns could match and +* when one does, it's label is returned. +* +* Constructor. Starts with no patterns. +* @param boolean case True for case sensitive, false +* for insensitive. +* @access public +* @author Marcus Baker (http://lastcraft.com) +* @author Bermi Ferrer (http://bermi.org) +*/ +WYMeditor.ParallelRegex = function(case_sensitive) +{ + this._case = case_sensitive; + this._patterns = []; + this._labels = []; + this._regex = null; + return this; +}; + + +/** +* Adds a pattern with an optional label. +* @param string pattern Perl style regex, but ( and ) +* lose the usual meaning. +* @param string label Label of regex to be returned +* on a match. +* @access public +*/ +WYMeditor.ParallelRegex.prototype.addPattern = function(pattern, label) +{ + label = label || true; + var count = this._patterns.length; + this._patterns[count] = pattern; + this._labels[count] = label; + this._regex = null; +}; + +/** +* Attempts to match all patterns at once against +* a string. +* @param string subject String to match against. +* +* @return boolean True on success. +* @return string match First matched portion of +* subject. +* @access public +*/ +WYMeditor.ParallelRegex.prototype.match = function(subject) +{ + if (this._patterns.length === 0) { + return [false, '']; + } + var matches = subject.match(this._getCompoundedRegex()); + + if(!matches){ + return [false, '']; + } + var match = matches[0]; + for (var i = 1; i < matches.length; i++) { + if (matches[i]) { + return [this._labels[i-1], match]; + } + } + return [true, matches[0]]; +}; + +/** +* Compounds the patterns into a single +* regular expression separated with the +* "or" operator. Caches the regex. +* Will automatically escape (, ) and / tokens. +* @param array patterns List of patterns in order. +* @access private +*/ +WYMeditor.ParallelRegex.prototype._getCompoundedRegex = function() +{ + if (this._regex === null) { + for (var i = 0, count = this._patterns.length; i < count; i++) { + this._patterns[i] = '(' + this._untokenizeRegex(this._tokenizeRegex(this._patterns[i]).replace(/([\/\(\)])/g,'\\$1')) + ')'; + } + this._regex = new RegExp(this._patterns.join("|") ,this._getPerlMatchingFlags()); + } + return this._regex; +}; + +/** +* Escape lookahead/lookbehind blocks +*/ +WYMeditor.ParallelRegex.prototype._tokenizeRegex = function(regex) +{ + return regex. + replace(/\(\?(i|m|s|x|U)\)/, '~~~~~~Tk1\$1~~~~~~'). + replace(/\(\?(\-[i|m|s|x|U])\)/, '~~~~~~Tk2\$1~~~~~~'). + replace(/\(\?\=(.*)\)/, '~~~~~~Tk3\$1~~~~~~'). + replace(/\(\?\!(.*)\)/, '~~~~~~Tk4\$1~~~~~~'). + replace(/\(\?\<\=(.*)\)/, '~~~~~~Tk5\$1~~~~~~'). + replace(/\(\?\<\!(.*)\)/, '~~~~~~Tk6\$1~~~~~~'). + replace(/\(\?\:(.*)\)/, '~~~~~~Tk7\$1~~~~~~'); +}; + +/** +* Unscape lookahead/lookbehind blocks +*/ +WYMeditor.ParallelRegex.prototype._untokenizeRegex = function(regex) +{ + return regex. + replace(/~~~~~~Tk1(.{1})~~~~~~/, "(?\$1)"). + replace(/~~~~~~Tk2(.{2})~~~~~~/, "(?\$1)"). + replace(/~~~~~~Tk3(.*)~~~~~~/, "(?=\$1)"). + replace(/~~~~~~Tk4(.*)~~~~~~/, "(?!\$1)"). + replace(/~~~~~~Tk5(.*)~~~~~~/, "(?<=\$1)"). + replace(/~~~~~~Tk6(.*)~~~~~~/, "(?", 'Comment'); +}; + +WYMeditor.XhtmlLexer.prototype.addScriptTokens = function(scope) { + this.addEntryPattern("", 'Script'); +}; + +WYMeditor.XhtmlLexer.prototype.addCssTokens = function(scope) { + this.addEntryPattern("", 'Css'); +}; + +WYMeditor.XhtmlLexer.prototype.addTagTokens = function(scope) { + this.addSpecialPattern("<\\s*[a-z0-9:\-]+\\s*/>", scope, 'SelfClosingTag'); + this.addSpecialPattern("<\\s*[a-z0-9:\-]+\\s*>", scope, 'OpeningTag'); + this.addEntryPattern("<[a-z0-9:\-]+"+'[\\\/ \\\>]+', scope, 'OpeningTag'); + this.addInTagDeclarationTokens('OpeningTag'); + + this.addSpecialPattern("", scope, 'ClosingTag'); + +}; + +WYMeditor.XhtmlLexer.prototype.addInTagDeclarationTokens = function(scope) { + this.addSpecialPattern('\\s+', scope, 'Ignore'); + + this.addAttributeTokens(scope); + + this.addExitPattern('/>', scope); + this.addExitPattern('>', scope); + +}; + +WYMeditor.XhtmlLexer.prototype.addAttributeTokens = function(scope) { + this.addSpecialPattern("\\s*[a-z-_0-9]*:?[a-z-_0-9]+\\s*(?=\=)\\s*", scope, 'TagAttributes'); + + this.addEntryPattern('=\\s*"', scope, 'DoubleQuotedAttribute'); + this.addPattern("\\\\\"", 'DoubleQuotedAttribute'); + this.addExitPattern('"', 'DoubleQuotedAttribute'); + + this.addEntryPattern("=\\s*'", scope, 'SingleQuotedAttribute'); + this.addPattern("\\\\'", 'SingleQuotedAttribute'); + this.addExitPattern("'", 'SingleQuotedAttribute'); + + this.addSpecialPattern('=\\s*[^>\\s]*', scope, 'UnquotedAttribute'); +}; + +/** +* XHTML Parser. +* +* This XHTML parser will trigger the events available on on +* current SaxListener +* +* @author Bermi Ferrer (http://bermi.org) +*/ +WYMeditor.XhtmlParser = function(Listener, mode) { + mode = mode || 'Text'; + this._Lexer = new WYMeditor.XhtmlLexer(this); + this._Listener = Listener; + this._mode = mode; + this._matches = []; + this._last_match = ''; + this._current_match = ''; + + return this; +}; + +WYMeditor.XhtmlParser.prototype.parse = function(raw) { + this._Lexer.parse(this.beforeParsing(raw)); + return this.afterParsing(this._Listener.getResult()); +}; + +WYMeditor.XhtmlParser.prototype.beforeParsing = function(raw) { + if (raw.match(/class="MsoNormal"/) || raw.match(/ns = "urn:schemas-microsoft-com/)) { + // Usefull for cleaning up content pasted from other sources (MSWord) + this._Listener.avoidStylingTagsAndAttributes(); + } + + return this._Listener.beforeParsing(raw); +}; + +WYMeditor.XhtmlParser.prototype.afterParsing = function(parsed) { + if (this._Listener._avoiding_tags_implicitly) { + this._Listener.allowStylingTagsAndAttributes(); + } + return this._Listener.afterParsing(parsed); +}; + + +WYMeditor.XhtmlParser.prototype.Ignore = function(match, state) { + return true; +}; + +WYMeditor.XhtmlParser.prototype.Text = function(text) { + this._Listener.addContent(text); + return true; +}; + +WYMeditor.XhtmlParser.prototype.Comment = function(match, status) { + return this._addNonTagBlock(match, status, 'addComment'); +}; + +WYMeditor.XhtmlParser.prototype.Script = function(match, status) { + return this._addNonTagBlock(match, status, 'addScript'); +}; + +WYMeditor.XhtmlParser.prototype.Css = function(match, status) { + return this._addNonTagBlock(match, status, 'addCss'); +}; + +WYMeditor.XhtmlParser.prototype._addNonTagBlock = function(match, state, type) { + switch (state) { + case WYMeditor.LEXER_ENTER: + this._non_tag = match; + break; + case WYMeditor.LEXER_UNMATCHED: + this._non_tag += match; + break; + case WYMeditor.LEXER_EXIT: + switch(type) { + case 'addComment': + this._Listener.addComment(this._non_tag+match); + break; + case 'addScript': + this._Listener.addScript(this._non_tag+match); + break; + case 'addCss': + this._Listener.addCss(this._non_tag+match); + break; + default: + break; + } + break; + default: + break; + } + return true; +}; + +WYMeditor.XhtmlParser.prototype.SelfClosingTag = function(match, state) { + var result = this.OpeningTag(match, state); + var tag = this.normalizeTag(match); + return this.ClosingTag(match, state); +}; + +WYMeditor.XhtmlParser.prototype.OpeningTag = function(match, state) { + switch (state){ + case WYMeditor.LEXER_ENTER: + this._tag = this.normalizeTag(match); + this._tag_attributes = {}; + break; + case WYMeditor.LEXER_SPECIAL: + this._callOpenTagListener(this.normalizeTag(match)); + break; + case WYMeditor.LEXER_EXIT: + this._callOpenTagListener(this._tag, this._tag_attributes); + break; + default: + break; + } + return true; +}; + +WYMeditor.XhtmlParser.prototype.ClosingTag = function(match, state) { + this._callCloseTagListener(this.normalizeTag(match)); + return true; +}; + +WYMeditor.XhtmlParser.prototype._callOpenTagListener = function(tag, attributes) { + attributes = attributes || {}; + this.autoCloseUnclosedBeforeNewOpening(tag); + + if (this._Listener.isBlockTag(tag)) { + this._Listener._tag_stack.push(tag); + this._Listener.fixNestingBeforeOpeningBlockTag(tag, attributes); + this._Listener.openBlockTag(tag, attributes); + this._increaseOpenTagCounter(tag); + } else if (this._Listener.isInlineTag(tag)) { + this._Listener.inlineTag(tag, attributes); + } else { + this._Listener.openUnknownTag(tag, attributes); + this._increaseOpenTagCounter(tag); + } + this._Listener.last_tag = tag; + this._Listener.last_tag_opened = true; + this._Listener.last_tag_attributes = attributes; +}; + +WYMeditor.XhtmlParser.prototype._callCloseTagListener = function(tag) { + if (this._decreaseOpenTagCounter(tag)) { + this.autoCloseUnclosedBeforeTagClosing(tag); + + if (this._Listener.isBlockTag(tag)) { + var expected_tag = this._Listener._tag_stack.pop(); + if (expected_tag === false) { + return; + } else if (expected_tag != tag) { + tag = expected_tag; + } + this._Listener.closeBlockTag(tag); + } + } else { + if(!this._Listener.isInlineTag(tag)) { + this._Listener.closeUnopenedTag(tag); + } + } + + this._Listener.last_tag = tag; + this._Listener.last_tag_opened = false; +}; + +WYMeditor.XhtmlParser.prototype._increaseOpenTagCounter = function(tag) { + this._Listener._open_tags[tag] = this._Listener._open_tags[tag] || 0; + this._Listener._open_tags[tag]++; +}; + +WYMeditor.XhtmlParser.prototype._decreaseOpenTagCounter = function(tag) { + if (this._Listener._open_tags[tag]) { + this._Listener._open_tags[tag]--; + if (this._Listener._open_tags[tag] === 0) { + this._Listener._open_tags[tag] = undefined; + } + return true; + } + return false; +}; + +WYMeditor.XhtmlParser.prototype.autoCloseUnclosedBeforeNewOpening = function(new_tag) { + this._autoCloseUnclosed(new_tag, false); +}; + +WYMeditor.XhtmlParser.prototype.autoCloseUnclosedBeforeTagClosing = function(tag) { + this._autoCloseUnclosed(tag, true); +}; + +WYMeditor.XhtmlParser.prototype._autoCloseUnclosed = function(new_tag, closing) { + closing = closing || false; + if (this._Listener._open_tags) { + for (var tag in this._Listener._open_tags) { + var counter = this._Listener._open_tags[tag]; + if (counter > 0 && this._Listener.shouldCloseTagAutomatically(tag, new_tag, closing)) { + this._callCloseTagListener(tag, true); + } + } + } +}; + +WYMeditor.XhtmlParser.prototype.getTagReplacements = function() { + return this._Listener.getTagReplacements(); +}; + +WYMeditor.XhtmlParser.prototype.normalizeTag = function(tag) { + tag = tag.replace(/^([\s<\/>]*)|([\s<\/>]*)$/gm,'').toLowerCase(); + var tags = this._Listener.getTagReplacements(); + if (tags[tag]) { + return tags[tag]; + } + return tag; +}; + +WYMeditor.XhtmlParser.prototype.TagAttributes = function(match, state) { + if (WYMeditor.LEXER_SPECIAL == state) { + this._current_attribute = match; + } + return true; +}; + +WYMeditor.XhtmlParser.prototype.DoubleQuotedAttribute = function(match, state) { + if (WYMeditor.LEXER_UNMATCHED == state) { + this._tag_attributes[this._current_attribute] = match; + } + return true; +}; + +WYMeditor.XhtmlParser.prototype.SingleQuotedAttribute = function(match, state) { + if (WYMeditor.LEXER_UNMATCHED == state) { + this._tag_attributes[this._current_attribute] = match; + } + return true; +}; + +WYMeditor.XhtmlParser.prototype.UnquotedAttribute = function(match, state) { + this._tag_attributes[this._current_attribute] = match.replace(/^=/,''); + return true; +}; + +/** +* XHTML Sax parser. +* +* @author Bermi Ferrer (http://bermi.org) +*/ +WYMeditor.XhtmlSaxListener = function() { + this.output = ''; + this.helper = new WYMeditor.XmlHelper(); + this._open_tags = {}; + this.validator = WYMeditor.XhtmlValidator; + this._tag_stack = []; + this.avoided_tags = []; + this._insert_before_closing = []; + this._insert_after_closing = []; + this._last_node_was_text = false; + + // This flag is set to true if the parser is currently inside a tag flagged + // for removal. Nothing will be added to the output while this flag is set + // to true. + this._insideTagToRemove = false; + + // If the last tag was not added to the output, this flag is set to true. + // This is needed because if we are trying to fix an invalid tag by nesting + // it in the last outputted tag as in the case of some invalid lists, if + // the last tag was removed, the invalid tag should just be removed as well + // instead of trying to fix it by nesting it in a tag that was already + // removed from the output. + this._lastTagRemoved = false; + + // When correcting invalid list nesting, situations can occur that will + // result in an extra closing LI tags coming up later in the parser. When + // one of these situations occurs, this counter is incremented so that it + // can be referenced to find how many extra LI closing tags to expect. This + // counter should be decremented everytime one of these extra LI closing + // tags is removed. + this._extraLIClosingTags = 0; + + // This is for storage of a tag's index in the tag stack so that the + // Listener can use it to check for when the tag has been closed (i.e. when + // the top of the tag stack is at the stored index again). + this._removedTagStackIndex = 0; + + this.entities = { + ' ':' ','¡':'¡','¢':'¢', + '£':'£','¤':'¤','¥':'¥', + '¦':'¦','§':'§','¨':'¨', + '©':'©','ª':'ª','«':'«', + '¬':'¬','­':'­','®':'®', + '¯':'¯','°':'°','±':'±', + '²':'²','³':'³','´':'´', + 'µ':'µ','¶':'¶','·':'·', + '¸':'¸','¹':'¹','º':'º', + '»':'»','¼':'¼','½':'½', + '¾':'¾','¿':'¿','À':'À', + 'Á':'Á','Â':'Â','Ã':'Ã', + 'Ä':'Ä','Å':'Å','Æ':'Æ', + 'Ç':'Ç','È':'È','É':'É', + 'Ê':'Ê','Ë':'Ë','Ì':'Ì', + 'Í':'Í','Î':'Î','Ï':'Ï', + 'Ð':'Ð','Ñ':'Ñ','Ò':'Ò', + 'Ó':'Ó','Ô':'Ô','Õ':'Õ', + 'Ö':'Ö','×':'×','Ø':'Ø', + 'Ù':'Ù','Ú':'Ú','Û':'Û', + 'Ü':'Ü','Ý':'Ý','Þ':'Þ', + 'ß':'ß','à':'à','á':'á', + 'â':'â','ã':'ã','ä':'ä', + 'å':'å','æ':'æ','ç':'ç', + 'è':'è','é':'é','ê':'ê', + 'ë':'ë','ì':'ì','í':'í', + 'î':'î','ï':'ï','ð':'ð', + 'ñ':'ñ','ò':'ò','ó':'ó', + 'ô':'ô','õ':'õ','ö':'ö', + '÷':'÷','ø':'ø','ù':'ù', + 'ú':'ú','û':'û','ü':'ü', + 'ý':'ý','þ':'þ','ÿ':'ÿ', + 'Œ':'Œ','œ':'œ','Š':'Š', + 'š':'š','Ÿ':'Ÿ','ƒ':'ƒ', + 'ˆ':'ˆ','˜':'˜','Α':'Α', + 'Β':'Β','Γ':'Γ','Δ':'Δ', + 'Ε':'Ε','Ζ':'Ζ','Η':'Η', + 'Θ':'Θ','Ι':'Ι','Κ':'Κ', + 'Λ':'Λ','Μ':'Μ','Ν':'Ν', + 'Ξ':'Ξ','Ο':'Ο','Π':'Π', + 'Ρ':'Ρ','Σ':'Σ','Τ':'Τ', + 'Υ':'Υ','Φ':'Φ','Χ':'Χ', + 'Ψ':'Ψ','Ω':'Ω','α':'α', + 'β':'β','γ':'γ','δ':'δ', + 'ε':'ε','ζ':'ζ','η':'η', + 'θ':'θ','ι':'ι','κ':'κ', + 'λ':'λ','μ':'μ','ν':'ν', + 'ξ':'ξ','ο':'ο','π':'π', + 'ρ':'ρ','ς':'ς','σ':'σ', + 'τ':'τ','υ':'υ','φ':'φ', + 'χ':'χ','ψ':'ψ','ω':'ω', + 'ϑ':'ϑ','ϒ':'ϒ','ϖ':'ϖ', + ' ':' ',' ':' ',' ':' ', + '‌':'‌','‍':'‍','‎':'‎', + '‏':'‏','–':'–','—':'—', + '‘':'‘','’':'’','‚':'‚', + '“':'“','”':'”','„':'„', + '†':'†','‡':'‡','•':'•', + '…':'…','‰':'‰','′':'′', + '″':'″','‹':'‹','›':'›', + '‾':'‾','⁄':'⁄','€':'€', + 'ℑ':'ℑ','℘':'℘','ℜ':'ℜ', + '™':'™','ℵ':'ℵ','←':'←', + '↑':'↑','→':'→','↓':'↓', + '↔':'↔','↵':'↵','⇐':'⇐', + '⇑':'⇑','⇒':'⇒','⇓':'⇓', + '⇔':'⇔','∀':'∀','∂':'∂', + '∃':'∃','∅':'∅','∇':'∇', + '∈':'∈','∉':'∉','∋':'∋', + '∏':'∏','∑':'∑','−':'−', + '∗':'∗','√':'√','∝':'∝', + '∞':'∞','∠':'∠','∧':'∧', + '∨':'∨','∩':'∩','∪':'∪', + '∫':'∫','∴':'∴','∼':'∼', + '≅':'≅','≈':'≈','≠':'≠', + '≡':'≡','≤':'≤','≥':'≥', + '⊂':'⊂','⊃':'⊃','⊄':'⊄', + '⊆':'⊆','⊇':'⊇','⊕':'⊕', + '⊗':'⊗','⊥':'⊥','⋅':'⋅', + '⌈':'⌈','⌉':'⌉','⌊':'⌊', + '⌋':'⌋','⟨':'〈','⟩':'〉', + '◊':'◊','♠':'♠','♣':'♣', + '♥':'♥','♦':'♦'}; + + this.block_tags = [ + "a", "abbr", "acronym", "address", "area", "b", + "base", "bdo", "big", "blockquote", "body", "button", + "caption", "cite", "code", "colgroup", "dd", "del", "div", + "dfn", "dl", "dt", "em", "fieldset", "form", "head", "h1", "h2", + "h3", "h4", "h5", "h6", "html", "i", "iframe", "ins", + "kbd", "label", "legend", "li", "map", "noscript", + "object", "ol", "optgroup", "option", "p", "param", "pre", "q", + "samp", "script", "select", "small", "span", "strong", "style", + "sub", "sup", "table", "tbody", "td", "textarea", "tfoot", "th", + "thead", "title", "tr", "tt", "ul", "var", "extends"]; + + + this.inline_tags = ["br", "col", "hr", "img", "input"]; + + return this; +}; + +WYMeditor.XhtmlSaxListener.prototype.shouldCloseTagAutomatically = function(tag, now_on_tag, closing) { + closing = closing || false; + if (tag == 'td') { + if ((closing && now_on_tag == 'tr') || (!closing && now_on_tag == 'td')) { + return true; + } + } else if (tag == 'option') { + if ((closing && now_on_tag == 'select') || (!closing && now_on_tag == 'option')) { + return true; + } + } + return false; +}; + +WYMeditor.XhtmlSaxListener.prototype.beforeParsing = function(raw) { + this.output = ''; + + // Reset attributes that might bleed over between parsing + this._insert_before_closing = []; + this._insert_after_closing = []; + this._open_tags = {}; + this._tag_stack = []; + this._last_node_was_text = false; + this._lastTagRemoved = false; + this.last_tag = null; + + return raw; +}; + +WYMeditor.XhtmlSaxListener.prototype.afterParsing = function(xhtml) { + xhtml = this.replaceNamedEntities(xhtml); + xhtml = this.joinRepeatedEntities(xhtml); + xhtml = this.removeEmptyTags(xhtml); + xhtml = this.removeBrInPre(xhtml); + + return xhtml; +}; + +WYMeditor.XhtmlSaxListener.prototype.replaceNamedEntities = function(xhtml) { + for (var entity in this.entities) { + xhtml = xhtml.replace(new RegExp(entity, 'g'), this.entities[entity]); + } + return xhtml; +}; + +WYMeditor.XhtmlSaxListener.prototype.joinRepeatedEntities = function(xhtml) { + var tags = 'em|strong|sub|sup|acronym|pre|del|address'; + return xhtml.replace(new RegExp('<\/('+tags+')><\\1>' ,''), ''). + replace( + new RegExp('(\s*<('+tags+')>\s*){2}(.*)(\s*<\/\\2>\s*){2}' ,''), + '<\$2>\$3<\$2>'); +}; + +WYMeditor.XhtmlSaxListener.prototype.removeEmptyTags = function(xhtml) { + return xhtml.replace( + new RegExp( + '<('+this.block_tags.join("|"). + replace(/\|td/,''). + replace(/\|th/, '') + + ')>(
            | | |\\s)*<\/\\1>' ,'g'), + ''); +}; + +WYMeditor.XhtmlSaxListener.prototype.removeBrInPre = function(xhtml) { + var matches = xhtml.match(new RegExp(']*>(.*?)<\/pre>','gmi')); + if (matches) { + for (var i=0; i', 'g'), String.fromCharCode(13,10))); + } + } + return xhtml; +}; + +WYMeditor.XhtmlSaxListener.prototype.getResult = function() { + return this.output; +}; + +WYMeditor.XhtmlSaxListener.prototype.getTagReplacements = function() { + return {'b':'strong', 'i':'em'}; +}; + +WYMeditor.XhtmlSaxListener.prototype.getTagForStyle = function (style) { + if (/sub/.test(style)) { + return 'sub'; + } else if (/super/.test(style)) { + return 'sup'; + } else if (/bold/.test(style)) { + return 'strong'; + } else if (/italic/.test(style)) { + return 'em'; + } + + return false; +}; + +WYMeditor.XhtmlSaxListener.prototype.addContent = function(text) { + if (this.last_tag && this.last_tag == 'li') { + // We should strip trailing newlines from text inside li tags because + // IE adds random significant newlines inside nested lists + text = text.replace(/(\r|\n|\r\n)+$/g, ''); + + // Let's also normalize multiple newlines down to a single space + text = text.replace(/(\r|\n|\r\n)+/g, ' '); + } + if (text.replace(/^\s+|\s+$/g, '').length > 0) { + // Don't count it as text if it's empty + this._last_node_was_text = true; + } + if (!this._insideTagToRemove) { + this.output += text; + } +}; + +WYMeditor.XhtmlSaxListener.prototype.addComment = function(text) { + if (this.remove_comments || this._insideTagToRemove) { + return; + } + this.output += text; +}; + +WYMeditor.XhtmlSaxListener.prototype.addScript = function(text) { + if (this.remove_scripts || this._insideTagToRemove) { + return; + } + this.output += text; +}; + +WYMeditor.XhtmlSaxListener.prototype.addCss = function(text) { + if (this.remove_embeded_styles || this._insideTagToRemove) { + return; + } + this.output += text; +}; + +WYMeditor.XhtmlSaxListener.prototype.openBlockTag = function(tag, attributes) { + this._last_node_was_text = false; + if (this._insideTagToRemove) { + // If we're currently in a block marked for removal, don't add it to + // the output. + return; + } + if (this._shouldRemoveTag(tag, attributes)) { + // If this tag is marked for removal, set a flag signifying that + // we're in a tag to remove and mark the position in the tag stack + // of this tag so that we know when we've reached the end of it. + this._insideTagToRemove = true; + this._removedTagStackIndex = this._tag_stack.length - 1; + return; + } + + attributes = this.validator.getValidTagAttributes(tag, attributes); + attributes = this.removeUnwantedClasses(attributes); + + // Handle Mozilla and Safari styled spans + if (tag === 'span' && attributes.style) { + var new_tag = this.getTagForStyle(attributes.style); + if (new_tag) { + tag = new_tag; + this._tag_stack.pop(); + this._tag_stack.push(tag); + attributes.style = ''; + } + } + + this.output += this.helper.tag(tag, attributes, true); + this._lastTagRemoved = false; +}; + +WYMeditor.XhtmlSaxListener.prototype.inlineTag = function(tag, attributes) { + this._last_node_was_text = false; + if (this._insideTagToRemove || this._shouldRemoveTag(tag, attributes)) { + // If we're currently in a block marked for removal or if this tag is + // marked for removal, don't add it to the output. + return; + } + + attributes = this.validator.getValidTagAttributes(tag, attributes); + attributes = this.removeUnwantedClasses(attributes); + this.output += this.helper.tag(tag, attributes); + this._lastTagRemoved = false; +}; + +WYMeditor.XhtmlSaxListener.prototype.openUnknownTag = function(tag, attributes) { + //this.output += this.helper.tag(tag, attributes, true); +}; + +WYMeditor.XhtmlSaxListener.prototype.closeBlockTag = function(tag) { + this._last_node_was_text = false; + if (this._insideTagToRemove) { + if (this._tag_stack.length === this._removedTagStackIndex) { + // If we've reached the index in the tag stack were the tag to be + // removed started, we're no longer inside that tag and can turn + // the insideTagToRemove flag off. + this._insideTagToRemove = false; + } + this._lastTagRemoved = true; + return; + } + + this.output = this.output.replace(/
            $/, '') + + this._getClosingTagContent('before', tag) + + "" + + this._getClosingTagContent('after', tag); +}; + +WYMeditor.XhtmlSaxListener.prototype.closeUnknownTag = function(tag) { + //this.output += ""; +}; + +WYMeditor.XhtmlSaxListener.prototype.closeUnopenedTag = function(tag) { + this._last_node_was_text = false; + if (this._insideTagToRemove) { + return; + } + + if (tag === 'li' && this._extraLIClosingTags) { + this._extraLIClosingTags--; + } else { + this.output += ""; + } +}; + +WYMeditor.XhtmlSaxListener.prototype.avoidStylingTagsAndAttributes = function() { + this.avoided_tags = ['div','span']; + this.validator.skiped_attributes = ['style']; + this.validator.skiped_attribute_values = ['MsoNormal','main1']; // MS Word attributes for class + this._avoiding_tags_implicitly = true; +}; + +WYMeditor.XhtmlSaxListener.prototype.allowStylingTagsAndAttributes = function() { + this.avoided_tags = []; + this.validator.skiped_attributes = []; + this.validator.skiped_attribute_values = []; + this._avoiding_tags_implicitly = false; +}; + +WYMeditor.XhtmlSaxListener.prototype.isBlockTag = function(tag) { + return !WYMeditor.Helper.contains(this.avoided_tags, tag) && + WYMeditor.Helper.contains(this.block_tags, tag); +}; + +WYMeditor.XhtmlSaxListener.prototype.isInlineTag = function(tag) { + return !WYMeditor.Helper.contains(this.avoided_tags, tag) && + WYMeditor.Helper.contains(this.inline_tags, tag); +}; + +WYMeditor.XhtmlSaxListener.prototype.insertContentAfterClosingTag = function(tag, content) { + this._insertContentWhenClosingTag('after', tag, content); +}; + +WYMeditor.XhtmlSaxListener.prototype.insertContentBeforeClosingTag = function(tag, content) { + this._insertContentWhenClosingTag('before', tag, content); +}; + +/* + removeUnwantedClasses + ===================== + + Removes the unwanted classes specified in the + WYMeditor.CLASSES_REMOVED_BY_PARSER constant from the passed attributes + object and returns the attributes object after the removals. The passed + attributes object should be in a format with attribute names as properties + and those attributes' values as those properties' values. The class + matching for removal is case insensitive. +*/ +WYMeditor.XhtmlSaxListener.prototype.removeUnwantedClasses = function(attributes) { + var pattern, + i; + + if (!attributes["class"]) { + return attributes; + } + + for (i = 0; i < WYMeditor.CLASSES_REMOVED_BY_PARSER.length; ++i) { + pattern = new RegExp('(^|\\s)' + WYMeditor.CLASSES_REMOVED_BY_PARSER[i] + + '($|\\s)', 'gi'); + attributes["class"] = attributes["class"].replace(pattern, '$1'); + } + + // Remove possible trailing space that could have been left over if the + // last class was removed + attributes["class"] = attributes["class"].replace(/\s$/, ''); + return attributes; +}; + +WYMeditor.XhtmlSaxListener.prototype.fixNestingBeforeOpeningBlockTag = function(tag, attributes) { + if (!this._last_node_was_text && (tag == 'ul' || tag == 'ol') && this.last_tag && + !this.last_tag_opened && this.last_tag == 'li') { + // We have a
            1. ... situation. The new list should be a + // child of the li tag. Not a sibling. + + if (this._lastTagRemoved) { + // If the previous li tag was removed, the new list should be + // removed with it. + this._insideTagToRemove = true; + this._removedTagStackIndex = this._tag_stack.length - 1; + } else if (!this._shouldRemoveTag(tag, attributes)){ + // If this tag is not going to be removed, remove the last closing + // li tag + this.output = this.output.replace(/<\/li>\s*$/, ''); + this.insertContentAfterClosingTag(tag, ''); + } + } else if ((tag == 'ul' || tag == 'ol') && this.last_tag && + this.last_tag_opened && (this.last_tag == 'ul' || this.last_tag == 'ol')) { + // We have a ... situation. The new list should be have + // a li tag parent and shouldn't be directly nested. + + // If this tag is not going to be removed, add an opening li tag before + // and after this tag + if (!this._shouldRemoveTag(tag, attributes)) { + this.output += this.helper.tag('li', {}, true); + this.insertContentAfterClosingTag(tag, ''); + } + this._last_node_was_text = false; + } else if (tag == 'li') { + // Closest open tag that's not this tag + if (this._tag_stack.length >= 2) { + var closestOpenTag = this._tag_stack[this._tag_stack.length - 2]; + if (closestOpenTag == 'li' && !this._shouldRemoveTag(tag, attributes)){ + // Pop the tag off of the stack to indicate we closed it + this._open_tags.li -= 1; + if (this._open_tags.li === 0) { + this._open_tags.li = undefined; + } + this._tag_stack.splice(this._tag_stack.length - 2, 1); + this._last_node_was_text = false; + + if (!this._insideTagToRemove) { + // If not inside a tag to remove, close the outer LI now + // before adding the LI that was nested within it to the + // output. + this.output += ''; + } else if (this._tag_stack.length - 1 === + this._removedTagStackIndex) { + // If the outer LI was the start of a block to be removed, + // reset the flag for removing a tag. + this._insideTagToRemove = false; + this._lastTagRemoved = true; + this._extraLIClosingTags++; + } + } + } + // Opening a new li tag while another li tag is still open. + // LI tags aren't allowed to be nested within eachother + // It probably means we forgot to close the last LI tag + //return true; + } +}; + +WYMeditor.XhtmlSaxListener.prototype._insertContentWhenClosingTag = function(position, tag, content) { + if (!this['_insert_'+position+'_closing']) { + this['_insert_'+position+'_closing'] = []; + } + if (!this['_insert_'+position+'_closing'][tag]) { + this['_insert_'+position+'_closing'][tag] = []; + } + this['_insert_'+position+'_closing'][tag].push(content); +}; + +WYMeditor.XhtmlSaxListener.prototype._getClosingTagContent = function(position, tag) { + if (this['_insert_'+position+'_closing'] && + this['_insert_'+position+'_closing'][tag] && + this['_insert_'+position+'_closing'][tag].length > 0) { + return this['_insert_'+position+'_closing'][tag].pop(); + } + return ''; +}; + +/* + _shouldRemoveTag + ================ + + Specifies if the passed tag with the passed attributes should be removed + from the output or not. This is determined by whether the tag has the class + WYMeditor.EDITOR_ONLY_CLASS or not. If the tag should be removed, the + function returns true. Otherwise, the function returns false. +*/ +WYMeditor.XhtmlSaxListener.prototype._shouldRemoveTag = function(tag, attributes) { + var classes; + + if (!attributes["class"]) { + return false; + } + + classes = attributes["class"].split(" "); + if (jQuery.inArray(WYMeditor.EDITOR_ONLY_CLASS, classes) > -1) { + return true; + } + return false; +}; + +WYMeditor.WymCssLexer = function(parser, only_wym_blocks) +{ + only_wym_blocks = (typeof only_wym_blocks == 'undefined' ? true : only_wym_blocks); + + jQuery.extend(this, new WYMeditor.Lexer(parser, (only_wym_blocks?'Ignore':'WymCss'))); + + this.mapHandler('WymCss', 'Ignore'); + + if(only_wym_blocks === true){ + this.addEntryPattern("/\\\x2a[<\\s]*WYMeditor[>\\s]*\\\x2a/", 'Ignore', 'WymCss'); + this.addExitPattern("/\\\x2a[<\/\\s]*WYMeditor[>\\s]*\\\x2a/", 'WymCss'); + } + + this.addSpecialPattern("[\\sa-z1-6]*\\\x2e[a-z-_0-9]+", 'WymCss', 'WymCssStyleDeclaration'); + + this.addEntryPattern("/\\\x2a", 'WymCss', 'WymCssComment'); + this.addExitPattern("\\\x2a/", 'WymCssComment'); + + this.addEntryPattern("\x7b", 'WymCss', 'WymCssStyle'); + this.addExitPattern("\x7d", 'WymCssStyle'); + + this.addEntryPattern("/\\\x2a", 'WymCssStyle', 'WymCssFeedbackStyle'); + this.addExitPattern("\\\x2a/", 'WymCssFeedbackStyle'); + + return this; +}; + +WYMeditor.WymCssParser = function() +{ + this._in_style = false; + this._has_title = false; + this.only_wym_blocks = true; + this.css_settings = {'classesItems':[], 'editorStyles':[], 'dialogStyles':[]}; + return this; +}; + +WYMeditor.WymCssParser.prototype.parse = function(raw, only_wym_blocks) +{ + only_wym_blocks = (typeof only_wym_blocks == 'undefined' ? this.only_wym_blocks : only_wym_blocks); + this._Lexer = new WYMeditor.WymCssLexer(this, only_wym_blocks); + this._Lexer.parse(raw); +}; + +WYMeditor.WymCssParser.prototype.Ignore = function(match, state) +{ + return true; +}; + +WYMeditor.WymCssParser.prototype.WymCssComment = function(text, status) +{ + if(text.match(/end[a-z0-9\s]*wym[a-z0-9\s]*/mi)){ + return false; + } + if(status == WYMeditor.LEXER_UNMATCHED){ + if(!this._in_style){ + this._has_title = true; + this._current_item = {'title':WYMeditor.Helper.trim(text)}; + }else{ + if(this._current_item[this._current_element]){ + if(!this._current_item[this._current_element].expressions){ + this._current_item[this._current_element].expressions = [text]; + }else{ + this._current_item[this._current_element].expressions.push(text); + } + } + } + this._in_style = true; + } + return true; +}; + +WYMeditor.WymCssParser.prototype.WymCssStyle = function(match, status) +{ + if(status == WYMeditor.LEXER_UNMATCHED){ + match = WYMeditor.Helper.trim(match); + if(match !== ''){ + this._current_item[this._current_element].style = match; + } + }else if (status == WYMeditor.LEXER_EXIT){ + this._in_style = false; + this._has_title = false; + this.addStyleSetting(this._current_item); + } + return true; +}; + +WYMeditor.WymCssParser.prototype.WymCssFeedbackStyle = function(match, status) +{ + if(status == WYMeditor.LEXER_UNMATCHED){ + this._current_item[this._current_element].feedback_style = match.replace(/^([\s\/\*]*)|([\s\/\*]*)$/gm,''); + } + return true; +}; + +WYMeditor.WymCssParser.prototype.WymCssStyleDeclaration = function(match) +{ + match = match.replace(/^([\s\.]*)|([\s\.*]*)$/gm, ''); + + var tag = ''; + if(match.indexOf('.') > 0){ + var parts = match.split('.'); + this._current_element = parts[1]; + tag = parts[0]; + }else{ + this._current_element = match; + } + + if(!this._has_title){ + this._current_item = {'title':(!tag?'':tag.toUpperCase()+': ')+this._current_element}; + this._has_title = true; + } + + if(!this._current_item[this._current_element]){ + this._current_item[this._current_element] = {'name':this._current_element}; + } + if(tag){ + if(!this._current_item[this._current_element].tags){ + this._current_item[this._current_element].tags = [tag]; + }else{ + this._current_item[this._current_element].tags.push(tag); + } + } + return true; +}; + +WYMeditor.WymCssParser.prototype.addStyleSetting = function(style_details) +{ + for (var name in style_details){ + var details = style_details[name]; + if(typeof details == 'object' && name != 'title'){ + + this.css_settings.classesItems.push({ + 'name': WYMeditor.Helper.trim(details.name), + 'title': style_details.title, + 'expr' : WYMeditor.Helper.trim((details.expressions||details.tags).join(', ')) + }); + if(details.feedback_style){ + this.css_settings.editorStyles.push({ + 'name': '.'+ WYMeditor.Helper.trim(details.name), + 'css': details.feedback_style + }); + } + if(details.style){ + this.css_settings.dialogStyles.push({ + 'name': '.'+ WYMeditor.Helper.trim(details.name), + 'css': details.style + }); + } +} +} +}; + + diff --git a/public/wymeditor/wymeditor/jquery.wymeditor.min.js b/public/wymeditor/wymeditor/jquery.wymeditor.min.js new file mode 100644 index 0000000..ea8e97f --- /dev/null +++ b/public/wymeditor/wymeditor/jquery.wymeditor.min.js @@ -0,0 +1,6 @@ +if(typeof WYMeditor==="undefined"){WYMeditor={}}!function(){if(typeof window.console==="undefined"&&typeof console==="undefined"){var names=["log","debug","info","warn","error","assert","dir","dirxml","group","groupEnd","time","timeEnd","count","trace","profile","profileEnd"],noOp=function(){},i;WYMeditor.console={};for(i=0;i"+"
              "+WYMeditor.TOOLS+"
              "+"
              "+"
              "+WYMeditor.CONTAINERS+WYMeditor.CLASSES+"
              "+"
              "+WYMeditor.HTML+WYMeditor.IFRAME+WYMeditor.STATUS+"
              "+"
              "+WYMeditor.LOGO+"
              "+"
      ",logoHtml:String()+'WYMeditor',iframeHtml:String()+'
      '+'"+"
      ",editorStyles:[],toolsHtml:String()+'
      '+"

      {Tools}

      "+"
        "+WYMeditor.TOOLS_ITEMS+"
      "+"
      ",toolsItemHtml:String()+'
    2. '+''+WYMeditor.TOOL_TITLE+""+"
    3. ",toolsItems:[{name:"Bold",title:"Strong",css:"wym_tools_strong"},{name:"Italic",title:"Emphasis",css:"wym_tools_emphasis"},{name:"Superscript",title:"Superscript",css:"wym_tools_superscript"},{name:"Subscript",title:"Subscript",css:"wym_tools_subscript"},{name:"InsertOrderedList",title:"Ordered_List",css:"wym_tools_ordered_list"},{name:"InsertUnorderedList",title:"Unordered_List",css:"wym_tools_unordered_list"},{name:"Indent",title:"Indent",css:"wym_tools_indent"},{name:"Outdent",title:"Outdent",css:"wym_tools_outdent"},{name:"Undo",title:"Undo",css:"wym_tools_undo"},{name:"Redo",title:"Redo",css:"wym_tools_redo"},{name:"CreateLink",title:"Link",css:"wym_tools_link"},{name:"Unlink",title:"Unlink",css:"wym_tools_unlink"},{name:"InsertImage",title:"Image",css:"wym_tools_image"},{name:"InsertTable",title:"Table",css:"wym_tools_table"},{name:"Paste",title:"Paste_From_Word",css:"wym_tools_paste"},{name:"ToggleHtml",title:"HTML",css:"wym_tools_html"},{name:"Preview",title:"Preview",css:"wym_tools_preview"}],containersHtml:String()+'
      '+"

      {Containers}

      "+"
        "+WYMeditor.CONTAINERS_ITEMS+"
      "+"
      ",containersItemHtml:String()+'
    4. '+''+WYMeditor.CONTAINER_TITLE+""+"
    5. ",containersItems:[{name:"P",title:"Paragraph",css:"wym_containers_p"},{name:"H1",title:"Heading_1",css:"wym_containers_h1"},{name:"H2",title:"Heading_2",css:"wym_containers_h2"},{name:"H3",title:"Heading_3",css:"wym_containers_h3"},{name:"H4",title:"Heading_4",css:"wym_containers_h4"},{name:"H5",title:"Heading_5",css:"wym_containers_h5"},{name:"H6",title:"Heading_6",css:"wym_containers_h6"},{name:"PRE",title:"Preformatted",css:"wym_containers_pre"},{name:"BLOCKQUOTE",title:"Blockquote",css:"wym_containers_blockquote"},{name:"TH",title:"Table_Header",css:"wym_containers_th"}],classesHtml:String()+'
      '+"

      {Classes}

      "+"
        "+WYMeditor.CLASSES_ITEMS+"
      "+"
      ",classesItemHtml:String()+'
    6. '+''+WYMeditor.CLASS_TITLE+""+"
    7. ",classesItems:[],statusHtml:String()+'
      '+"

      {Status}

      "+"
      ",htmlHtml:String()+'
      '+"

      {Source_Code}

      "+''+"
      ",boxSelector:".wym_box",toolsSelector:".wym_tools",toolsListSelector:" ul",containersSelector:".wym_containers",classesSelector:".wym_classes",htmlSelector:".wym_html",iframeSelector:".wym_iframe iframe",iframeBodySelector:".wym_iframe",statusSelector:".wym_status",toolSelector:".wym_tools a",containerSelector:".wym_containers a",classSelector:".wym_classes a",htmlValSelector:".wym_html_val",hrefSelector:".wym_href",srcSelector:".wym_src",titleSelector:".wym_title",relSelector:".wym_rel",altSelector:".wym_alt",textSelector:".wym_text",rowsSelector:".wym_rows",colsSelector:".wym_cols",captionSelector:".wym_caption",summarySelector:".wym_summary",submitSelector:"form",cancelSelector:".wym_cancel",previewSelector:"",dialogTypeSelector:".wym_dialog_type",dialogLinkSelector:".wym_dialog_link",dialogImageSelector:".wym_dialog_image",dialogTableSelector:".wym_dialog_table",dialogPasteSelector:".wym_dialog_paste",dialogPreviewSelector:".wym_dialog_preview",updateSelector:".wymupdate",updateEvent:"click",dialogFeatures:"menubar=no,titlebar=no,toolbar=no,resizable=no"+",width=560,height=300,top=0,left=0",dialogFeaturesPreview:"menubar=no,titlebar=no,toolbar=no,resizable=no"+",scrollbars=yes,width=560,height=300,top=0,left=0",dialogHtml:String()+''+''+""+''+""+WYMeditor.DIALOG_TITLE+""+''+''+""+WYMeditor.DIALOG_BODY+"",dialogLinkHtml:String()+''+"
      "+"
      "+''+"{Link}"+'
      '+""+''+"
      "+'
      '+""+''+"
      "+'
      '+""+''+"
      "+'
      '+''+''+"
      "+"
      "+"
      "+"",dialogImageHtml:String()+''+"
      "+"
      "+''+"{Image}"+'
      '+""+''+"
      "+'
      '+""+''+"
      "+'
      '+""+''+"
      "+'
      '+''+''+"
      "+"
      "+"
      "+"",dialogTableHtml:String()+''+"
      "+"
      "+''+"{Table}"+'
      '+""+''+"
      "+'
      '+""+''+"
      "+'
      '+""+''+"
      "+'
      '+""+''+"
      "+'
      '+''+''+"
      "+"
      "+"
      "+"",dialogPasteHtml:String()+''+"
      "+''+"
      "+"{Paste_From_Word}"+'
      '+''+"
      "+'
      '+''+''+"
      "+"
      "+"
      "+"",dialogPreviewHtml:String()+'',dialogStyles:[],stringDelimiterLeft:"{",stringDelimiterRight:"}",preInit:null,preBind:null,postInit:null,preInitDialog:null,postInitDialog:null},options);return this.each(function(){var _editor=new WYMeditor.editor(jQuery(this),options)})};jQuery.extend({wymeditors:function(i){return WYMeditor.INSTANCES[i]}});WYMeditor.computeWymPath=function(){var script=jQuery(jQuery.grep(jQuery("script"),function(s){if(!s.src){return null}return s.src.match(/jquery\.wymeditor(\.pack|\.min|\.packed)?\.js(\?.*)?$/)||s.src.match(/\/core\.js(\?.*)?$/)}));if(script.length>0){return script.attr("src")}WYMeditor.console.warn("Error determining wymPath. No base WYMeditor file located.");WYMeditor.console.warn("Assuming wymPath to be the current URL");WYMeditor.console.warn("Please pass a correct wymPath option");return""};WYMeditor.computeBasePath=function(wymPath){var lastSlashIndex=wymPath.lastIndexOf("/");return wymPath.substr(0,lastSlashIndex+1)};WYMeditor.computeJqueryPath=function(){return jQuery(jQuery.grep(jQuery("script"),function(s){return s.src&&s.src.match(/jquery(-(.*)){0,1}(\.pack|\.min|\.packed)?\.js(\?.*)?$/)})).attr("src")};WYMeditor.INIT_DIALOG=function(index){var wym=window.opener.WYMeditor.INSTANCES[index],doc=window.document,selected=wym.selected(),dialogType=jQuery(wym._options.dialogTypeSelector).val(),sStamp=wym.uniqueStamp(),styles,aCss,tableOnClick;if(dialogType===WYMeditor.DIALOG_LINK){if(selected&&selected.tagName&&selected.tagName.toLowerCase!==WYMeditor.A){selected=jQuery(selected).parentsOrSelf(WYMeditor.A)}if(!selected&&wym._selected_image){selected=jQuery(wym._selected_image).parentsOrSelf(WYMeditor.A)}}if(jQuery.isFunction(wym._options.preInitDialog)){wym._options.preInitDialog(wym,window)}styles=doc.styleSheets[0];aCss=eval(wym._options.dialogStyles);wym.addCssRules(doc,aCss);if(selected){jQuery(wym._options.hrefSelector).val(jQuery(selected).attr(WYMeditor.HREF));jQuery(wym._options.srcSelector).val(jQuery(selected).attr(WYMeditor.SRC));jQuery(wym._options.titleSelector).val(jQuery(selected).attr(WYMeditor.TITLE));jQuery(wym._options.relSelector).val(jQuery(selected).attr(WYMeditor.REL));jQuery(wym._options.altSelector).val(jQuery(selected).attr(WYMeditor.ALT))}if(wym._selected_image){jQuery(wym._options.dialogImageSelector+" "+wym._options.srcSelector).val(jQuery(wym._selected_image).attr(WYMeditor.SRC));jQuery(wym._options.dialogImageSelector+" "+wym._options.titleSelector).val(jQuery(wym._selected_image).attr(WYMeditor.TITLE));jQuery(wym._options.dialogImageSelector+" "+wym._options.altSelector).val(jQuery(wym._selected_image).attr(WYMeditor.ALT))}jQuery(wym._options.dialogLinkSelector+" "+wym._options.submitSelector).submit(function(){var sUrl=jQuery(wym._options.hrefSelector).val(),link;if(sUrl.length>0){if(selected[0]&&selected[0].tagName.toLowerCase()===WYMeditor.A){link=selected}else{wym._exec(WYMeditor.CREATE_LINK,sStamp);link=jQuery("a[href="+sStamp+"]",wym._doc.body)}link.attr(WYMeditor.HREF,sUrl);link.attr(WYMeditor.TITLE,jQuery(wym._options.titleSelector).val());link.attr(WYMeditor.REL,jQuery(wym._options.relSelector).val())}window.close()});jQuery(wym._options.dialogImageSelector+" "+wym._options.submitSelector).submit(function(){var sUrl=jQuery(wym._options.srcSelector).val(),$img;if(sUrl.length>0){wym._exec(WYMeditor.INSERT_IMAGE,sStamp);$img=jQuery("img[src$="+sStamp+"]",wym._doc.body);$img.attr(WYMeditor.SRC,sUrl);$img.attr(WYMeditor.TITLE,jQuery(wym._options.titleSelector).val());$img.attr(WYMeditor.ALT,jQuery(wym._options.altSelector).val())}window.close()});tableOnClick=WYMeditor.MAKE_TABLE_ONCLICK(wym);jQuery(wym._options.dialogTableSelector+" "+wym._options.submitSelector).submit(tableOnClick);jQuery(wym._options.dialogPasteSelector+" "+wym._options.submitSelector).submit(function(){var sText=jQuery(wym._options.textSelector).val();wym.paste(sText);window.close()});jQuery(wym._options.dialogPreviewSelector+" "+wym._options.previewSelector).html(wym.xhtml());jQuery(wym._options.cancelSelector).mousedown(function(){window.close()});if(jQuery.isFunction(wym._options.postInitDialog)){wym._options.postInitDialog(wym,window)}};WYMeditor.MAKE_TABLE_ONCLICK=function(wym){var tableOnClick=function(){var numRows=jQuery(wym._options.rowsSelector).val(),numColumns=jQuery(wym._options.colsSelector).val(),caption=jQuery(wym._options.captionSelector).val(),summary=jQuery(wym._options.summarySelector).val(),table=wym.insertTable(numRows,numColumns,caption,summary);window.close()};return tableOnClick};jQuery.fn.isPhantomNode=function(){if(this[0].nodeType===3){return!/[^\t\n\r ]/.test(this[0].data)}return false};jQuery.fn.nextContentsUntil=function(selector,filter){var matched=[],$matched,cur=this.get(0);selector=selector?selector:"";filter=filter?filter:"";if(!cur){return jQuery()}cur=cur.nextSibling;while(cur){if(!jQuery(cur).is(selector)){matched.push(cur);cur=cur.nextSibling}else{break}}$matched=jQuery(matched);if(filter){return $matched.filter(filter)}return $matched};jQuery.fn.nextAllContents=function(){return jQuery(this).nextContentsUntil("","")};jQuery.fn.prevContentsUntil=function(selector,filter){var matched=[],$matched,cur=this.get(0);selector=selector?selector:"";filter=filter?filter:"";if(!cur){return jQuery()}cur=cur.previousSibling;while(cur){if(!jQuery(cur).is(selector)){matched.push(cur);cur=cur.previousSibling}else{break}}$matched=jQuery(matched);if(filter){return $matched.filter(filter)}return $matched};jQuery.fn.prevAllContents=function(){return jQuery(this).prevContentsUntil("","")};WYMeditor.isPhantomNode=function(n){if(n.nodeType===3){return!/[^\t\n\r ]/.test(n.data)}return false};WYMeditor.isPhantomString=function(str){return!/[^\t\n\r ]/.test(str)};jQuery.fn.parentsOrSelf=function(jqexpr){var n=this;if(n[0].nodeType===3){n=n.parents().slice(0,1)}if(n.filter(jqexpr).size()===1){return n}else{return n.parents(jqexpr).slice(0,1)}};WYMeditor.changeNodeType=function(node,newTag){var newNode,i,attributes=node.attributes;jQuery(node).wrapInner("<"+newTag+">");newNode=jQuery(node).children().get(0);for(i=0;i["+node.childNodes.length+"]"}else{return node.nodeName}}function NodeIterator(root){this.root=root;this._next=root}NodeIterator.prototype={_current:null,hasNext:function(){return!!this._next},next:function(){var n=this._current=this._next;var child,next;if(this._current){child=n.firstChild;if(child){this._next=child}else{next=null;while(n!==this.root&&!(next=n.nextSibling)){n=n.parentNode}this._next=next}}return this._current},detach:function(){this._current=this._next=this.root=null}};function createIterator(root){return new NodeIterator(root)}function DomPosition(node,offset){this.node=node;this.offset=offset}DomPosition.prototype={equals:function(pos){return this.node===pos.node&this.offset==pos.offset},inspect:function(){return"[DomPosition("+inspectNode(this.node)+":"+this.offset+")]"}};function DOMException(codeName){this.code=this[codeName];this.codeName=codeName;this.message="DOMException: "+this.codeName}DOMException.prototype={INDEX_SIZE_ERR:1,HIERARCHY_REQUEST_ERR:3,WRONG_DOCUMENT_ERR:4,NO_MODIFICATION_ALLOWED_ERR:7,NOT_FOUND_ERR:8,NOT_SUPPORTED_ERR:9,INVALID_STATE_ERR:11};DOMException.prototype.toString=function(){return this.message};api.dom={arrayContains:arrayContains,isHtmlNamespace:isHtmlNamespace,parentElement:parentElement,getNodeIndex:getNodeIndex,getNodeLength:getNodeLength,getCommonAncestor:getCommonAncestor,isAncestorOf:isAncestorOf,getClosestAncestorIn:getClosestAncestorIn,isCharacterDataNode:isCharacterDataNode,insertAfter:insertAfter,splitDataNode:splitDataNode,getDocument:getDocument,getWindow:getWindow,getIframeWindow:getIframeWindow,getIframeDocument:getIframeDocument,getBody:getBody,getRootContainer:getRootContainer,comparePoints:comparePoints,inspectNode:inspectNode,fragmentFromNodeChildren:fragmentFromNodeChildren,createIterator:createIterator,DomPosition:DomPosition}; +api.DOMException=DOMException});rangy.createModule("DomRange",function(api,module){api.requireModules(["DomUtil"]);var dom=api.dom;var DomPosition=dom.DomPosition;var DOMException=api.DOMException;function isNonTextPartiallySelected(node,range){return node.nodeType!=3&&(dom.isAncestorOf(node,range.startContainer,true)||dom.isAncestorOf(node,range.endContainer,true))}function getRangeDocument(range){return dom.getDocument(range.startContainer)}function dispatchEvent(range,type,args){var listeners=range._listeners[type];if(listeners){for(var i=0,len=listeners.length;i=n.childNodes.length){n.appendChild(node)}else{n.insertBefore(node,n.childNodes[o])}return firstNodeInserted}function cloneSubtree(iterator){var partiallySelected;for(var node,frag=getRangeDocument(iterator.range).createDocumentFragment(),subIterator;node=iterator.next();){partiallySelected=iterator.isPartiallySelectedSubtree();node=node.cloneNode(!partiallySelected);if(partiallySelected){subIterator=iterator.getSubtreeIterator();node.appendChild(cloneSubtree(subIterator));subIterator.detach(true)}if(node.nodeType==10){throw new DOMException("HIERARCHY_REQUEST_ERR")}frag.appendChild(node)}return frag}function iterateSubtree(rangeIterator,func,iteratorState){var it,n;iteratorState=iteratorState||{stop:false};for(var node,subRangeIterator;node=rangeIterator.next();){if(rangeIterator.isPartiallySelectedSubtree()){if(func(node)===false){iteratorState.stop=true;return}else{subRangeIterator=rangeIterator.getSubtreeIterator();iterateSubtree(subRangeIterator,func,iteratorState);subRangeIterator.detach(true);if(iteratorState.stop){return}}}else{it=dom.createIterator(node);while(n=it.next()){if(func(n)===false){iteratorState.stop=true;return}}}}}function deleteSubtree(iterator){var subIterator;while(iterator.next()){if(iterator.isPartiallySelectedSubtree()){subIterator=iterator.getSubtreeIterator();deleteSubtree(subIterator);subIterator.detach(true)}else{iterator.remove()}}}function extractSubtree(iterator){for(var node,frag=getRangeDocument(iterator.range).createDocumentFragment(),subIterator;node=iterator.next();){if(iterator.isPartiallySelectedSubtree()){node=node.cloneNode(false);subIterator=iterator.getSubtreeIterator();node.appendChild(extractSubtree(subIterator));subIterator.detach(true)}else{iterator.remove()}if(node.nodeType==10){throw new DOMException("HIERARCHY_REQUEST_ERR")}frag.appendChild(node)}return frag}function getNodesInRange(range,nodeTypes,filter){var filterNodeTypes=!!(nodeTypes&&nodeTypes.length),regex;var filterExists=!!filter;if(filterNodeTypes){regex=new RegExp("^("+nodeTypes.join("|")+")$")}var nodes=[];iterateSubtree(new RangeIterator(range,false),function(node){if((!filterNodeTypes||regex.test(node.nodeType))&&(!filterExists||filter(node))){nodes.push(node)}});return nodes}function inspect(range){var name=typeof range.getName=="undefined"?"Range":range.getName();return"["+name+"("+dom.inspectNode(range.startContainer)+":"+range.startOffset+", "+dom.inspectNode(range.endContainer)+":"+range.endOffset+")]"}function RangeIterator(range,clonePartiallySelectedTextNodes){this.range=range;this.clonePartiallySelectedTextNodes=clonePartiallySelectedTextNodes;if(!range.collapsed){this.sc=range.startContainer;this.so=range.startOffset;this.ec=range.endContainer;this.eo=range.endOffset;var root=range.commonAncestorContainer;if(this.sc===this.ec&&dom.isCharacterDataNode(this.sc)){this.isSingleCharacterDataNode=true;this._first=this._last=this._next=this.sc}else{this._first=this._next=this.sc===root&&!dom.isCharacterDataNode(this.sc)?this.sc.childNodes[this.so]:dom.getClosestAncestorIn(this.sc,root,true);this._last=this.ec===root&&!dom.isCharacterDataNode(this.ec)?this.ec.childNodes[this.eo-1]:dom.getClosestAncestorIn(this.ec,root,true)}}}RangeIterator.prototype={_current:null,_next:null,_first:null,_last:null,isSingleCharacterDataNode:false,reset:function(){this._current=null;this._next=this._first},hasNext:function(){return!!this._next},next:function(){var current=this._current=this._next;if(current){this._next=current!==this._last?current.nextSibling:null;if(dom.isCharacterDataNode(current)&&this.clonePartiallySelectedTextNodes){if(current===this.ec){(current=current.cloneNode(true)).deleteData(this.eo,current.length-this.eo)}if(this._current===this.sc){(current=current.cloneNode(true)).deleteData(0,this.so)}}}return current},remove:function(){var current=this._current,start,end;if(dom.isCharacterDataNode(current)&&(current===this.sc||current===this.ec)){start=current===this.sc?this.so:0;end=current===this.ec?this.eo:current.length;if(start!=end){current.deleteData(start,end-start)}}else{if(current.parentNode){current.parentNode.removeChild(current)}else{}}},isPartiallySelectedSubtree:function(){var current=this._current;return isNonTextPartiallySelected(current,this.range)},getSubtreeIterator:function(){var subRange;if(this.isSingleCharacterDataNode){subRange=this.range.cloneRange();subRange.collapse()}else{subRange=new Range(getRangeDocument(this.range));var current=this._current;var startContainer=current,startOffset=0,endContainer=current,endOffset=dom.getNodeLength(current);if(dom.isAncestorOf(current,this.sc,true)){startContainer=this.sc;startOffset=this.so}if(dom.isAncestorOf(current,this.ec,true)){endContainer=this.ec;endOffset=this.eo}updateBoundaries(subRange,startContainer,startOffset,endContainer,endOffset)}return new RangeIterator(subRange,this.clonePartiallySelectedTextNodes)},detach:function(detachRange){if(detachRange){this.range.detach()}this.range=this._current=this._next=this._first=this._last=this.sc=this.so=this.ec=this.eo=null}};function RangeException(codeName){this.code=this[codeName];this.codeName=codeName;this.message="RangeException: "+this.codeName}RangeException.prototype={BAD_BOUNDARYPOINTS_ERR:1,INVALID_NODE_TYPE_ERR:2};RangeException.prototype.toString=function(){return this.message};function RangeNodeIterator(range,nodeTypes,filter){this.nodes=getNodesInRange(range,nodeTypes,filter);this._next=this.nodes[0];this._position=0}RangeNodeIterator.prototype={_current:null,hasNext:function(){return!!this._next},next:function(){this._current=this._next;this._next=this.nodes[++this._position];return this._current},detach:function(){this._current=this._next=this.nodes=null}};var beforeAfterNodeTypes=[1,3,4,5,7,8,10];var rootContainerNodeTypes=[2,9,11];var readonlyNodeTypes=[5,6,10,12];var insertableNodeTypes=[1,3,4,5,7,8,10,11];var surroundNodeTypes=[1,3,4,5,7,8];function createAncestorFinder(nodeTypes){return function(node,selfIsAncestor){var t,n=selfIsAncestor?node:node.parentNode;while(n){t=n.nodeType;if(dom.arrayContains(nodeTypes,t)){return n}n=n.parentNode}return null}}var getRootContainer=dom.getRootContainer;var getDocumentOrFragmentContainer=createAncestorFinder([9,11]);var getReadonlyAncestor=createAncestorFinder(readonlyNodeTypes);var getDocTypeNotationEntityAncestor=createAncestorFinder([6,10,12]);function assertNoDocTypeNotationEntityAncestor(node,allowSelf){if(getDocTypeNotationEntityAncestor(node,allowSelf)){throw new RangeException("INVALID_NODE_TYPE_ERR")}}function assertNotDetached(range){if(!range.startContainer){throw new DOMException("INVALID_STATE_ERR")}}function assertValidNodeType(node,invalidTypes){if(!dom.arrayContains(invalidTypes,node.nodeType)){throw new RangeException("INVALID_NODE_TYPE_ERR")}}function assertValidOffset(node,offset){if(offset<0||offset>(dom.isCharacterDataNode(node)?node.length:node.childNodes.length)){throw new DOMException("INDEX_SIZE_ERR")}}function assertSameDocumentOrFragment(node1,node2){if(getDocumentOrFragmentContainer(node1,true)!==getDocumentOrFragmentContainer(node2,true)){throw new DOMException("WRONG_DOCUMENT_ERR")}}function assertNodeNotReadOnly(node){if(getReadonlyAncestor(node,true)){throw new DOMException("NO_MODIFICATION_ALLOWED_ERR")}}function assertNode(node,codeName){if(!node){throw new DOMException(codeName)}}function isOrphan(node){return!dom.arrayContains(rootContainerNodeTypes,node.nodeType)&&!getDocumentOrFragmentContainer(node,true)}function isValidOffset(node,offset){return offset<=(dom.isCharacterDataNode(node)?node.length:node.childNodes.length)}function assertRangeValid(range){assertNotDetached(range);if(isOrphan(range.startContainer)||isOrphan(range.endContainer)||!isValidOffset(range.startContainer,range.startOffset)||!isValidOffset(range.endContainer,range.endOffset)){throw new Error("Range error: Range is no longer valid after DOM mutation ("+range.inspect()+")")}}var styleEl=document.createElement("style");var htmlParsingConforms=false;try{styleEl.innerHTML="x";htmlParsingConforms=styleEl.firstChild.nodeType==3}catch(e){}api.features.htmlParsingConforms=htmlParsingConforms;var createContextualFragment=htmlParsingConforms?function(fragmentStr){var node=this.startContainer;var doc=dom.getDocument(node);if(!node){throw new DOMException("INVALID_STATE_ERR")}var el=null;if(node.nodeType==1){el=node}else if(dom.isCharacterDataNode(node)){el=dom.parentElement(node)}if(el===null||el.nodeName=="HTML"&&dom.isHtmlNamespace(dom.getDocument(el).documentElement)&&dom.isHtmlNamespace(el)){el=doc.createElement("body")}else{el=el.cloneNode(false)}el.innerHTML=fragmentStr;return dom.fragmentFromNodeChildren(el)}:function(fragmentStr){assertNotDetached(this);var doc=getRangeDocument(this);var el=doc.createElement("body");el.innerHTML=fragmentStr;return dom.fragmentFromNodeChildren(el)};var rangeProperties=["startContainer","startOffset","endContainer","endOffset","collapsed","commonAncestorContainer"];var s2s=0,s2e=1,e2e=2,e2s=3;var n_b=0,n_a=1,n_b_a=2,n_i=3;function RangePrototype(){}RangePrototype.prototype={attachListener:function(type,listener){this._listeners[type].push(listener)},compareBoundaryPoints:function(how,range){assertRangeValid(this);assertSameDocumentOrFragment(this.startContainer,range.startContainer);var nodeA,offsetA,nodeB,offsetB;var prefixA=how==e2s||how==s2s?"start":"end";var prefixB=how==s2e||how==s2s?"start":"end";nodeA=this[prefixA+"Container"];offsetA=this[prefixA+"Offset"];nodeB=range[prefixB+"Container"];offsetB=range[prefixB+"Offset"];return dom.comparePoints(nodeA,offsetA,nodeB,offsetB)},insertNode:function(node){assertRangeValid(this);assertValidNodeType(node,insertableNodeTypes);assertNodeNotReadOnly(this.startContainer);if(dom.isAncestorOf(node,this.startContainer,true)){throw new DOMException("HIERARCHY_REQUEST_ERR")}var firstNodeInserted=insertNodeAtPosition(node,this.startContainer,this.startOffset);this.setStartBefore(firstNodeInserted)},cloneContents:function(){assertRangeValid(this);var clone,frag;if(this.collapsed){return getRangeDocument(this).createDocumentFragment()}else{if(this.startContainer===this.endContainer&&dom.isCharacterDataNode(this.startContainer)){clone=this.startContainer.cloneNode(true);clone.data=clone.data.slice(this.startOffset,this.endOffset);frag=getRangeDocument(this).createDocumentFragment();frag.appendChild(clone);return frag}else{var iterator=new RangeIterator(this,true);clone=cloneSubtree(iterator);iterator.detach()}return clone}},canSurroundContents:function(){assertRangeValid(this);assertNodeNotReadOnly(this.startContainer);assertNodeNotReadOnly(this.endContainer);var iterator=new RangeIterator(this,true);var boundariesInvalid=iterator._first&&isNonTextPartiallySelected(iterator._first,this)||iterator._last&&isNonTextPartiallySelected(iterator._last,this);iterator.detach();return!boundariesInvalid},surroundContents:function(node){assertValidNodeType(node,surroundNodeTypes);if(!this.canSurroundContents()){throw new RangeException("BAD_BOUNDARYPOINTS_ERR")}var content=this.extractContents();if(node.hasChildNodes()){while(node.lastChild){node.removeChild(node.lastChild)}}insertNodeAtPosition(node,this.startContainer,this.startOffset);node.appendChild(content);this.selectNode(node)},cloneRange:function(){assertRangeValid(this);var range=new Range(getRangeDocument(this));var i=rangeProperties.length,prop;while(i--){prop=rangeProperties[i];range[prop]=this[prop]}return range},toString:function(){assertRangeValid(this);var sc=this.startContainer;if(sc===this.endContainer&&dom.isCharacterDataNode(sc)){return sc.nodeType==3||sc.nodeType==4?sc.data.slice(this.startOffset,this.endOffset):""}else{var textBits=[],iterator=new RangeIterator(this,true);iterateSubtree(iterator,function(node){if(node.nodeType==3||node.nodeType==4){textBits.push(node.data)}});iterator.detach();return textBits.join("")}},compareNode:function(node){assertRangeValid(this);var parent=node.parentNode;var nodeIndex=dom.getNodeIndex(node);if(!parent){throw new DOMException("NOT_FOUND_ERR")}var startComparison=this.comparePoint(parent,nodeIndex),endComparison=this.comparePoint(parent,nodeIndex+1);if(startComparison<0){return endComparison>0?n_b_a:n_b}else{return endComparison>0?n_a:n_i}},comparePoint:function(node,offset){assertRangeValid(this);assertNode(node,"HIERARCHY_REQUEST_ERR");assertSameDocumentOrFragment(node,this.startContainer);if(dom.comparePoints(node,offset,this.startContainer,this.startOffset)<0){return-1}else if(dom.comparePoints(node,offset,this.endContainer,this.endOffset)>0){return 1}return 0},createContextualFragment:createContextualFragment,toHtml:function(){assertRangeValid(this);var container=getRangeDocument(this).createElement("div");container.appendChild(this.cloneContents());return container.innerHTML},intersectsNode:function(node,touchingIsIntersecting){assertRangeValid(this);assertNode(node,"NOT_FOUND_ERR");if(dom.getDocument(node)!==getRangeDocument(this)){return false}var parent=node.parentNode,offset=dom.getNodeIndex(node);assertNode(parent,"NOT_FOUND_ERR");var startComparison=dom.comparePoints(parent,offset,this.endContainer,this.endOffset),endComparison=dom.comparePoints(parent,offset+1,this.startContainer,this.startOffset);return touchingIsIntersecting?startComparison<=0&&endComparison>=0:startComparison<0&&endComparison>0},isPointInRange:function(node,offset){assertRangeValid(this);assertNode(node,"HIERARCHY_REQUEST_ERR");assertSameDocumentOrFragment(node,this.startContainer);return dom.comparePoints(node,offset,this.startContainer,this.startOffset)>=0&&dom.comparePoints(node,offset,this.endContainer,this.endOffset)<=0},intersectsRange:function(range,touchingIsIntersecting){assertRangeValid(this);if(getRangeDocument(range)!=getRangeDocument(this)){throw new DOMException("WRONG_DOCUMENT_ERR")}var startComparison=dom.comparePoints(this.startContainer,this.startOffset,range.endContainer,range.endOffset),endComparison=dom.comparePoints(this.endContainer,this.endOffset,range.startContainer,range.startOffset);return touchingIsIntersecting?startComparison<=0&&endComparison>=0:startComparison<0&&endComparison>0},intersection:function(range){if(this.intersectsRange(range)){var startComparison=dom.comparePoints(this.startContainer,this.startOffset,range.startContainer,range.startOffset),endComparison=dom.comparePoints(this.endContainer,this.endOffset,range.endContainer,range.endOffset);var intersectionRange=this.cloneRange();if(startComparison==-1){intersectionRange.setStart(range.startContainer,range.startOffset)}if(endComparison==1){intersectionRange.setEnd(range.endContainer,range.endOffset)}return intersectionRange}return null},union:function(range){if(this.intersectsRange(range,true)){var unionRange=this.cloneRange();if(dom.comparePoints(range.startContainer,range.startOffset,this.startContainer,this.startOffset)==-1){unionRange.setStart(range.startContainer,range.startOffset)}if(dom.comparePoints(range.endContainer,range.endOffset,this.endContainer,this.endOffset)==1){unionRange.setEnd(range.endContainer,range.endOffset)}return unionRange}else{throw new RangeException("Ranges do not intersect")}},containsNode:function(node,allowPartial){if(allowPartial){return this.intersectsNode(node,false)}else{return this.compareNode(node)==n_i}},containsNodeContents:function(node){return this.comparePoint(node,0)>=0&&this.comparePoint(node,dom.getNodeLength(node))<=0},containsRange:function(range){return this.intersection(range).equals(range)},containsNodeText:function(node){var nodeRange=this.cloneRange();nodeRange.selectNode(node);var textNodes=nodeRange.getNodes([3]);if(textNodes.length>0){nodeRange.setStart(textNodes[0],0);var lastTextNode=textNodes.pop();nodeRange.setEnd(lastTextNode,lastTextNode.length);var contains=this.containsRange(nodeRange);nodeRange.detach();return contains}else{return this.containsNodeContents(node)}},createNodeIterator:function(nodeTypes,filter){assertRangeValid(this);return new RangeNodeIterator(this,nodeTypes,filter)},getNodes:function(nodeTypes,filter){assertRangeValid(this);return getNodesInRange(this,nodeTypes,filter)},getDocument:function(){return getRangeDocument(this)},collapseBefore:function(node){assertNotDetached(this);this.setEndBefore(node);this.collapse(false)},collapseAfter:function(node){assertNotDetached(this);this.setStartAfter(node);this.collapse(true)},getName:function(){return"DomRange"},equals:function(range){return Range.rangesEqual(this,range)},inspect:function(){return inspect(this)}};function copyComparisonConstantsToObject(obj){obj.START_TO_START=s2s;obj.START_TO_END=s2e;obj.END_TO_END=e2e;obj.END_TO_START=e2s;obj.NODE_BEFORE=n_b;obj.NODE_AFTER=n_a;obj.NODE_BEFORE_AND_AFTER=n_b_a;obj.NODE_INSIDE=n_i}function copyComparisonConstants(constructor){copyComparisonConstantsToObject(constructor);copyComparisonConstantsToObject(constructor.prototype)}function createRangeContentRemover(remover,boundaryUpdater){return function(){assertRangeValid(this);var sc=this.startContainer,so=this.startOffset,root=this.commonAncestorContainer;var iterator=new RangeIterator(this,true);var node,boundary;if(sc!==root){node=dom.getClosestAncestorIn(sc,root,true);boundary=getBoundaryAfterNode(node);sc=boundary.node;so=boundary.offset}iterateSubtree(iterator,assertNodeNotReadOnly);iterator.reset();var returnValue=remover(iterator);iterator.detach();boundaryUpdater(this,sc,so,sc,so);return returnValue}}function createPrototypeRange(constructor,boundaryUpdater,detacher){function createBeforeAfterNodeSetter(isBefore,isStart){return function(node){assertNotDetached(this);assertValidNodeType(node,beforeAfterNodeTypes);assertValidNodeType(getRootContainer(node),rootContainerNodeTypes);var boundary=(isBefore?getBoundaryBeforeNode:getBoundaryAfterNode)(node);(isStart?setRangeStart:setRangeEnd)(this,boundary.node,boundary.offset)}}function setRangeStart(range,node,offset){var ec=range.endContainer,eo=range.endOffset;if(node!==range.startContainer||offset!==range.startOffset){if(getRootContainer(node)!=getRootContainer(ec)||dom.comparePoints(node,offset,ec,eo)==1){ec=node;eo=offset}boundaryUpdater(range,node,offset,ec,eo)}}function setRangeEnd(range,node,offset){var sc=range.startContainer,so=range.startOffset;if(node!==range.endContainer||offset!==range.endOffset){if(getRootContainer(node)!=getRootContainer(sc)||dom.comparePoints(node,offset,sc,so)==-1){sc=node;so=offset}boundaryUpdater(range,sc,so,node,offset)}}function setRangeStartAndEnd(range,node,offset){if(node!==range.startContainer||offset!==range.startOffset||node!==range.endContainer||offset!==range.endOffset){boundaryUpdater(range,node,offset,node,offset)}}constructor.prototype=new RangePrototype;api.util.extend(constructor.prototype,{setStart:function(node,offset){assertNotDetached(this);assertNoDocTypeNotationEntityAncestor(node,true);assertValidOffset(node,offset);setRangeStart(this,node,offset)},setEnd:function(node,offset){assertNotDetached(this);assertNoDocTypeNotationEntityAncestor(node,true);assertValidOffset(node,offset);setRangeEnd(this,node,offset)},setStartBefore:createBeforeAfterNodeSetter(true,true),setStartAfter:createBeforeAfterNodeSetter(false,true),setEndBefore:createBeforeAfterNodeSetter(true,false),setEndAfter:createBeforeAfterNodeSetter(false,false),collapse:function(isStart){assertRangeValid(this);if(isStart){boundaryUpdater(this,this.startContainer,this.startOffset,this.startContainer,this.startOffset)}else{boundaryUpdater(this,this.endContainer,this.endOffset,this.endContainer,this.endOffset)}},selectNodeContents:function(node){assertNotDetached(this);assertNoDocTypeNotationEntityAncestor(node,true);boundaryUpdater(this,node,0,node,dom.getNodeLength(node))},selectNode:function(node){assertNotDetached(this);assertNoDocTypeNotationEntityAncestor(node,false);assertValidNodeType(node,beforeAfterNodeTypes);var start=getBoundaryBeforeNode(node),end=getBoundaryAfterNode(node);boundaryUpdater(this,start.node,start.offset,end.node,end.offset)},extractContents:createRangeContentRemover(extractSubtree,boundaryUpdater),deleteContents:createRangeContentRemover(deleteSubtree,boundaryUpdater),canSurroundContents:function(){assertRangeValid(this);assertNodeNotReadOnly(this.startContainer);assertNodeNotReadOnly(this.endContainer);var iterator=new RangeIterator(this,true);var boundariesInvalid=iterator._first&&isNonTextPartiallySelected(iterator._first,this)||iterator._last&&isNonTextPartiallySelected(iterator._last,this);iterator.detach();return!boundariesInvalid},detach:function(){detacher(this)},splitBoundaries:function(){assertRangeValid(this);var sc=this.startContainer,so=this.startOffset,ec=this.endContainer,eo=this.endOffset;var startEndSame=sc===ec;if(dom.isCharacterDataNode(ec)&&eo>0&&eo0&&so=dom.getNodeIndex(sc)){eo++}so=0}boundaryUpdater(this,sc,so,ec,eo)},normalizeBoundaries:function(){assertRangeValid(this);var sc=this.startContainer,so=this.startOffset,ec=this.endContainer,eo=this.endOffset;var mergeForward=function(node){var sibling=node.nextSibling;if(sibling&&sibling.nodeType==node.nodeType){ec=node;eo=node.length;node.appendData(sibling.data);sibling.parentNode.removeChild(sibling)}};var mergeBackward=function(node){var sibling=node.previousSibling;if(sibling&&sibling.nodeType==node.nodeType){sc=node;var nodeLength=node.length;so=sibling.length;node.insertData(0,sibling.data);sibling.parentNode.removeChild(sibling);if(sc==ec){eo+=so;ec=sc}else if(ec==node.parentNode){var nodeIndex=dom.getNodeIndex(node);if(eo==nodeIndex){ec=node;eo=nodeLength}else if(eo>nodeIndex){eo--}}}};var normalizeStart=true;if(dom.isCharacterDataNode(ec)){if(ec.length==eo){mergeForward(ec)}}else{if(eo>0){var endNode=ec.childNodes[eo-1];if(endNode&&dom.isCharacterDataNode(endNode)){mergeForward(endNode)}}normalizeStart=!this.collapsed}if(normalizeStart){if(dom.isCharacterDataNode(sc)){if(so==0){mergeBackward(sc)}}else{if(so0&&workingNode.previousSibling);boundaryNode=workingNode.nextSibling;if(comparison==-1&&boundaryNode&&dom.isCharacterDataNode(boundaryNode)){workingRange.setEndPoint(isStart?"EndToStart":"EndToEnd",textRange);var offset;if(/[\r\n]/.test(boundaryNode.data)){var tempRange=workingRange.duplicate();var rangeLength=tempRange.text.replace(/\r\n/g,"\r").length;offset=tempRange.moveStart("character",rangeLength);while((comparison=tempRange.compareEndPoints("StartToEnd",tempRange))==-1){offset++;tempRange.moveStart("character",1)}}else{offset=workingRange.text.length}boundaryPosition=new DomPosition(boundaryNode,offset)}else{previousNode=(isCollapsed||!isStart)&&workingNode.previousSibling;nextNode=(isCollapsed||isStart)&&workingNode.nextSibling;if(nextNode&&dom.isCharacterDataNode(nextNode)){boundaryPosition=new DomPosition(nextNode,0)}else if(previousNode&&dom.isCharacterDataNode(previousNode)){boundaryPosition=new DomPosition(previousNode,previousNode.length)}else{boundaryPosition=new DomPosition(containerElement,dom.getNodeIndex(workingNode))}}workingNode.parentNode.removeChild(workingNode);return boundaryPosition}function createBoundaryTextRange(boundaryPosition,isStart){var boundaryNode,boundaryParent,boundaryOffset=boundaryPosition.offset;var doc=dom.getDocument(boundaryPosition.node);var workingNode,childNodes,workingRange=doc.body.createTextRange();var nodeIsDataNode=dom.isCharacterDataNode(boundaryPosition.node);if(nodeIsDataNode){boundaryNode=boundaryPosition.node;boundaryParent=boundaryNode.parentNode}else{childNodes=boundaryPosition.node.childNodes;boundaryNode=boundaryOffset12");iframeDoc.close();var sel=dom.getIframeWindow(iframe).getSelection();var docEl=iframeDoc.documentElement;var iframeBody=docEl.lastChild,textNode=iframeBody.firstChild;var r1=iframeDoc.createRange();r1.setStart(textNode,1);r1.collapse(true);sel.addRange(r1);collapsedNonEditableSelectionsSupported=sel.rangeCount==1;sel.removeAllRanges();var r2=r1.cloneRange();r1.setStart(textNode,0);r2.setEnd(textNode,2);sel.addRange(r1);sel.addRange(r2);selectionSupportsMultipleRanges=sel.rangeCount==2;r1.detach();r2.detach();body.removeChild(iframe)}()}api.features.selectionSupportsMultipleRanges=selectionSupportsMultipleRanges;api.features.collapsedNonEditableSelectionsSupported=collapsedNonEditableSelectionsSupported;var implementsControlRange=false,testControlRange;if(body&&util.isHostMethod(body,"createControlRange")){testControlRange=body.createControlRange();if(util.areHostProperties(testControlRange,["item","add"])){implementsControlRange=true}}api.features.implementsControlRange=implementsControlRange;if(selectionHasAnchorAndFocus){selectionIsCollapsed=function(sel){return sel.anchorNode===sel.focusNode&&sel.anchorOffset===sel.focusOffset}}else{selectionIsCollapsed=function(sel){return sel.rangeCount?sel.getRangeAt(sel.rangeCount-1).collapsed:false}}function updateAnchorAndFocusFromRange(sel,range,backwards){var anchorPrefix=backwards?"end":"start",focusPrefix=backwards?"start":"end";sel.anchorNode=range[anchorPrefix+"Container"];sel.anchorOffset=range[anchorPrefix+"Offset"];sel.focusNode=range[focusPrefix+"Container"];sel.focusOffset=range[focusPrefix+"Offset"]}function updateAnchorAndFocusFromNativeSelection(sel){var nativeSel=sel.nativeSelection;sel.anchorNode=nativeSel.anchorNode;sel.anchorOffset=nativeSel.anchorOffset;sel.focusNode=nativeSel.focusNode;sel.focusOffset=nativeSel.focusOffset}function updateEmptySelection(sel){sel.anchorNode=sel.focusNode=null;sel.anchorOffset=sel.focusOffset=0;sel.rangeCount=0;sel.isCollapsed=true;sel._ranges.length=0}function getNativeRange(range){var nativeRange;if(range instanceof DomRange){nativeRange=range._selectionNativeRange;if(!nativeRange){nativeRange=api.createNativeRange(dom.getDocument(range.startContainer));nativeRange.setEnd(range.endContainer,range.endOffset);nativeRange.setStart(range.startContainer,range.startOffset);range._selectionNativeRange=nativeRange;range.attachListener("detach",function(){this._selectionNativeRange=null})}}else if(range instanceof WrappedRange){nativeRange=range.nativeRange}else if(api.features.implementsDomRange&&range instanceof dom.getWindow(range.startContainer).Range){nativeRange=range}return nativeRange}function rangeContainsSingleElement(rangeNodes){if(!rangeNodes.length||rangeNodes[0].nodeType!=1){return false}for(var i=1,len=rangeNodes.length;i1){createControlSelection(this,ranges)}else{this.removeAllRanges();for(var i=0,len=ranges.length;i1){createControlSelection(this,ranges)}else if(rangeCount){this.addRange(ranges[0])}}}else{module.fail("No means of selecting a Range or TextRange was found");return false}selProto.getRangeAt=function(index){if(index<0||index>=this.rangeCount){throw new DOMException("INDEX_SIZE_ERR")}else{return this._ranges[index]}};var refreshSelection;if(useDocumentSelection){refreshSelection=function(sel){var range;if(api.isSelectionValid(sel.win)){range=sel.docSelection.createRange()}else{range=dom.getBody(sel.win.document).createTextRange();range.collapse(true)}if(sel.docSelection.type==CONTROL){updateControlSelection(sel)}else if(isTextRange(range)){updateFromTextRange(sel,range)}else{updateEmptySelection(sel)}}}else if(util.isHostMethod(testSelection,"getRangeAt")&&typeof testSelection.rangeCount=="number"){refreshSelection=function(sel){if(implementsControlRange&&implementsDocSelection&&sel.docSelection.type==CONTROL){updateControlSelection(sel)}else{sel._ranges.length=sel.rangeCount=sel.nativeSelection.rangeCount;if(sel.rangeCount){for(var i=0,len=sel.rangeCount;i=0;--i){range=ranges[i];if(range.collapsed){range.collapseBefore(gEBI(rangeInfos[i].markerId,doc))}else{range.setEndBefore(gEBI(rangeInfos[i].endMarkerId,doc));range.setStartAfter(gEBI(rangeInfos[i].startMarkerId,doc))}}sel.setRanges(ranges);return{win:win,doc:doc,rangeInfos:rangeInfos,restored:false}}function restoreSelection(savedSelection,preserveDirection){if(!savedSelection.restored){var rangeInfos=savedSelection.rangeInfos;var sel=api.getSelection(savedSelection.win);var ranges=[];for(var len=rangeInfos.length,i=len-1,rangeInfo,range;i>=0;--i){rangeInfo=rangeInfos[i];range=api.createRange(savedSelection.doc);if(rangeInfo.collapsed){var markerEl=gEBI(rangeInfo.markerId,savedSelection.doc);if(markerEl){markerEl.style.display="inline";var previousNode=markerEl.previousSibling;if(previousNode&&previousNode.nodeType==3){markerEl.parentNode.removeChild(markerEl);range.collapseToPoint(previousNode,previousNode.length)}else{range.collapseBefore(markerEl);markerEl.parentNode.removeChild(markerEl)}}else{module.warn("Marker element has been removed. Cannot restore selection.")}}else{setRangeBoundary(savedSelection.doc,range,rangeInfo.startMarkerId,true);setRangeBoundary(savedSelection.doc,range,rangeInfo.endMarkerId,false)}if(len==1){range.normalizeBoundaries()}ranges[i]=range}if(len==1&&preserveDirection&&api.features.selectionHasExtend&&rangeInfos[0].backwards){sel.removeAllRanges();sel.addRange(ranges[0],true)}else{sel.setRanges(ranges)}savedSelection.restored=true}}function removeMarkerElement(doc,markerId){var markerEl=gEBI(markerId,doc);if(markerEl){markerEl.parentNode.removeChild(markerEl)}}function removeMarkers(savedSelection){var rangeInfos=savedSelection.rangeInfos;for(var i=0,len=rangeInfos.length,rangeInfo;i0){firstNode=nodes.item(0)}for(x=0;x-1};WYMeditor.editor.prototype.keyCanCreateBlockElement=function(keyCode){return jQuery.inArray(keyCode,WYMeditor.POTENTIAL_BLOCK_ELEMENT_CREATION_KEYS)>-1};WYMeditor.editor.prototype.toggleClass=function(sClass,jqexpr){var container=null;if(this._selected_image){container=this._selected_image}else{container=jQuery(this.selected())}container=jQuery(container).parentsOrSelf(jqexpr);jQuery(container).toggleClass(sClass);if(!jQuery(container).attr(WYMeditor.CLASS)){jQuery(container).removeAttr(this._class)}};WYMeditor.editor.prototype.findUp=function(node,filter){if(typeof node==="undefined"||node===null){return null}if(node.nodeName==="#text"){node=node.parentNode}var tagname=node.tagName.toLowerCase(),bFound,i;if(typeof filter===WYMeditor.STRING){while(tagname!==filter&&tagname!==WYMeditor.BODY){node=node.parentNode;tagname=node.tagName.toLowerCase()}}else{bFound=false;while(!bFound&&tagname!==WYMeditor.BODY){for(i=0;i"}else{placeholderNode="
      "}if(children.length>0){$firstChild=jQuery(children[0]);$lastChild=jQuery(children[children.length-1]);if($firstChild.is(blockingSelector)){$firstChild.before(placeholderNode)}if($lastChild.is(blockingSelector)&&!(jQuery.browser.msie&&jQuery.browser.version<"7.0")){$lastChild.after(placeholderNode)}}blockSepSelector=this._getBlockSepSelector();$body.find(blockSepSelector).before(placeholderNode);blockInListSepSelector=this._getBlockInListSepSelector();$blockInList=$body.find(blockInListSepSelector);$blockInList.each(function(){var $block=jQuery(this);if(!$block.next(blockingSelector).length&&!$block.next(WYMeditor.BR).length){$block.after(placeholderNode)}})};WYMeditor.editor.prototype._getBlockSepSelector=function(){if(typeof this._blockSpacersSel!=="undefined"){return this._blockSpacersSel}var wym=this,blockCombo=[],containersBlockingNav=WYMeditor.DocumentStructureManager.CONTAINERS_BLOCKING_NAVIGATION,containersNotBlockingNav;containersNotBlockingNav=jQuery.grep(wym.documentStructureManager.structureRules.validRootContainers,function(item){return jQuery.inArray(item,containersBlockingNav)===-1});jQuery.each(containersBlockingNav,function(indexO,elementO){jQuery.each(containersBlockingNav,function(indexI,elementI){blockCombo.push(elementO+" + "+elementI)})});jQuery.each(containersBlockingNav,function(indexO,elementO){jQuery.each(containersNotBlockingNav,function(indexI,elementI){blockCombo.push(elementO+" + "+elementI);blockCombo.push(elementI+" + "+elementO)})});this._blockSpacersSel=blockCombo.join(", ");return this._blockSpacersSel};WYMeditor.editor.prototype._getBlockInListSepSelector=function(){if(typeof this._blockInListSpacersSel!=="undefined"){return this._blockInListSpacersSel}var blockCombo=[];jQuery.each(WYMeditor.LIST_TYPE_ELEMENTS,function(indexO,elementO){jQuery.each(WYMeditor.BLOCKING_ELEMENTS,function(indexI,elementI){blockCombo.push(elementO+" "+elementI)})});this._blockInListSpacersSel=blockCombo.join(", ");return this._blockInListSpacersSel};WYMeditor.editor.prototype.fixDoubleBr=function(){var $body=jQuery(this._doc).find("body.wym_iframe"),$last_br,blockingSelector=WYMeditor.BLOCKING_ELEMENTS.join(", ");$body.children("br + br").filter(":not(pre br)").remove();$body.find("p + br").next("p").prev("br").remove();$last_br=$body.find("p + br").slice(-1);if($last_br.length>0){if($last_br.next().length===0){$last_br.remove()}}};WYMeditor.editor.prototype.dialog=function(dialogType,dialogFeatures,bodyHtml){var features=dialogFeatures||this._wym._options.dialogFeatures,wDialog=window.open("","dialog",features),sBodyHtml,h=WYMeditor.Helper,dialogHtml,doc;if(wDialog){sBodyHtml="";switch(dialogType){case WYMeditor.DIALOG_LINK:sBodyHtml=this._options.dialogLinkHtml;break;case WYMeditor.DIALOG_IMAGE:sBodyHtml=this._options.dialogImageHtml;break;case WYMeditor.DIALOG_TABLE:sBodyHtml=this._options.dialogTableHtml;break;case WYMeditor.DIALOG_PASTE:sBodyHtml=this._options.dialogPasteHtml;break;case WYMeditor.PREVIEW:sBodyHtml=this._options.dialogPreviewHtml;break;default:sBodyHtml=bodyHtml;break}dialogHtml=this._options.dialogHtml;dialogHtml=h.replaceAll(dialogHtml,WYMeditor.BASE_PATH,this._options.basePath);dialogHtml=h.replaceAll(dialogHtml,WYMeditor.DIRECTION,this._options.direction);dialogHtml=h.replaceAll(dialogHtml,WYMeditor.CSS_PATH,this._options.skinPath+WYMeditor.SKINS_DEFAULT_CSS);dialogHtml=h.replaceAll(dialogHtml,WYMeditor.WYM_PATH,this._options.wymPath);dialogHtml=h.replaceAll(dialogHtml,WYMeditor.JQUERY_PATH,this._options.jQueryPath);dialogHtml=h.replaceAll(dialogHtml,WYMeditor.DIALOG_TITLE,this.encloseString(dialogType));dialogHtml=h.replaceAll(dialogHtml,WYMeditor.DIALOG_BODY,sBodyHtml);dialogHtml=h.replaceAll(dialogHtml,WYMeditor.INDEX,this._index);dialogHtml=this.replaceStrings(dialogHtml);doc=wDialog.document;doc.write(dialogHtml);doc.close()}};WYMeditor.editor.prototype.toggleHtml=function(){jQuery(this._box).find(this._options.htmlSelector).toggle()};WYMeditor.editor.prototype.uniqueStamp=function(){var now=new Date;return"wym-"+now.getTime()};WYMeditor.editor.prototype._handleMultilineBlockContainerPaste=function(wym,$container,range,paragraphStrings){var i,blockSplitter,leftSide,rightSide,rangeNodeComparison,$splitRightParagraph,firstParagraphString,firstParagraphHtml,blockParent,blockParentType;$insertAfter=jQuery(blockParent);blockSplitter="p";if($container.is("li")){blockSplitter="li"}range.splitBoundaries();blockParent=wym.findUp(range.startContainer,["p","h1","h2","h3","h4","h5","h6","li"]);blockParentType=blockParent.tagName;leftSide=[];rightSide=[];jQuery(blockParent).contents().each(function(index,element){rangeNodeComparison=range.compareNode(element);if(rangeNodeComparison===range.NODE_BEFORE||rangeNodeComparison===range.NODE_BEFORE_AND_AFTER&&range.startOffset===range.startContainer.length){leftSide.push(element)}else{rightSide.push(element)}});for(i=0;i0){jQuery(blockParent).prepend(leftSide)}if(rightSide.length>0){$splitRightParagraph=jQuery("<"+blockParentType+">"+"",wym._doc);$splitRightParagraph.insertAfter(jQuery(blockParent));$splitRightParagraph.append(rightSide)}firstParagraphString=paragraphStrings.splice(0,1)[0];firstParagraphHtml=firstParagraphString.split(WYMeditor.NEWLINE).join("
      ");jQuery(blockParent).html(jQuery(blockParent).html()+firstParagraphHtml);$insertAfter=jQuery(blockParent);for(i=0;i"+paragraphStrings[i].split(WYMeditor.NEWLINE).join("
      ")+"";$insertAfter=jQuery(html,wym._doc).insertAfter($insertAfter)}};WYMeditor.editor.prototype.paste=function(str){var container=this.selected(),$container,html="",paragraphs,focusNode,i,l,isSingleLine=false,sel,textNode,wym,range,insertionNodes;wym=this;sel=rangy.getIframeSelection(wym._iframe);range=sel.getRangeAt(0);$container=jQuery(container);range.collapse(true);paragraphStrings=str.split(new RegExp(WYMeditor.NEWLINE+"{2,}","g"));if(paragraphStrings.length===1){isSingleLine=true}if(typeof container==="undefined"||container&&container.tagName.toLowerCase()===WYMeditor.BODY){if(isSingleLine){paragraphs=jQuery("

      "+paragraphStrings[0]+"

      ",this._doc).appendTo(this._doc.body)}else{blockSplitter="p";for(i=paragraphStrings.length-1;i>=0;i-=1){html="<"+blockSplitter+">"+paragraphStrings[i].split(WYMeditor.NEWLINE).join("
      ")+"";insertionNodes=jQuery(html,wym._doc);for(j=insertionNodes.length-1;j>=0;j--){range.insertNode(insertionNodes[j])}}}}else{if(isSingleLine||$container.is("pre")){textNode=this._doc.createTextNode(str);range.insertNode(textNode)}else if($container.is("p,h1,h2,h3,h4,h5,h6,li")){wym._handleMultilineBlockContainerPaste(wym,$container,range,paragraphStrings)}else{textNodesToInsert=str.split(WYMeditor.NEWLINE);for(i=textNodesToInsert.length-1;i>=0;i-=1){textNode=this._doc.createTextNode(textNodesToInsert[i]);range.insertNode(textNode);if(i>0){range.insertNode(jQuery("
      ",wym._doc).get(0))}}}}};WYMeditor.editor.prototype.insert=function(html){var selection=this._iframe.contentWindow.getSelection(),range,node;if(selection.focusNode!==null){range=selection.getRangeAt(0);node=range.createContextualFragment(html);range.deleteContents();range.insertNode(node)}else{this.paste(html)}};WYMeditor.editor.prototype.wrap=function(left,right){this.insert(left+this._iframe.contentWindow.getSelection().toString()+right)};WYMeditor.editor.prototype.unwrap=function(){this.insert(this._iframe.contentWindow.getSelection().toString())};WYMeditor.editor.prototype.setFocusToNode=function(node,toStart){var range=rangy.createRange(this._doc),selection=rangy.getIframeSelection(this._iframe);toStart=toStart||false;range.selectNodeContents(node);range.collapse(toStart);selection.setSingleRange(range)};WYMeditor.editor.prototype.addCssRules=function(doc,aCss){var styles=doc.styleSheets[0],i,oCss;if(styles){for(i=0;i';$liToIndent.before(spacerHtml);$prevLi=$liToIndent.prev()}$prevSublist=$prevLi.contents().last().filter("ol,ul");if($prevSublist.length>0){$prevSublist.append($liToIndent);$sublistContents.detach();$prevLi.append($sublistContents);$firstSublist=$sublistContents.first();wym.joinAdjacentLists($prevSublist.get(0),$firstSublist.get(0))}else{if($sublistContents.length>0){$sublistContents.detach();$prevLi.append($sublistContents);$prevSublist=$sublistContents.first()}else{containerHtml="<"+listType+">";$prevLi.append(containerHtml);$prevSublist=$prevLi.contents().last()}$prevSublist.prepend($liToIndent)}if($liToIndent.next().is(".spacer_li")){$spacer=$liToIndent.next(".spacer_li");$spacerContents=$spacer.contents();$spacerContents.detach();$liToIndent.append($spacerContents);$spacer.remove()}};WYMeditor.editor.prototype._outdentSingleItem=function(listItem){var wym=this,$liToOutdent,listType,spacerListHtml,splitContent,$itemContents,$sublistContents,$parentLi,$parentList,$subsequentSiblingContent,$subsequentParentListSiblingContent,$sublist;$liToOutdent=jQuery(listItem);listType=$liToOutdent.parent()[0].tagName.toLowerCase();if(!$liToOutdent.parent().parent().is("ol,ul,li")){return}if(!$liToOutdent.parent().parent().is("li")){WYMeditor.console.log("Attempting to fix invalid list nesting before outdenting.");wym.correctInvalidListNesting(listItem)}splitContent=wym.splitListItemContents($liToOutdent);$sublistContents=jQuery(splitContent.sublistContents);$itemContents=jQuery(splitContent.itemContents);$parentLi=$liToOutdent.parent().parent("li");if($parentLi.length===0){WYMeditor.console.error("Invalid list. No parentLi found, so aborting outdent");return}$parentList=$liToOutdent.parent();$subsequentSiblingContent=$liToOutdent.nextAllContents();$subsequentParentListSiblingContent=$parentList.nextAllContents();$liToOutdent.detach();$parentLi.after($liToOutdent);if($sublistContents.length>0){spacerListHtml="<"+listType+">"+'
    8. '+"";$liToOutdent.append(spacerListHtml);$sublist=$liToOutdent.children().last();$sublistContents.detach();$sublist.children("li").append($sublistContents)}if($subsequentSiblingContent.length>0){if(typeof $sublist==="undefined"){spacerListHtml="<"+listType+">";$liToOutdent.append(spacerListHtml);$sublist=$liToOutdent.children().last()}$subsequentSiblingContent.detach();$sublist.append($subsequentSiblingContent)}if($subsequentParentListSiblingContent.length>0){$subsequentParentListSiblingContent.detach();if($liToOutdent.contents().length>0&&$liToOutdent.contents().last()[0].nodeType===WYMeditor.NODE.TEXT&&$subsequentParentListSiblingContent[0].nodeType===WYMeditor.NODE.TEXT){$liToOutdent.append("
      ")}$liToOutdent.append($subsequentParentListSiblingContent)}if($parentList.contents().length===0){$parentList.remove()}if($parentLi.contents().length===0){$parentLi.remove()}};WYMeditor.editor.prototype.correctInvalidListNesting=function(listItem,alreadyCorrected){var currentNode=listItem,parentNode,tagName;if(typeof alreadyCorrected==="undefined"){alreadyCorrected=false}if(!currentNode){return alreadyCorrected}while(currentNode.parentNode){parentNode=currentNode.parentNode;if(parentNode.nodeType!==WYMeditor.NODE.ELEMENT){break}tagName=parentNode.tagName.toLowerCase();if(tagName!=="ol"&&tagName!=="ul"&&tagName!=="li"){break}currentNode=parentNode}if(jQuery(currentNode).is("li")){WYMeditor.console.log("Correcting orphaned root li before correcting invalid list nesting.");this._correctOrphanedListItem(currentNode);return this.correctInvalidListNesting(currentNode,true)}if(!jQuery(currentNode).is("ol,ul")){WYMeditor.console.error("Can't correct invalid list nesting. No root list found");return alreadyCorrected}return this._correctInvalidListNesting(currentNode,alreadyCorrected)};WYMeditor.editor.prototype._correctOrphanedListItem=function(listNode){var prevAdjacentLis,nextAdjacentLis,$adjacentLis=jQuery(),prevList,prevNode;prevAdjacentLis=jQuery(listNode).prevContentsUntil(":not(li)");nextAdjacentLis=jQuery(listNode).nextContentsUntil(":not(li)");$adjacentLis=$adjacentLis.add(prevAdjacentLis);$adjacentLis=$adjacentLis.add(listNode);$adjacentLis=$adjacentLis.add(nextAdjacentLis);prevNode=$adjacentLis[0].previousSibling;if(prevNode&&jQuery(prevNode).is("ol,ul")){prevList=prevNode}else{$adjacentLis.before("
        ");prevList=$adjacentLis.prev()[0]}jQuery(prevList).append($adjacentLis)};WYMeditor.editor.prototype._correctInvalidListNesting=function(listNode,alreadyCorrected){var rootNode=listNode,currentNode=listNode,wasCorrected=false,previousSibling,previousLi,$currentNode,tagName,ancestorNode,nodesToMove,targetLi,lastContentNode,spacerHtml='
      1. ';if(typeof alreadyCorrected!=="undefined"){wasCorrected=alreadyCorrected}while(currentNode){if(currentNode._wym_visited){currentNode._wym_visited=false;if(currentNode===rootNode){break}if(currentNode.previousSibling){currentNode=currentNode.previousSibling}else{currentNode=currentNode.parentNode}}else{if(currentNode!==rootNode&&!jQuery(currentNode).is("li")){ancestorNode=currentNode;while(ancestorNode.parentNode){ancestorNode=ancestorNode.parentNode;if(jQuery(ancestorNode).is("li")){break}if(ancestorNode.nodeType!==WYMeditor.NODE.ELEMENT){break}tagName=ancestorNode.tagName.toLowerCase();if(tagName==="ol"||tagName==="ul"){WYMeditor.console.log("Fixing orphaned list content");wasCorrected=true;nodesToMove=[currentNode];previousSibling=currentNode;targetLi=null;while(previousSibling.previousSibling){previousSibling=previousSibling.previousSibling;if(jQuery(previousSibling).is("li")){targetLi=previousSibling;break}nodesToMove.push(previousSibling)}nodesToMove.reverse();if(!targetLi&&nodesToMove.length===1){if(jQuery(currentNode.nextSibling).is("li")){targetLi=currentNode.nextSibling}}if(!targetLi){jQuery(nodesToMove[0]).before(spacerHtml);targetLi=jQuery(nodesToMove[0]).prev()[0]}lastContentNode=jQuery(targetLi).contents().last();if(lastContentNode.length===1&&lastContentNode[0].nodeType===WYMeditor.NODE.TEXT){if(nodesToMove[0].nodeType===WYMeditor.NODE.TEXT){jQuery(targetLi).append("
        ")}}jQuery(targetLi).append(jQuery(nodesToMove));break}}}if(currentNode.lastChild){currentNode._wym_visited=true;currentNode=currentNode.lastChild}else if(currentNode.previousSibling){currentNode=currentNode.previousSibling}else{currentNode=currentNode.parentNode}}}return wasCorrected};WYMeditor.editor.prototype.getCommonParentList=function(listItems){var firstLi,parentList,rootList;listItems=jQuery(listItems).filter("li");if(listItems.length===0){return null}firstLi=listItems[0];parentList=jQuery(firstLi).parent("ol,ul");if(parentList.length===0){return null}rootList=parentList[0];jQuery(listItems).each(function(index,elmnt){parentList=jQuery(elmnt).parent("ol,ul");if(parentList.length===0||parentList[0]!==rootList){return null}});return rootList};WYMeditor.editor.prototype._getSelectedListItems=function(sel){var wym=this,i,range,selectedLi,nodes=[],liNodes=[],containsNodeTextFilter,parentsToAdd,node,$node,$maybeParentLi;containsNodeTextFilter=function(testNode){var fullyContainsNodeText;if(rangy.dom.isCharacterDataNode(testNode)){return testNode}try{fullyContainsNodeText=range.containsNodeText(testNode)}catch(e){return true}if(fullyContainsNodeText===true){return true}};for(i=0;i
      2. ";if(this.findUp(blockElement,WYMeditor.MAIN_CONTAINERS)===blockElement){$blockElement.wrapInner(newListHtml);$newList=$blockElement.children();$newList.unwrap();return $newList.get(0)}$blockElement.wrapInner(newListHtml);$newList=$blockElement.children();return $newList.get(0)};WYMeditor.editor.prototype.insertTable=function(rows,columns,caption,summary){if(rows<=0||columns<=0){return}var table=this._doc.createElement(WYMeditor.TABLE),newRow=null,newCol=null,newCaption,x,y,container,selectedNode;newCaption=table.createCaption();newCaption.innerHTML=caption;for(x=0;x-1){selectedNode=this.selection().focusNode;if(jQuery.inArray(selectedNode.nodeName.toLowerCase(),WYMeditor.SELECTABLE_TABLE_ELEMENTS)>-1||jQuery.inArray(selectedNode.parentNode.nodeName.toLowerCase(),WYMeditor.SELECTABLE_TABLE_ELEMENTS)>-1){while(selectedNode.nodeName.toLowerCase()!==WYMeditor.TABLE){selectedNode=selectedNode.parentNode}}if(selectedNode.nodeName.toLowerCase()===WYMeditor.LI){jQuery(selectedNode).append(table)}else{jQuery(selectedNode).after(table)}}else{jQuery(container).after(table)}this.afterInsertTable(table);this.fixBodyHtml();return table};WYMeditor.editor.prototype.afterInsertTable=function(table){};WYMeditor.editor.prototype.configureEditorUsingRawCss=function(){var CssParser=new WYMeditor.WymCssParser;if(this._options.stylesheet){CssParser.parse(jQuery.ajax({url:this._options.stylesheet,async:false}).responseText)}else{CssParser.parse(this._options.styles,false)}if(this._options.classesItems.length===0){this._options.classesItems=CssParser.css_settings.classesItems}if(this._options.editorStyles.length===0){this._options.editorStyles=CssParser.css_settings.editorStyles}if(this._options.dialogStyles.length===0){this._options.dialogStyles=CssParser.css_settings.dialogStyles}};WYMeditor.editor.prototype.listen=function(){var wym=this;jQuery(this._doc.body).bind("mousedown",function(e){wym.mousedown(e)})};WYMeditor.editor.prototype.mousedown=function(evt){this._selected_image=null;if(evt.target.tagName.toLowerCase()===WYMeditor.IMG){this._selected_image=evt.target}};WYMeditor.loadCss=function(href){var link=document.createElement("link"),head;link.rel="stylesheet";link.href=href;head=jQuery("head").get(0);head.appendChild(link)};WYMeditor.editor.prototype.loadSkin=function(){if(this._options.loadSkin&&!WYMeditor.SKINS[this._options.skin]){var found=false,rExp=new RegExp(this._options.skin+"/"+WYMeditor.SKINS_DEFAULT_CSS+"$");jQuery("link").each(function(){if(this.href.match(rExp)){found=true}});if(!found){WYMeditor.loadCss(this._options.skinPath+WYMeditor.SKINS_DEFAULT_CSS)}}jQuery(this._box).addClass("wym_skin_"+this._options.skin);if(this._options.initSkin&&!WYMeditor.SKINS[this._options.skin]){eval(jQuery.ajax({url:this._options.skinPath+WYMeditor.SKINS_DEFAULT_JS,async:false}).responseText)}if(WYMeditor.SKINS[this._options.skin]&&WYMeditor.SKINS[this._options.skin].init){WYMeditor.SKINS[this._options.skin].init(this)}};WYMeditor.DocumentStructureManager=function(wym,defaultRootContainer){this._wym=wym;this.structureRules=WYMeditor.DocumentStructureManager.DEFAULTS;this.setDefaultRootContainer(defaultRootContainer)};jQuery.extend(WYMeditor.DocumentStructureManager,{VALID_DEFAULT_ROOT_CONTAINERS:["p","div"],DEFAULT_ROOT_CONTAINER_TITLES:{p:"Paragraph",div:"Division"},CONTAINERS_BLOCKING_NAVIGATION:["table","blockquote","pre"],DEFAULTS:{defaultRootContainer:"p",notValidRootContainers:["div"],validRootContainers:["p","div","h1","h2","h3","h4","h5","h6","pre","blockquote","table","ol","ul"],convertIfRootContainers:["div"],validListConversionTargetContainers:["p","div","h1","h2","h3","h4","h5","h6","pre","blockquote","td","th"],wrapContentsInList:["td","th"]}}); +WYMeditor.DocumentStructureManager.prototype.setDefaultRootContainer=function(defaultRootContainer){var validContainers,index;if(this.structureRules.defaultRootContainer===defaultRootContainer){return}validContainers=WYMeditor.DocumentStructureManager.VALID_DEFAULT_ROOT_CONTAINERS;index=jQuery.inArray(defaultRootContainer,validContainers);if(index===-1){throw new Error("a defaultRootContainer of '"+defaultRootContainer+"' is not supported")}this.structureRules.defaultRootContainer=defaultRootContainer;this.structureRules.notValidRootContainers=WYMeditor.DocumentStructureManager.VALID_DEFAULT_ROOT_CONTAINERS;this.structureRules.notValidRootContainers.splice(index,1);this.adjustDefaultRootContainerUI()};WYMeditor.DocumentStructureManager.prototype.adjustDefaultRootContainerUI=function(){var wym=this._wym,defaultRootContainer=this.structureRules.defaultRootContainer,$containerItems,$containerLink,$newContainerItem,containerName,newContainerLinkNeeded,newContainerLinkHtml,i;$containerItems=jQuery(wym._box).find(wym._options.containersSelector).find("li");newContainerLinkNeeded=true;for(i=0;i<$containerItems.length;++i){$containerLink=$containerItems.eq(i).find("a");containerName=$containerLink.attr("name").toLowerCase();if(jQuery.inArray(containerName,this.structureRules.notValidRootContainers)>-1){$containerItems.eq(i).remove()}if(containerName===defaultRootContainer){newContainerLinkNeeded=false}}if(newContainerLinkNeeded){newContainerLinkHtml=wym._options.containersItemHtml;newContainerLinkHtml=WYMeditor.Helper.replaceAll(newContainerLinkHtml,WYMeditor.CONTAINER_NAME,defaultRootContainer.toUpperCase());newContainerLinkHtml=WYMeditor.Helper.replaceAll(newContainerLinkHtml,WYMeditor.CONTAINER_TITLE,WYMeditor.DocumentStructureManager.DEFAULT_ROOT_CONTAINER_TITLES[defaultRootContainer]);newContainerLinkHtml=WYMeditor.Helper.replaceAll(newContainerLinkHtml,WYMeditor.CONTAINER_CLASS,"wym_containers_"+defaultRootContainer);$newContainerItem=jQuery(newContainerLinkHtml);$containerItems=jQuery(wym._box).find(wym._options.containersSelector).find("li");$containerItems.eq(0).before($newContainerItem);$newContainerItem.find("a").click(function(){wym.container(jQuery(this).attr(WYMeditor.NAME));return false})}};WYMeditor.WymClassExplorer=function(wym){this._wym=wym;this._class="className"};WYMeditor.WymClassExplorer.prototype.initIframe=function(iframe){this._iframe=iframe;this._doc=iframe.contentWindow.document;var styles=this._doc.styleSheets[0];var aCss=eval(this._options.editorStyles);this.addCssRules(this._doc,aCss);this._doc.title=this._wym._index;jQuery("html",this._doc).attr("dir",this._options.direction);jQuery(this._doc.body).html(this._wym._options.html);var wym=this;this._doc.body.onfocus=function(){wym._doc.designMode="on";wym._doc=iframe.contentWindow.document};this._doc.onbeforedeactivate=function(){wym.saveCaret()};jQuery(this._doc).bind("keyup",wym.keyup);var ieVersion=parseInt(jQuery.browser.version,10);if(ieVersion>=8&&ieVersion<9){jQuery(this._doc).bind("keydown",function(){wym.fixBluescreenOfDeath()})}this._doc.onkeyup=function(){wym.saveCaret()};this._doc.onclick=function(){wym.saveCaret()};this._doc.body.onbeforepaste=function(){wym._iframe.contentWindow.event.returnValue=false};this._doc.body.onpaste=function(){wym._iframe.contentWindow.event.returnValue=false;wym.paste(window.clipboardData.getData("Text"))};if(this._initialized){if(jQuery.isFunction(this._options.preBind)){this._options.preBind(this)}this._wym.bindEvents();if(jQuery.isFunction(this._options.postInit)){this._options.postInit(this)}this.listen()}this._initialized=true;this._doc.designMode="on";try{this._doc=iframe.contentWindow.document}catch(e){}};!function(editorLoadSkin){WYMeditor.WymClassExplorer.prototype.loadSkin=function(){jQuery(this._box).find(this._options.containerSelector).attr("unselectable","on");editorLoadSkin.call(this)}}(WYMeditor.editor.prototype.loadSkin);WYMeditor.WymClassExplorer.prototype.fixBluescreenOfDeath=function(){var position=jQuery(this._doc).find("p").eq(0).position();if(position!==null&&typeof position!=="undefined"&&position.left<0){jQuery(this._box).append('
        ');jQuery(this._box).find("#wym-bluescreen-bug-fix").remove()}};WYMeditor.WymClassExplorer.prototype._exec=function(cmd,param){if(param){this._doc.execCommand(cmd,false,param)}else{this._doc.execCommand(cmd)}};WYMeditor.WymClassExplorer.prototype.saveCaret=function(){this._doc.caretPos=this._doc.selection.createRange()};WYMeditor.WymClassExplorer.prototype.addCssRule=function(styles,oCss){var selectors=oCss.name.split(","),i;for(i=0;i");selection=rangy.getIframeSelection(wym._iframe);range=rangy.createRange(wym._doc);range.selectNodeContents($wrappedNode[0]);range.collapse();selection.setSingleRange(range)};WYMeditor.WymClassExplorer.prototype.unwrap=function(){var range=this._doc.selection.createRange();if(jQuery(range.parentElement()).parents().is(this._options.iframeBodySelector)){try{var text=range.text;this._exec("Cut");range.pasteHTML(text)}catch(e){}}};WYMeditor.WymClassExplorer.prototype.keyup=function(evt){var wym=WYMeditor.INSTANCES[this.title],container,defaultRootContainer,notValidRootContainers,name,parentName,forbiddenMainContainer=false;notValidRootContainers=wym.documentStructureManager.structureRules.notValidRootContainers;defaultRootContainer=wym.documentStructureManager.structureRules.defaultRootContainer;this._selected_image=null;if(!wym.keyCanCreateBlockElement(evt.which)&&evt.which!==WYMeditor.KEY.CTRL&&evt.which!==WYMeditor.KEY.COMMAND&&!evt.metaKey&&!evt.ctrlKey){container=wym.selected();selectedNode=wym.selection().focusNode;if(container!==null){name=container.tagName.toLowerCase()}if(container.parentNode){parentName=container.parentNode.tagName.toLowerCase()}if(wym.isForbiddenMainContainer(name)){name=parentName;forbiddenMainContainer=true}if(name===WYMeditor.BODY&&selectedNode.nodeName==="#text"){if(forbiddenMainContainer){selectedNode=selectedNode.parentNode}wym.wrapWithContainer(selectedNode,defaultRootContainer);wym.fixBodyHtml()}if(jQuery.inArray(name,notValidRootContainers)>-1&&parentName===WYMeditor.BODY){wym.switchTo(container,defaultRootContainer);wym.fixBodyHtml()}}if(wym.keyCanCreateBlockElement(evt.which)){container=wym.selected();name=container.tagName.toLowerCase();if(container.parentNode){parentName=container.parentNode.tagName.toLowerCase()}if(jQuery.inArray(name,notValidRootContainers)>-1&&parentName===WYMeditor.BODY){wym.switchTo(container,defaultRootContainer)}wym.fixBodyHtml()}};WYMeditor.WymClassMozilla=function(wym){this._wym=wym;this._class="class"};WYMeditor.WymClassMozilla.CELL_PLACEHOLDER='
        ';WYMeditor.WymClassMozilla.NEEDS_CELL_FIX=parseInt(jQuery.browser.version,10)===1&&jQuery.browser.version>="1.9.1"&&jQuery.browser.version<"2.0";WYMeditor.WymClassMozilla.prototype.initIframe=function(iframe){var wym=this,styles,aCss;this._iframe=iframe;this._doc=iframe.contentDocument;styles=this._doc.styleSheets[0];aCss=eval(this._options.editorStyles);this.addCssRules(this._doc,aCss);this._doc.title=this._wym._index;jQuery("html",this._doc).attr("dir",this._options.direction);this._html(this._wym._options.html);this.enableDesignMode();if(jQuery.isFunction(this._options.preBind)){this._options.preBind(this)}this._wym.bindEvents();jQuery(this._doc).bind("keydown",this.keydown);jQuery(this._doc).bind("keyup",this.keyup);jQuery(this._doc).bind("click",this.click);jQuery(this._doc).bind("focus",function(){wym.enableDesignMode.call(wym)});if(jQuery.isFunction(this._options.postInit)){this._options.postInit(this)}this.listen()};WYMeditor.WymClassMozilla.prototype._html=function(html){if(typeof html==="string"){try{this._doc.designMode="off"}catch(e){}html=html.replace(/]*)>/gi,"");html=html.replace(/<\/em>/gi,"");html=html.replace(/]*)>/gi,"");html=html.replace(/<\/strong>/gi,"");jQuery(this._doc.body).html(html);this._wym.fixBodyHtml();this.enableDesignMode()}else{return jQuery(this._doc.body).html()}return false};WYMeditor.WymClassMozilla.prototype._exec=function(cmd,param){if(!this.selected()){return false}if(param){this._doc.execCommand(cmd,"",param)}else{this._doc.execCommand(cmd,"",null)}var container=this.selected();if(container&&container.tagName.toLowerCase()===WYMeditor.BODY){this._exec(WYMeditor.FORMAT_BLOCK,WYMeditor.P);this.fixBodyHtml()}return true};WYMeditor.WymClassMozilla.prototype.addCssRule=function(styles,oCss){styles.insertRule(oCss.name+" {"+oCss.css+"}",styles.cssRules.length)};WYMeditor.WymClassMozilla.prototype.keydown=function(evt){var wym=WYMeditor.INSTANCES[this.title];if(evt.ctrlKey){if(evt.which===66){wym._exec(WYMeditor.BOLD);return false}if(evt.which===73){wym._exec(WYMeditor.ITALIC);return false}}return true};WYMeditor.WymClassMozilla.prototype.keyup=function(evt){var wym=WYMeditor.INSTANCES[this.title],container,defaultRootContainer,notValidRootContainers,name,parentName;notValidRootContainers=wym.documentStructureManager.structureRules.notValidRootContainers;defaultRootContainer=wym.documentStructureManager.structureRules.defaultRootContainer;wym._selected_image=null;container=null;if(!wym.keyCanCreateBlockElement(evt.which)&&evt.which!==WYMeditor.KEY.CTRL&&evt.which!==WYMeditor.KEY.COMMAND&&!evt.metaKey&&!evt.ctrlKey){container=wym.selected();name=container.tagName.toLowerCase();if(container.parentNode){parentName=container.parentNode.tagName.toLowerCase()}if(wym.isForbiddenMainContainer(name)){name=parentName}if(name===WYMeditor.BODY||jQuery.inArray(name,notValidRootContainers)>-1&&parentName===WYMeditor.BODY){wym._exec(WYMeditor.FORMAT_BLOCK,defaultRootContainer);wym.fixBodyHtml()}}if(wym.keyCanCreateBlockElement(evt.which)){container=wym.selected();name=container.tagName.toLowerCase();if(container.parentNode){parentName=container.parentNode.tagName.toLowerCase()}if(jQuery.inArray(name,notValidRootContainers)>-1&&parentName===WYMeditor.BODY){wym._exec(WYMeditor.FORMAT_BLOCK,defaultRootContainer)}wym.fixBodyHtml()}};WYMeditor.WymClassMozilla.prototype.click=function(evt){var wym=WYMeditor.INSTANCES[this.title],container=wym.selected(),sel;if(WYMeditor.WymClassMozilla.NEEDS_CELL_FIX===true){if(container&&container.tagName.toLowerCase()===WYMeditor.TR){jQuery(WYMeditor.TD,wym._doc.body).append(WYMeditor.WymClassMozilla.CELL_PLACEHOLDER)}}if(container&&container.tagName.toLowerCase()===WYMeditor.BODY){sel=wym._iframe.contentWindow.getSelection();if(sel.isCollapsed===true){wym._exec(WYMeditor.FORMAT_BLOCK,WYMeditor.P)}}};WYMeditor.WymClassMozilla.prototype.enableDesignMode=function(){if(this._doc.designMode==="off"){try{this._doc.designMode="on";this._doc.execCommand("styleWithCSS","",false);this._doc.execCommand("enableObjectResizing",false,true)}catch(e){}}};WYMeditor.WymClassMozilla.prototype.afterInsertTable=function(table){if(WYMeditor.WymClassMozilla.NEEDS_CELL_FIX===true){jQuery(table).find("td").each(function(index,element){jQuery(element).append(WYMeditor.WymClassMozilla.CELL_PLACEHOLDER)})}};WYMeditor.WymClassOpera=function(wym){this._wym=wym;this._class="class"};WYMeditor.WymClassOpera.prototype.initIframe=function(iframe){this._iframe=iframe;this._doc=iframe.contentWindow.document;var styles=this._doc.styleSheets[0];var aCss=eval(this._options.editorStyles);this.addCssRules(this._doc,aCss);this._doc.title=this._wym._index;jQuery("html",this._doc).attr("dir",this._options.direction);this._doc.designMode="on";this._html(this._wym._options.html);if(jQuery.isFunction(this._options.preBind)){this._options.preBind(this)}this._wym.bindEvents();jQuery(this._doc).bind("keydown",this.keydown);jQuery(this._doc).bind("keyup",this.keyup);if(jQuery.isFunction(this._options.postInit)){this._options.postInit(this)}this.listen()};WYMeditor.WymClassOpera.prototype._exec=function(cmd,param){if(param){this._doc.execCommand(cmd,false,param)}else{this._doc.execCommand(cmd)}};WYMeditor.WymClassOpera.prototype.selected=function(){var sel=this._iframe.contentWindow.getSelection();var node=sel.focusNode;if(node){if(node.nodeName==="#text"){return node.parentNode}else{return node}}else{return null}};WYMeditor.WymClassOpera.prototype.addCssRule=function(styles,oCss){styles.insertRule(oCss.name+" {"+oCss.css+"}",styles.cssRules.length)};WYMeditor.WymClassOpera.prototype.keydown=function(evt){var wym=WYMeditor.INSTANCES[this.title];var sel=wym._iframe.contentWindow.getSelection();startNode=sel.getRangeAt(0).startContainer;if(!jQuery(startNode).parentsOrSelf(WYMeditor.MAIN_CONTAINERS.join(","))[0]&&!jQuery(startNode).parentsOrSelf("li")&&!keyCanCreateBlockElement(evt.which)){wym._exec(WYMeditor.FORMAT_BLOCK,WYMeditor.P)}};WYMeditor.WymClassOpera.prototype.keyup=function(evt){var wym=WYMeditor.INSTANCES[this.title];wym._selected_image=null};WYMeditor.WymClassSafari=function(wym){this._wym=wym;this._class="class"};WYMeditor.WymClassSafari.prototype.initIframe=function(iframe){var wym=this,styles,aCss;this._iframe=iframe;this._doc=iframe.contentDocument;styles=this._doc.styleSheets[0];aCss=eval(this._options.editorStyles);this.addCssRules(this._doc,aCss);this._doc.title=this._wym._index;jQuery("html",this._doc).attr("dir",this._options.direction);this._doc.designMode="on";this._html(this._wym._options.html);if(jQuery.isFunction(this._options.preBind)){this._options.preBind(this)}this._wym.bindEvents();jQuery(this._doc).bind("keydown",this.keydown);jQuery(this._doc).bind("keyup",this.keyup);if(jQuery.isFunction(this._options.postInit)){this._options.postInit(this)}this.listen()};WYMeditor.WymClassSafari.prototype._exec=function(cmd,param){if(!this.selected()){return false}var container,$container,tagName;if(param){this._doc.execCommand(cmd,"",param)}else{this._doc.execCommand(cmd,"",null)}container=this.selected();if(container){$container=jQuery(container);tagName=container.tagName.toLowerCase();if(tagName===WYMeditor.BODY){this._exec(WYMeditor.FORMAT_BLOCK,this.documentStructureManager.structureRules.defaultRootContainer);this.fixBodyHtml()}if(cmd===WYMeditor.FORMAT_BLOCK&&$container.siblings("body.wym_iframe").length){$container.siblings("body.wym_iframe").append(container)}if(tagName==="span"&&(!$container.attr("class")||$container.attr("class").toLowerCase()==="apple-style-span")&&$container.attr("style")==="font-weight: normal;"){$container.contents().unwrap()}}return true};WYMeditor.WymClassSafari.prototype.addCssRule=function(styles,oCss){styles.insertRule(oCss.name+" {"+oCss.css+"}",styles.cssRules.length)};WYMeditor.WymClassSafari.prototype.keydown=function(e){var wym=WYMeditor.INSTANCES[this.title];if(e.ctrlKey){if(e.which===WYMeditor.KEY.B){wym._exec(WYMeditor.BOLD);e.preventDefault()}if(e.which===WYMeditor.KEY.I){wym._exec(WYMeditor.ITALIC);e.preventDefault()}}else if(e.shiftKey&&e.which===WYMeditor.KEY.ENTER){wym._exec("InsertLineBreak");e.preventDefault()}};WYMeditor.WymClassSafari.prototype.keyup=function(evt){var wym=WYMeditor.INSTANCES[this.title],container,defaultRootContainer,notValidRootContainers,name,parentName;notValidRootContainers=wym.documentStructureManager.structureRules.notValidRootContainers;defaultRootContainer=wym.documentStructureManager.structureRules.defaultRootContainer;wym._selected_image=null;if(jQuery.browser.version<534.1){if(evt.which===WYMeditor.KEY.ENTER&&evt.shiftKey){wym._exec("InsertLineBreak")}}if(!wym.keyCanCreateBlockElement(evt.which)&&evt.which!==WYMeditor.KEY.CTRL&&evt.which!==WYMeditor.KEY.COMMAND&&!evt.metaKey&&!evt.ctrlKey){container=wym.selected();name=container.tagName.toLowerCase();if(container.parentNode){parentName=container.parentNode.tagName.toLowerCase()}if(wym.isForbiddenMainContainer(name)){name=parentName}if(name===WYMeditor.BODY||jQuery.inArray(name,notValidRootContainers)>-1&&parentName===WYMeditor.BODY){wym._exec(WYMeditor.FORMAT_BLOCK,defaultRootContainer);wym.fixBodyHtml()}}if(wym.keyCanCreateBlockElement(evt.which)){container=wym.selected();name=container.tagName.toLowerCase();if(container.parentNode){parentName=container.parentNode.tagName.toLowerCase()}if(jQuery.inArray(name,notValidRootContainers)>-1&&parentName===WYMeditor.BODY){wym._exec(WYMeditor.FORMAT_BLOCK,defaultRootContainer)}wym.fixBodyHtml()}};WYMeditor.XmlHelper=function(){this._entitiesDiv=document.createElement("div");return this};WYMeditor.XmlHelper.prototype.tag=function(name,options,open){options=options||false;open=open||false;return"<"+name+(options?this.tagOptions(options):"")+(open?">":" />")};WYMeditor.XmlHelper.prototype.contentTag=function(name,content,options){options=options||false;return"<"+name+(options?this.tagOptions(options):"")+">"+content+""};WYMeditor.XmlHelper.prototype.cdataSection=function(content){return""};WYMeditor.XmlHelper.prototype.escapeOnce=function(xml){return this._fixDoubleEscape(this.escapeEntities(xml))};WYMeditor.XmlHelper.prototype._fixDoubleEscape=function(escaped){return escaped.replace(/&([a-z]+|(#\d+));/gi,"&$1;")};WYMeditor.XmlHelper.prototype.tagOptions=function(options){var xml=this;xml._formated_options="";for(var key in options){var formated_options="";var value=options[key];if(typeof value!="function"&&value.length>0){if(parseInt(key,10)==key&&typeof value=="object"){key=value.shift();value=value.pop()}if(key!==""&&value!==""){xml._formated_options+=" "+key+'="'+xml.escapeOnce(value)+'"'}}}return xml._formated_options};WYMeditor.XmlHelper.prototype.escapeEntities=function(string,escape_quotes){this._entitiesDiv.innerHTML=string;this._entitiesDiv.textContent=string;var result=this._entitiesDiv.innerHTML;if(typeof escape_quotes=="undefined"){if(escape_quotes!==false)result=result.replace('"',""");if(escape_quotes===true)result=result.replace('"',"'")}return result};WYMeditor.XmlHelper.prototype.parseAttributes=function(tag_attributes){var result=[];var matches=tag_attributes.split(/((=\s*")(")("))|((=\s*\')(\')(\'))|((=\s*[^>\s]*))/g);if(matches.toString()!=tag_attributes){for(var k in matches){var v=matches[k];if(typeof v!="function"&&v.length!==0){var re=new RegExp("(\\w+)\\s*"+v);var match=tag_attributes.match(re);if(match){var value=v.replace(/^[\s=]+/,"");var delimiter=value.charAt(0);delimiter=delimiter=='"'?'"':delimiter=="'"?"'":"";if(delimiter!==""){value=delimiter=='"'?value.replace(/^"|"+$/g,""):value.replace(/^'|'+$/g,"")}tag_attributes=tag_attributes.replace(match[0],"");result.push([match[1],value])}}}}return result};WYMeditor.XhtmlValidator={_attributes:{core:{except:["base","head","html","meta","param","script","style","title"],attributes:["class","id","style","title","accesskey","tabindex","/^data-.*/"]},language:{except:["base","br","hr","iframe","param","script"],attributes:{dir:["ltr","rtl"],0:"lang",1:"xml:lang"}},keyboard:{attributes:{accesskey:/^(\w){1}$/,tabindex:/^(\d)+$/}}},_events:{window:{only:["body"],attributes:["onload","onunload"]},form:{only:["form","input","textarea","select","a","label","button"],attributes:["onchange","onsubmit","onreset","onselect","onblur","onfocus"]},keyboard:{except:["base","bdo","br","frame","frameset","head","html","iframe","meta","param","script","style","title"],attributes:["onkeydown","onkeypress","onkeyup"]},mouse:{except:["base","bdo","br","head","html","meta","param","script","style","title"],attributes:["onclick","ondblclick","onmousedown","onmousemove","onmouseover","onmouseout","onmouseup"]}},_tags:{a:{attributes:{0:"charset",1:"coords",2:"href",3:"hreflang",4:"name",5:"rel",6:"rev",shape:/^(rect|rectangle|circ|circle|poly|polygon)$/,7:"type"}},0:"abbr",1:"acronym",2:"address",area:{attributes:{0:"alt",1:"coords",2:"href",nohref:/^(true|false)$/,shape:/^(rect|rectangle|circ|circle|poly|polygon)$/},required:["alt"]},3:"b",base:{attributes:["href"],required:["href"]},bdo:{attributes:{dir:/^(ltr|rtl)$/},required:["dir"]},4:"big",blockquote:{attributes:["cite"]},5:"body",6:"br",button:{attributes:{disabled:/^(disabled)$/,type:/^(button|reset|submit)$/,0:"value"},inside:"form"},7:"caption",8:"cite",9:"code",col:{attributes:{align:/^(right|left|center|justify)$/,0:"char",1:"charoff",span:/^(\d)+$/,valign:/^(top|middle|bottom|baseline)$/,2:"width"},inside:"colgroup"},colgroup:{attributes:{align:/^(right|left|center|justify)$/,0:"char",1:"charoff",span:/^(\d)+$/,valign:/^(top|middle|bottom|baseline)$/,2:"width"}},10:"dd",del:{attributes:{0:"cite",datetime:/^([0-9]){8}/}},11:"div",12:"dfn",13:"dl",14:"dt",15:"em",fieldset:{inside:"form"},form:{attributes:{0:"action",1:"accept",2:"accept-charset",3:"enctype",method:/^(get|post)$/},required:["action"]},head:{attributes:["profile"]},16:"h1",17:"h2",18:"h3",19:"h4",20:"h5",21:"h6",22:"hr",html:{attributes:["xmlns"]},23:"i",img:{attributes:["alt","src","height","ismap","longdesc","usemap","width"],required:["alt","src"]},input:{attributes:{0:"accept",1:"alt",checked:/^(checked)$/,disabled:/^(disabled)$/,maxlength:/^(\d)+$/,2:"name",readonly:/^(readonly)$/,size:/^(\d)+$/,3:"src",type:/^(button|checkbox|file|hidden|image|password|radio|reset|submit|text)$/,4:"value"},inside:"form"},ins:{attributes:{0:"cite",datetime:/^([0-9]){8}/}},24:"kbd",label:{attributes:["for"],inside:"form"},25:"legend",26:"li",link:{attributes:{0:"charset",1:"href",2:"hreflang",media:/^(all|braille|print|projection|screen|speech|,|;| )+$/i,rel:/^(alternate|appendix|bookmark|chapter|contents|copyright|glossary|help|home|index|next|prev|section|start|stylesheet|subsection| |shortcut|icon)+$/i,rev:/^(alternate|appendix|bookmark|chapter|contents|copyright|glossary|help|home|index|next|prev|section|start|stylesheet|subsection| |shortcut|icon)+$/i,3:"type"},inside:"head"},map:{attributes:["id","name"],required:["id"]},meta:{attributes:{0:"content","http-equiv":/^(content\-type|expires|refresh|set\-cookie)$/i,1:"name",2:"scheme"},required:["content"]},27:"noscript",object:{attributes:["archive","classid","codebase","codetype","data","declare","height","name","standby","type","usemap","width"]},28:"ol",optgroup:{attributes:{0:"label",disabled:/^(disabled)$/},required:["label"]},option:{attributes:{0:"label",disabled:/^(disabled)$/,selected:/^(selected)$/,1:"value"},inside:"select"},29:"p",param:{attributes:{0:"type",valuetype:/^(data|ref|object)$/,1:"valuetype",2:"value"},required:["name"]},30:"pre",q:{attributes:["cite"]},31:"samp",script:{attributes:{type:/^(text\/ecmascript|text\/javascript|text\/jscript|text\/vbscript|text\/vbs|text\/xml)$/,0:"charset",defer:/^(defer)$/,1:"src"},required:["type"]},select:{attributes:{disabled:/^(disabled)$/,multiple:/^(multiple)$/,0:"name",1:"size"},inside:"form"},32:"small",33:"span",34:"strong",style:{attributes:{0:"type",media:/^(screen|tty|tv|projection|handheld|print|braille|aural|all)$/},required:["type"]},35:"sub",36:"sup",table:{attributes:{0:"border",1:"cellpadding",2:"cellspacing",frame:/^(void|above|below|hsides|lhs|rhs|vsides|box|border)$/,rules:/^(none|groups|rows|cols|all)$/,3:"summary",4:"width"}},tbody:{attributes:{align:/^(right|left|center|justify)$/,0:"char",1:"charoff",valign:/^(top|middle|bottom|baseline)$/}},td:{attributes:{0:"abbr",align:/^(left|right|center|justify|char)$/,1:"axis",2:"char",3:"charoff",colspan:/^(\d)+$/,4:"headers",rowspan:/^(\d)+$/,scope:/^(col|colgroup|row|rowgroup)$/,valign:/^(top|middle|bottom|baseline)$/}},textarea:{attributes:["cols","rows","disabled","name","readonly"],required:["cols","rows"],inside:"form"},tfoot:{attributes:{align:/^(right|left|center|justify)$/,0:"char",1:"charoff",valign:/^(top|middle|bottom)$/,2:"baseline"}},th:{attributes:{0:"abbr",align:/^(left|right|center|justify|char)$/,1:"axis",2:"char",3:"charoff",colspan:/^(\d)+$/,4:"headers",rowspan:/^(\d)+$/,scope:/^(col|colgroup|row|rowgroup)$/,valign:/^(top|middle|bottom|baseline)$/}},thead:{attributes:{align:/^(right|left|center|justify)$/,0:"char",1:"charoff",valign:/^(top|middle|bottom|baseline)$/}},37:"title",tr:{attributes:{align:/^(right|left|center|justify|char)$/,0:"char",1:"charoff",valign:/^(top|middle|bottom|baseline)$/}},38:"tt",39:"ul",40:"var"},skiped_attributes:[],skiped_attribute_values:[],getValidTagAttributes:function(tag,attributes){var valid_attributes={};var possible_attributes=this.getPossibleTagAttributes(tag);for(var attribute in attributes){var value=attributes[attribute];attribute=attribute.toLowerCase();var h=WYMeditor.Helper;if(!h.contains(this.skiped_attributes,attribute)&&!h.contains(this.skiped_attribute_values,value)){if(typeof value!="function"&&h.contains(possible_attributes,attribute)){if(this.doesAttributeNeedsValidation(tag,attribute)){if(this.validateAttribute(tag,attribute,value)){valid_attributes[attribute]=value}}else{valid_attributes[attribute]=value}}else{jQuery.each(possible_attributes,function(){if(this.match(/\/(.*)\//)){regex=new RegExp(this.match(/\/(.*)\//)[1]);if(regex.test(attribute)){valid_attributes[attribute]=value}}})}}}return valid_attributes},getUniqueAttributesAndEventsForTag:function(tag){var result=[];if(this._tags[tag]&&this._tags[tag].attributes){for(var k in this._tags[tag].attributes){result.push(parseInt(k,10)==k?this._tags[tag].attributes[k]:k)}}return result},getDefaultAttributesAndEventsForTags:function(){var result=[];for(var key in this._events){result.push(this._events[key])}for(key in this._attributes){result.push(this._attributes[key])}return result},isValidTag:function(tag){if(this._tags[tag]){return true}for(var key in this._tags){if(this._tags[key]==tag){return true}}return false},getDefaultAttributesAndEventsForTag:function(tag){var default_attributes=[];if(this.isValidTag(tag)){var default_attributes_and_events=this.getDefaultAttributesAndEventsForTags();for(var key in default_attributes_and_events){var defaults=default_attributes_and_events[key];if(typeof defaults=="object"){var h=WYMeditor.Helper;if(defaults["except"]&&h.contains(defaults["except"],tag)||defaults["only"]&&!h.contains(defaults["only"],tag)){continue}var tag_defaults=defaults["attributes"]?defaults["attributes"]:defaults["events"];for(var k in tag_defaults){default_attributes.push(typeof tag_defaults[k]!="string"?k:tag_defaults[k])}}}}return default_attributes},doesAttributeNeedsValidation:function(tag,attribute){return this._tags[tag]&&(this._tags[tag]["attributes"]&&this._tags[tag]["attributes"][attribute]||this._tags[tag]["required"]&&WYMeditor.Helper.contains(this._tags[tag]["required"],attribute))},validateAttribute:function(tag,attribute,value){if(this._tags[tag]&&this._tags[tag]["attributes"]&&this._tags[tag]["attributes"][attribute]&&value.length>0&&!value.match(this._tags[tag]["attributes"][attribute])||this._tags[tag]&&this._tags[tag]["required"]&&WYMeditor.Helper.contains(this._tags[tag]["required"],attribute)&&value.length===0){return false}return typeof this._tags[tag]!="undefined"},getPossibleTagAttributes:function(tag){if(!this._possible_tag_attributes){this._possible_tag_attributes={}}if(!this._possible_tag_attributes[tag]){this._possible_tag_attributes[tag]=this.getUniqueAttributesAndEventsForTag(tag).concat(this.getDefaultAttributesAndEventsForTag(tag))}return this._possible_tag_attributes[tag]}};WYMeditor.ParallelRegex=function(case_sensitive){this._case=case_sensitive;this._patterns=[];this._labels=[];this._regex=null;return this};WYMeditor.ParallelRegex.prototype.addPattern=function(pattern,label){label=label||true;var count=this._patterns.length;this._patterns[count]=pattern;this._labels[count]=label;this._regex=null};WYMeditor.ParallelRegex.prototype.match=function(subject){if(this._patterns.length===0){return[false,""]}var matches=subject.match(this._getCompoundedRegex());if(!matches){return[false,""]}var match=matches[0];for(var i=1;i","Comment")};WYMeditor.XhtmlLexer.prototype.addScriptTokens=function(scope){this.addEntryPattern("","Script")};WYMeditor.XhtmlLexer.prototype.addCssTokens=function(scope){this.addEntryPattern("","Css")};WYMeditor.XhtmlLexer.prototype.addTagTokens=function(scope){this.addSpecialPattern("<\\s*[a-z0-9:-]+\\s*/>",scope,"SelfClosingTag");this.addSpecialPattern("<\\s*[a-z0-9:-]+\\s*>",scope,"OpeningTag");this.addEntryPattern("<[a-z0-9:-]+"+"[\\/ \\>]+",scope,"OpeningTag");this.addInTagDeclarationTokens("OpeningTag");this.addSpecialPattern("",scope,"ClosingTag")};WYMeditor.XhtmlLexer.prototype.addInTagDeclarationTokens=function(scope){this.addSpecialPattern("\\s+",scope,"Ignore");this.addAttributeTokens(scope);this.addExitPattern("/>",scope);this.addExitPattern(">",scope)};WYMeditor.XhtmlLexer.prototype.addAttributeTokens=function(scope){this.addSpecialPattern("\\s*[a-z-_0-9]*:?[a-z-_0-9]+\\s*(?==)\\s*",scope,"TagAttributes");this.addEntryPattern('=\\s*"',scope,"DoubleQuotedAttribute");this.addPattern('\\\\"',"DoubleQuotedAttribute");this.addExitPattern('"',"DoubleQuotedAttribute");this.addEntryPattern("=\\s*'",scope,"SingleQuotedAttribute");this.addPattern("\\\\'","SingleQuotedAttribute");this.addExitPattern("'","SingleQuotedAttribute");this.addSpecialPattern("=\\s*[^>\\s]*",scope,"UnquotedAttribute")};WYMeditor.XhtmlParser=function(Listener,mode){mode=mode||"Text";this._Lexer=new WYMeditor.XhtmlLexer(this);this._Listener=Listener;this._mode=mode;this._matches=[];this._last_match="";this._current_match="";return this};WYMeditor.XhtmlParser.prototype.parse=function(raw){this._Lexer.parse(this.beforeParsing(raw));return this.afterParsing(this._Listener.getResult())};WYMeditor.XhtmlParser.prototype.beforeParsing=function(raw){if(raw.match(/class="MsoNormal"/)||raw.match(/ns = "urn:schemas-microsoft-com/)){this._Listener.avoidStylingTagsAndAttributes()}return this._Listener.beforeParsing(raw)};WYMeditor.XhtmlParser.prototype.afterParsing=function(parsed){if(this._Listener._avoiding_tags_implicitly){this._Listener.allowStylingTagsAndAttributes()}return this._Listener.afterParsing(parsed)};WYMeditor.XhtmlParser.prototype.Ignore=function(match,state){return true};WYMeditor.XhtmlParser.prototype.Text=function(text){this._Listener.addContent(text);return true};WYMeditor.XhtmlParser.prototype.Comment=function(match,status){return this._addNonTagBlock(match,status,"addComment")};WYMeditor.XhtmlParser.prototype.Script=function(match,status){return this._addNonTagBlock(match,status,"addScript")};WYMeditor.XhtmlParser.prototype.Css=function(match,status){return this._addNonTagBlock(match,status,"addCss")};WYMeditor.XhtmlParser.prototype._addNonTagBlock=function(match,state,type){switch(state){case WYMeditor.LEXER_ENTER:this._non_tag=match;break;case WYMeditor.LEXER_UNMATCHED:this._non_tag+=match;break;case WYMeditor.LEXER_EXIT:switch(type){case"addComment":this._Listener.addComment(this._non_tag+match);break;case"addScript":this._Listener.addScript(this._non_tag+match);break;case"addCss":this._Listener.addCss(this._non_tag+match);break;default:break}break;default:break}return true};WYMeditor.XhtmlParser.prototype.SelfClosingTag=function(match,state){var result=this.OpeningTag(match,state);var tag=this.normalizeTag(match);return this.ClosingTag(match,state)};WYMeditor.XhtmlParser.prototype.OpeningTag=function(match,state){switch(state){case WYMeditor.LEXER_ENTER:this._tag=this.normalizeTag(match);this._tag_attributes={};break;case WYMeditor.LEXER_SPECIAL:this._callOpenTagListener(this.normalizeTag(match));break;case WYMeditor.LEXER_EXIT:this._callOpenTagListener(this._tag,this._tag_attributes);break;default:break}return true};WYMeditor.XhtmlParser.prototype.ClosingTag=function(match,state){this._callCloseTagListener(this.normalizeTag(match));return true};WYMeditor.XhtmlParser.prototype._callOpenTagListener=function(tag,attributes){attributes=attributes||{};this.autoCloseUnclosedBeforeNewOpening(tag);if(this._Listener.isBlockTag(tag)){this._Listener._tag_stack.push(tag);this._Listener.fixNestingBeforeOpeningBlockTag(tag,attributes);this._Listener.openBlockTag(tag,attributes);this._increaseOpenTagCounter(tag)}else if(this._Listener.isInlineTag(tag)){this._Listener.inlineTag(tag,attributes)}else{this._Listener.openUnknownTag(tag,attributes);this._increaseOpenTagCounter(tag)}this._Listener.last_tag=tag;this._Listener.last_tag_opened=true;this._Listener.last_tag_attributes=attributes};WYMeditor.XhtmlParser.prototype._callCloseTagListener=function(tag){if(this._decreaseOpenTagCounter(tag)){this.autoCloseUnclosedBeforeTagClosing(tag);if(this._Listener.isBlockTag(tag)){var expected_tag=this._Listener._tag_stack.pop();if(expected_tag===false){return}else if(expected_tag!=tag){tag=expected_tag}this._Listener.closeBlockTag(tag)}}else{if(!this._Listener.isInlineTag(tag)){this._Listener.closeUnopenedTag(tag)}}this._Listener.last_tag=tag;this._Listener.last_tag_opened=false};WYMeditor.XhtmlParser.prototype._increaseOpenTagCounter=function(tag){this._Listener._open_tags[tag]=this._Listener._open_tags[tag]||0;this._Listener._open_tags[tag]++};WYMeditor.XhtmlParser.prototype._decreaseOpenTagCounter=function(tag){if(this._Listener._open_tags[tag]){this._Listener._open_tags[tag]--;if(this._Listener._open_tags[tag]===0){this._Listener._open_tags[tag]=undefined}return true}return false};WYMeditor.XhtmlParser.prototype.autoCloseUnclosedBeforeNewOpening=function(new_tag){this._autoCloseUnclosed(new_tag,false)};WYMeditor.XhtmlParser.prototype.autoCloseUnclosedBeforeTagClosing=function(tag){this._autoCloseUnclosed(tag,true)};WYMeditor.XhtmlParser.prototype._autoCloseUnclosed=function(new_tag,closing){closing=closing||false;if(this._Listener._open_tags){for(var tag in this._Listener._open_tags){var counter=this._Listener._open_tags[tag];if(counter>0&&this._Listener.shouldCloseTagAutomatically(tag,new_tag,closing)){this._callCloseTagListener(tag,true)}}}};WYMeditor.XhtmlParser.prototype.getTagReplacements=function(){return this._Listener.getTagReplacements()};WYMeditor.XhtmlParser.prototype.normalizeTag=function(tag){tag=tag.replace(/^([\s<\/>]*)|([\s<\/>]*)$/gm,"").toLowerCase();var tags=this._Listener.getTagReplacements();if(tags[tag]){return tags[tag]}return tag};WYMeditor.XhtmlParser.prototype.TagAttributes=function(match,state){if(WYMeditor.LEXER_SPECIAL==state){this._current_attribute=match}return true};WYMeditor.XhtmlParser.prototype.DoubleQuotedAttribute=function(match,state){if(WYMeditor.LEXER_UNMATCHED==state){this._tag_attributes[this._current_attribute]=match}return true};WYMeditor.XhtmlParser.prototype.SingleQuotedAttribute=function(match,state){if(WYMeditor.LEXER_UNMATCHED==state){this._tag_attributes[this._current_attribute]=match}return true};WYMeditor.XhtmlParser.prototype.UnquotedAttribute=function(match,state){this._tag_attributes[this._current_attribute]=match.replace(/^=/,"");return true};WYMeditor.XhtmlSaxListener=function(){this.output="";this.helper=new WYMeditor.XmlHelper;this._open_tags={};this.validator=WYMeditor.XhtmlValidator;this._tag_stack=[];this.avoided_tags=[];this._insert_before_closing=[];this._insert_after_closing=[];this._last_node_was_text=false;this._insideTagToRemove=false;this._lastTagRemoved=false;this._extraLIClosingTags=0;this._removedTagStackIndex=0;this.entities={" ":" ","¡":"¡","¢":"¢","£":"£","¤":"¤","¥":"¥","¦":"¦","§":"§","¨":"¨","©":"©","ª":"ª","«":"«","¬":"¬","­":"­","®":"®","¯":"¯","°":"°","±":"±","²":"²","³":"³","´":"´","µ":"µ","¶":"¶","·":"·","¸":"¸","¹":"¹","º":"º","»":"»","¼":"¼","½":"½","¾":"¾","¿":"¿","À":"À","Á":"Á","Â":"Â","Ã":"Ã","Ä":"Ä","Å":"Å","Æ":"Æ","Ç":"Ç","È":"È","É":"É","Ê":"Ê","Ë":"Ë","Ì":"Ì","Í":"Í","Î":"Î","Ï":"Ï","Ð":"Ð","Ñ":"Ñ","Ò":"Ò","Ó":"Ó","Ô":"Ô","Õ":"Õ","Ö":"Ö","×":"×","Ø":"Ø","Ù":"Ù","Ú":"Ú","Û":"Û","Ü":"Ü","Ý":"Ý","Þ":"Þ","ß":"ß","à":"à","á":"á","â":"â","ã":"ã","ä":"ä","å":"å","æ":"æ","ç":"ç","è":"è","é":"é","ê":"ê","ë":"ë","ì":"ì","í":"í","î":"î","ï":"ï","ð":"ð","ñ":"ñ","ò":"ò","ó":"ó","ô":"ô","õ":"õ","ö":"ö","÷":"÷","ø":"ø","ù":"ù","ú":"ú","û":"û","ü":"ü","ý":"ý","þ":"þ","ÿ":"ÿ","Œ":"Œ","œ":"œ","Š":"Š","š":"š","Ÿ":"Ÿ","ƒ":"ƒ","ˆ":"ˆ","˜":"˜","Α":"Α","Β":"Β","Γ":"Γ","Δ":"Δ","Ε":"Ε","Ζ":"Ζ","Η":"Η","Θ":"Θ","Ι":"Ι","Κ":"Κ","Λ":"Λ","Μ":"Μ","Ν":"Ν","Ξ":"Ξ","Ο":"Ο","Π":"Π","Ρ":"Ρ","Σ":"Σ","Τ":"Τ","Υ":"Υ","Φ":"Φ","Χ":"Χ","Ψ":"Ψ","Ω":"Ω","α":"α","β":"β","γ":"γ","δ":"δ","ε":"ε","ζ":"ζ","η":"η","θ":"θ","ι":"ι","κ":"κ","λ":"λ","μ":"μ","ν":"ν","ξ":"ξ","ο":"ο","π":"π","ρ":"ρ","ς":"ς","σ":"σ","τ":"τ","υ":"υ","φ":"φ","χ":"χ","ψ":"ψ","ω":"ω","ϑ":"ϑ","ϒ":"ϒ","ϖ":"ϖ"," ":" "," ":" "," ":" ","‌":"‌","‍":"‍","‎":"‎","‏":"‏","–":"–","—":"—","‘":"‘","’":"’","‚":"‚","“":"“","”":"”","„":"„","†":"†","‡":"‡","•":"•","…":"…","‰":"‰","′":"′","″":"″","‹":"‹","›":"›","‾":"‾","⁄":"⁄","€":"€","ℑ":"ℑ","℘":"℘","ℜ":"ℜ","™":"™","ℵ":"ℵ","←":"←","↑":"↑","→":"→","↓":"↓","↔":"↔","↵":"↵","⇐":"⇐","⇑":"⇑","⇒":"⇒","⇓":"⇓","⇔":"⇔","∀":"∀","∂":"∂","∃":"∃","∅":"∅","∇":"∇","∈":"∈","∉":"∉","∋":"∋","∏":"∏","∑":"∑","−":"−","∗":"∗","√":"√","∝":"∝","∞":"∞","∠":"∠","∧":"∧","∨":"∨","∩":"∩","∪":"∪","∫":"∫","∴":"∴","∼":"∼","≅":"≅","≈":"≈","≠":"≠","≡":"≡","≤":"≤","≥":"≥","⊂":"⊂","⊃":"⊃","⊄":"⊄","⊆":"⊆","⊇":"⊇","⊕":"⊕","⊗":"⊗","⊥":"⊥","⋅":"⋅","⌈":"⌈","⌉":"⌉","⌊":"⌊","⌋":"⌋","⟨":"〈","⟩":"〉","◊":"◊","♠":"♠","♣":"♣","♥":"♥","♦":"♦"};this.block_tags=["a","abbr","acronym","address","area","b","base","bdo","big","blockquote","body","button","caption","cite","code","colgroup","dd","del","div","dfn","dl","dt","em","fieldset","form","head","h1","h2","h3","h4","h5","h6","html","i","iframe","ins","kbd","label","legend","li","map","noscript","object","ol","optgroup","option","p","param","pre","q","samp","script","select","small","span","strong","style","sub","sup","table","tbody","td","textarea","tfoot","th","thead","title","tr","tt","ul","var","extends"];this.inline_tags=["br","col","hr","img","input"];return this};WYMeditor.XhtmlSaxListener.prototype.shouldCloseTagAutomatically=function(tag,now_on_tag,closing){closing=closing||false;if(tag=="td"){if(closing&&now_on_tag=="tr"||!closing&&now_on_tag=="td"){return true}}else if(tag=="option"){if(closing&&now_on_tag=="select"||!closing&&now_on_tag=="option"){return true}}return false};WYMeditor.XhtmlSaxListener.prototype.beforeParsing=function(raw){this.output="";this._insert_before_closing=[];this._insert_after_closing=[];this._open_tags={};this._tag_stack=[];this._last_node_was_text=false;this._lastTagRemoved=false;this.last_tag=null;return raw};WYMeditor.XhtmlSaxListener.prototype.afterParsing=function(xhtml){xhtml=this.replaceNamedEntities(xhtml);xhtml=this.joinRepeatedEntities(xhtml);xhtml=this.removeEmptyTags(xhtml);xhtml=this.removeBrInPre(xhtml);return xhtml};WYMeditor.XhtmlSaxListener.prototype.replaceNamedEntities=function(xhtml){for(var entity in this.entities){xhtml=xhtml.replace(new RegExp(entity,"g"),this.entities[entity])}return xhtml};WYMeditor.XhtmlSaxListener.prototype.joinRepeatedEntities=function(xhtml){var tags="em|strong|sub|sup|acronym|pre|del|address";return xhtml.replace(new RegExp("<\\1>",""),"").replace(new RegExp("(s*<("+tags+")>s*){2}(.*)(s*s*){2}",""),"<$2>$3<$2>")};WYMeditor.XhtmlSaxListener.prototype.removeEmptyTags=function(xhtml){return xhtml.replace(new RegExp("<("+this.block_tags.join("|").replace(/\|td/,"").replace(/\|th/,"")+")>(
        | | |\\s)*","g"),"")};WYMeditor.XhtmlSaxListener.prototype.removeBrInPre=function(xhtml){var matches=xhtml.match(new RegExp("]*>(.*?)","gmi"));if(matches){for(var i=0;i","g"),String.fromCharCode(13,10)))}}return xhtml};WYMeditor.XhtmlSaxListener.prototype.getResult=function(){return this.output};WYMeditor.XhtmlSaxListener.prototype.getTagReplacements=function(){return{b:"strong",i:"em"}};WYMeditor.XhtmlSaxListener.prototype.getTagForStyle=function(style){if(/sub/.test(style)){return"sub"}else if(/super/.test(style)){return"sup"}else if(/bold/.test(style)){return"strong"}else if(/italic/.test(style)){return"em"}return false};WYMeditor.XhtmlSaxListener.prototype.addContent=function(text){if(this.last_tag&&this.last_tag=="li"){text=text.replace(/(\r|\n|\r\n)+$/g,"");text=text.replace(/(\r|\n|\r\n)+/g," ")}if(text.replace(/^\s+|\s+$/g,"").length>0){this._last_node_was_text=true}if(!this._insideTagToRemove){this.output+=text}};WYMeditor.XhtmlSaxListener.prototype.addComment=function(text){if(this.remove_comments||this._insideTagToRemove){return}this.output+=text};WYMeditor.XhtmlSaxListener.prototype.addScript=function(text){if(this.remove_scripts||this._insideTagToRemove){return}this.output+=text};WYMeditor.XhtmlSaxListener.prototype.addCss=function(text){if(this.remove_embeded_styles||this._insideTagToRemove){return}this.output+=text};WYMeditor.XhtmlSaxListener.prototype.openBlockTag=function(tag,attributes){this._last_node_was_text=false;if(this._insideTagToRemove){return}if(this._shouldRemoveTag(tag,attributes)){this._insideTagToRemove=true;this._removedTagStackIndex=this._tag_stack.length-1;return}attributes=this.validator.getValidTagAttributes(tag,attributes);attributes=this.removeUnwantedClasses(attributes);if(tag==="span"&&attributes.style){var new_tag=this.getTagForStyle(attributes.style);if(new_tag){tag=new_tag;this._tag_stack.pop();this._tag_stack.push(tag);attributes.style=""}}this.output+=this.helper.tag(tag,attributes,true);this._lastTagRemoved=false};WYMeditor.XhtmlSaxListener.prototype.inlineTag=function(tag,attributes){this._last_node_was_text=false;if(this._insideTagToRemove||this._shouldRemoveTag(tag,attributes)){return}attributes=this.validator.getValidTagAttributes(tag,attributes);attributes=this.removeUnwantedClasses(attributes);this.output+=this.helper.tag(tag,attributes);this._lastTagRemoved=false};WYMeditor.XhtmlSaxListener.prototype.openUnknownTag=function(tag,attributes){};WYMeditor.XhtmlSaxListener.prototype.closeBlockTag=function(tag){this._last_node_was_text=false;if(this._insideTagToRemove){if(this._tag_stack.length===this._removedTagStackIndex){this._insideTagToRemove=false}this._lastTagRemoved=true;return}this.output=this.output.replace(/
        $/,"")+this._getClosingTagContent("before",tag)+""+this._getClosingTagContent("after",tag)};WYMeditor.XhtmlSaxListener.prototype.closeUnknownTag=function(tag){};WYMeditor.XhtmlSaxListener.prototype.closeUnopenedTag=function(tag){this._last_node_was_text=false;if(this._insideTagToRemove){return}if(tag==="li"&&this._extraLIClosingTags){this._extraLIClosingTags--}else{this.output+=""}};WYMeditor.XhtmlSaxListener.prototype.avoidStylingTagsAndAttributes=function(){this.avoided_tags=["div","span"];this.validator.skiped_attributes=["style"];this.validator.skiped_attribute_values=["MsoNormal","main1"];this._avoiding_tags_implicitly=true};WYMeditor.XhtmlSaxListener.prototype.allowStylingTagsAndAttributes=function(){this.avoided_tags=[];this.validator.skiped_attributes=[];this.validator.skiped_attribute_values=[];this._avoiding_tags_implicitly=false};WYMeditor.XhtmlSaxListener.prototype.isBlockTag=function(tag){return!WYMeditor.Helper.contains(this.avoided_tags,tag)&&WYMeditor.Helper.contains(this.block_tags,tag)};WYMeditor.XhtmlSaxListener.prototype.isInlineTag=function(tag){return!WYMeditor.Helper.contains(this.avoided_tags,tag)&&WYMeditor.Helper.contains(this.inline_tags,tag)};WYMeditor.XhtmlSaxListener.prototype.insertContentAfterClosingTag=function(tag,content){this._insertContentWhenClosingTag("after",tag,content)};WYMeditor.XhtmlSaxListener.prototype.insertContentBeforeClosingTag=function(tag,content){this._insertContentWhenClosingTag("before",tag,content)};WYMeditor.XhtmlSaxListener.prototype.removeUnwantedClasses=function(attributes){var pattern,i;if(!attributes["class"]){return attributes}for(i=0;i\s*$/,"");this.insertContentAfterClosingTag(tag,"")}}else if((tag=="ul"||tag=="ol")&&this.last_tag&&this.last_tag_opened&&(this.last_tag=="ul"||this.last_tag=="ol")){if(!this._shouldRemoveTag(tag,attributes)){this.output+=this.helper.tag("li",{},true);this.insertContentAfterClosingTag(tag,"")}this._last_node_was_text=false}else if(tag=="li"){if(this._tag_stack.length>=2){var closestOpenTag=this._tag_stack[this._tag_stack.length-2];if(closestOpenTag=="li"&&!this._shouldRemoveTag(tag,attributes)){this._open_tags.li-=1;if(this._open_tags.li===0){this._open_tags.li=undefined}this._tag_stack.splice(this._tag_stack.length-2,1);this._last_node_was_text=false;if(!this._insideTagToRemove){this.output+=""}else if(this._tag_stack.length-1===this._removedTagStackIndex){this._insideTagToRemove=false;this._lastTagRemoved=true;this._extraLIClosingTags++}}}}};WYMeditor.XhtmlSaxListener.prototype._insertContentWhenClosingTag=function(position,tag,content){if(!this["_insert_"+position+"_closing"]){this["_insert_"+position+"_closing"]=[]}if(!this["_insert_"+position+"_closing"][tag]){this["_insert_"+position+"_closing"][tag]=[]}this["_insert_"+position+"_closing"][tag].push(content)};WYMeditor.XhtmlSaxListener.prototype._getClosingTagContent=function(position,tag){if(this["_insert_"+position+"_closing"]&&this["_insert_"+position+"_closing"][tag]&&this["_insert_"+position+"_closing"][tag].length>0){return this["_insert_"+position+"_closing"][tag].pop()}return""};WYMeditor.XhtmlSaxListener.prototype._shouldRemoveTag=function(tag,attributes){var classes;if(!attributes["class"]){return false}classes=attributes["class"].split(" ");if(jQuery.inArray(WYMeditor.EDITOR_ONLY_CLASS,classes)>-1){return true}return false};WYMeditor.WymCssLexer=function(parser,only_wym_blocks){only_wym_blocks=typeof only_wym_blocks=="undefined"?true:only_wym_blocks;jQuery.extend(this,new WYMeditor.Lexer(parser,only_wym_blocks?"Ignore":"WymCss"));this.mapHandler("WymCss","Ignore");if(only_wym_blocks===true){this.addEntryPattern("/\\*[<\\s]*WYMeditor[>\\s]*\\*/","Ignore","WymCss");this.addExitPattern("/\\*[\\s]*\\*/","WymCss")}this.addSpecialPattern("[\\sa-z1-6]*\\.[a-z-_0-9]+","WymCss","WymCssStyleDeclaration");this.addEntryPattern("/\\*","WymCss","WymCssComment");this.addExitPattern("\\*/","WymCssComment");this.addEntryPattern("{","WymCss","WymCssStyle");this.addExitPattern("}","WymCssStyle");this.addEntryPattern("/\\*","WymCssStyle","WymCssFeedbackStyle");this.addExitPattern("\\*/","WymCssFeedbackStyle");return this};WYMeditor.WymCssParser=function(){this._in_style=false;this._has_title=false;this.only_wym_blocks=true;this.css_settings={classesItems:[],editorStyles:[],dialogStyles:[]};return this};WYMeditor.WymCssParser.prototype.parse=function(raw,only_wym_blocks){only_wym_blocks=typeof only_wym_blocks=="undefined"?this.only_wym_blocks:only_wym_blocks;this._Lexer=new WYMeditor.WymCssLexer(this,only_wym_blocks);this._Lexer.parse(raw)};WYMeditor.WymCssParser.prototype.Ignore=function(match,state){return true};WYMeditor.WymCssParser.prototype.WymCssComment=function(text,status){if(text.match(/end[a-z0-9\s]*wym[a-z0-9\s]*/im)){return false}if(status==WYMeditor.LEXER_UNMATCHED){if(!this._in_style){this._has_title=true;this._current_item={title:WYMeditor.Helper.trim(text)}}else{if(this._current_item[this._current_element]){if(!this._current_item[this._current_element].expressions){this._current_item[this._current_element].expressions=[text]}else{this._current_item[this._current_element].expressions.push(text)}}}this._in_style=true}return true};WYMeditor.WymCssParser.prototype.WymCssStyle=function(match,status){if(status==WYMeditor.LEXER_UNMATCHED){match=WYMeditor.Helper.trim(match);if(match!==""){this._current_item[this._current_element].style=match}}else if(status==WYMeditor.LEXER_EXIT){this._in_style=false;this._has_title=false;this.addStyleSetting(this._current_item)}return true};WYMeditor.WymCssParser.prototype.WymCssFeedbackStyle=function(match,status){if(status==WYMeditor.LEXER_UNMATCHED){this._current_item[this._current_element].feedback_style=match.replace(/^([\s\/\*]*)|([\s\/\*]*)$/gm,"")}return true};WYMeditor.WymCssParser.prototype.WymCssStyleDeclaration=function(match){match=match.replace(/^([\s\.]*)|([\s\.*]*)$/gm,"");var tag="";if(match.indexOf(".")>0){var parts=match.split(".");this._current_element=parts[1];tag=parts[0]}else{this._current_element=match}if(!this._has_title){this._current_item={title:(!tag?"":tag.toUpperCase()+": ")+this._current_element};this._has_title=true}if(!this._current_item[this._current_element]){this._current_item[this._current_element]={name:this._current_element}}if(tag){if(!this._current_item[this._current_element].tags){this._current_item[this._current_element].tags=[tag]}else{this._current_item[this._current_element].tags.push(tag)}}return true};WYMeditor.WymCssParser.prototype.addStyleSetting=function(style_details){for(var name in style_details){var details=style_details[name];if(typeof details=="object"&&name!="title"){this.css_settings.classesItems.push({name:WYMeditor.Helper.trim(details.name),title:style_details.title,expr:WYMeditor.Helper.trim((details.expressions||details.tags).join(", "))});if(details.feedback_style){this.css_settings.editorStyles.push({name:"."+WYMeditor.Helper.trim(details.name),css:details.feedback_style})}if(details.style){this.css_settings.dialogStyles.push({name:"."+WYMeditor.Helper.trim(details.name),css:details.style})}}}}; \ No newline at end of file diff --git a/public/wymeditor/wymeditor/lang/bg.js b/public/wymeditor/wymeditor/lang/bg.js new file mode 100644 index 0000000..35a8e2b --- /dev/null +++ b/public/wymeditor/wymeditor/lang/bg.js @@ -0,0 +1,45 @@ +WYMeditor.STRINGS.bg = { + Strong: 'Получер', + Emphasis: 'Курсив', + Superscript: 'Горен индекс', + Subscript: 'Долен индекс', + Ordered_List: 'Подреден списък', + Unordered_List: 'Неподреден списък', + Indent: 'Блок навътре', + Outdent: 'Блок навън', + Undo: 'Стъпка назад', + Redo: 'Стъпка напред', + Link: 'Създай хипервръзка', + Unlink: 'Премахни хипервръзката', + Image: 'Изображение', + Table: 'Таблица', + HTML: 'HTML', + Paragraph: 'Абзац', + Heading_1: 'Заглавие 1', + Heading_2: 'Заглавие 2', + Heading_3: 'Заглавие 3', + Heading_4: 'Заглавие 4', + Heading_5: 'Заглавие 5', + Heading_6: 'Заглавие 6', + Preformatted: 'Преформатиран', + Blockquote: 'Цитат', + Table_Header: 'Заглавие на таблицата', + URL: 'URL', + Title: 'Заглавие', + Alternative_Text: 'Алтернативен текст', + Caption: 'Етикет', + Summary: 'Общо', + Number_Of_Rows: 'Брой редове', + Number_Of_Cols: 'Брой колони', + Submit: 'Изпрати', + Cancel: 'Отмени', + Choose: 'Затвори', + Preview: 'Предварителен преглед', + Paste_From_Word: 'Вмъкни от MS WORD', + Tools: 'Инструменти', + Containers: 'Контейнери', + Classes: 'Класове', + Status: 'Статус', + Source_Code: 'Източник, код' +}; + diff --git a/public/wymeditor/wymeditor/lang/ca.js b/public/wymeditor/wymeditor/lang/ca.js new file mode 100644 index 0000000..595b8ce --- /dev/null +++ b/public/wymeditor/wymeditor/lang/ca.js @@ -0,0 +1,45 @@ +WYMeditor.STRINGS.ca = { + Strong: 'Ressaltar', + Emphasis: 'Emfatitzar', + Superscript: 'Superindex', + Subscript: 'Subindex', + Ordered_List: 'Llistat ordenat', + Unordered_List: 'Llistat sense ordenar', + Indent: 'Indentat', + Outdent: 'Sense indentar', + Undo: 'Desfer', + Redo: 'Refer', + Link: 'Enllaçar', + Unlink: 'Eliminar enllaç', + Image: 'Imatge', + Table: 'Taula', + HTML: 'HTML', + Paragraph: 'Paràgraf', + Heading_1: 'Capçalera 1', + Heading_2: 'Capçalera 2', + Heading_3: 'Capçalera 3', + Heading_4: 'Capçalera 4', + Heading_5: 'Capçalera 5', + Heading_6: 'Capçalera 6', + Preformatted: 'Pre-formatejat', + Blockquote: 'Cita', + Table_Header: 'Capçalera de la taula', + URL: 'URL', + Title: 'Títol', + Alternative_Text: 'Text alternatiu', + Caption: 'Llegenda', + Summary: 'Summary', + Number_Of_Rows: 'Nombre de files', + Number_Of_Cols: 'Nombre de columnes', + Submit: 'Enviar', + Cancel: 'Cancel·lar', + Choose: 'Triar', + Preview: 'Vista prèvia', + Paste_From_Word: 'Pegar des de Word', + Tools: 'Eines', + Containers: 'Contenidors', + Classes: 'Classes', + Status: 'Estat', + Source_Code: 'Codi font' +}; + diff --git a/public/wymeditor/wymeditor/lang/cs.js b/public/wymeditor/wymeditor/lang/cs.js new file mode 100644 index 0000000..fdbbcc4 --- /dev/null +++ b/public/wymeditor/wymeditor/lang/cs.js @@ -0,0 +1,45 @@ +WYMeditor.STRINGS.cs = { + Strong: 'Tučné', + Emphasis: 'Kurzíva', + Superscript: 'Horní index', + Subscript: 'Dolní index', + Ordered_List: 'Číslovaný seznam', + Unordered_List: 'Nečíslovaný seznam', + Indent: 'Zvětšit odsazení', + Outdent: 'Zmenšit odsazení', + Undo: 'Zpět', + Redo: 'Znovu', + Link: 'Vytvořit odkaz', + Unlink: 'Zrušit odkaz', + Image: 'Obrázek', + Table: 'Tabulka', + HTML: 'HTML', + Paragraph: 'Odstavec', + Heading_1: 'Nadpis 1. úrovně', + Heading_2: 'Nadpis 2. úrovně', + Heading_3: 'Nadpis 3. úrovně', + Heading_4: 'Nadpis 4. úrovně', + Heading_5: 'Nadpis 5. úrovně', + Heading_6: 'Nadpis 6. úrovně', + Preformatted: 'Předformátovaný text', + Blockquote: 'Citace', + Table_Header: 'Hlavičková buňka tabulky', + URL: 'Adresa', + Title: 'Text po najetí myší', + Alternative_Text: 'Text pro případ nezobrazení obrázku', + Caption: 'Titulek tabulky', + Summary: 'Shrnutí obsahu', + Number_Of_Rows: 'Počet řádek', + Number_Of_Cols: 'Počet sloupců', + Submit: 'Vytvořit', + Cancel: 'Zrušit', + Choose: 'Vybrat', + Preview: 'Náhled', + Paste_From_Word: 'Vložit z Wordu', + Tools: 'Nástroje', + Containers: 'Typy obsahu', + Classes: 'Třídy', + Status: 'Stav', + Source_Code: 'Zdrojový kód' +}; + diff --git a/public/wymeditor/wymeditor/lang/cy.js b/public/wymeditor/wymeditor/lang/cy.js new file mode 100644 index 0000000..e812677 --- /dev/null +++ b/public/wymeditor/wymeditor/lang/cy.js @@ -0,0 +1,45 @@ +WYMeditor.STRINGS.cy = { + Strong: 'Bras', + Emphasis: 'Italig', + Superscript: 'Uwchsgript', + Subscript: 'Is-sgript', + Ordered_List: 'Rhestr mewn Trefn', + Unordered_List: 'Pwyntiau Bwled', + Indent: 'Mewnoli', + Outdent: 'Alloli', + Undo: 'Dadwneud', + Redo: 'Ailwneud', + Link: 'Cysylltu', + Unlink: 'Datgysylltu', + Image: 'Delwedd', + Table: 'Tabl', + HTML: 'HTML', + Paragraph: 'Paragraff', + Heading_1: 'Pennawd 1', + Heading_2: 'Pennawd 2', + Heading_3: 'Pennawd 3', + Heading_4: 'Pennawd 4', + Heading_5: 'Pennawd 5', + Heading_6: 'Pennawd 6', + Preformatted: 'Rhagfformat', + Blockquote: 'Bloc Dyfyniad', + Table_Header: 'Pennyn Tabl', + URL: 'URL', + Title: 'Teitl', + Alternative_Text: 'Testun Amgen', + Caption: 'Pennawd', + Summary: 'Crynodeb', + Number_Of_Rows: 'Nifer y rhesi', + Number_Of_Cols: 'Nifer y colofnau', + Submit: 'Anfon', + Cancel: 'Diddymu', + Choose: 'Dewis', + Preview: 'Rhagolwg', + Paste_From_Word: 'Gludo o Word', + Tools: 'Offer', + Containers: 'Cynhwysyddion', + Classes: 'Dosbarthiadau', + Status: 'Statws', + Source_Code: 'Cod ffynhonnell' +}; + diff --git a/public/wymeditor/wymeditor/lang/da.js b/public/wymeditor/wymeditor/lang/da.js new file mode 100644 index 0000000..21a63c8 --- /dev/null +++ b/public/wymeditor/wymeditor/lang/da.js @@ -0,0 +1,45 @@ +WYMeditor.STRINGS['da'] = { + Strong: 'Fed', + Emphasis: 'Skrå', + Superscript: 'Superscript', + Subscript: 'Subscript', + Ordered_List: 'Ordnet liste', + Unordered_List: 'Uordnet liste', + Indent: 'Indrykke', + Outdent: 'Udrykke', + Undo: 'Fortryd', + Redo: 'Fortryd', + Link: 'Link', + Unlink: 'Fjern link', + Image: 'Billede', + Table: 'Tabel', + HTML: 'HTML', + Paragraph: 'Paragraf', + Heading_1: 'Overskrift 1', + Heading_2: 'Overskrift 2', + Heading_3: 'Overskrift 3', + Heading_4: 'Overskrift 4', + Heading_5: 'Overskrift 5', + Heading_6: 'Overskrift 6', + Preformatted: 'Forudformateret', + Blockquote: 'Citat', + Table_Header: 'Tabel Overskrift', + URL: 'URL', + Title: 'Titel', + Alternative_Text: 'Alternativ tekst', + Caption: 'Billedtekst', + Summary: 'Resumé', + Number_Of_Rows: 'Antal rækker', + Number_Of_Cols: 'Antal kolonner', + Submit: 'Indsend', + Cancel: 'Afbryd', + Choose: 'Vælg', + Preview: 'Forhåndsvisning', + Paste_From_Word: 'Indsæt fra Word', + Tools: 'Værktøjer', + Containers: 'Containere', + Classes: 'Klasser', + Status: 'Status', + Source_Code: 'Kildekode' +}; + diff --git a/public/wymeditor/wymeditor/lang/de.js b/public/wymeditor/wymeditor/lang/de.js new file mode 100644 index 0000000..d106829 --- /dev/null +++ b/public/wymeditor/wymeditor/lang/de.js @@ -0,0 +1,45 @@ +WYMeditor.STRINGS.de = { + Strong: 'Fett', + Emphasis: 'Kursiv', + Superscript: 'Text hochstellen', + Subscript: 'Text tiefstellen', + Ordered_List: 'Geordnete Liste einfügen', + Unordered_List: 'Ungeordnete Liste einfügen', + Indent: 'Einzug erhöhen', + Outdent: 'Einzug vermindern', + Undo: 'Befehle rückgängig machen', + Redo: 'Befehle wiederherstellen', + Link: 'Hyperlink einfügen', + Unlink: 'Hyperlink entfernen', + Image: 'Bild einfügen', + Table: 'Tabelle einfügen', + HTML: 'HTML anzeigen/verstecken', + Paragraph: 'Absatz', + Heading_1: 'Überschrift 1', + Heading_2: 'Überschrift 2', + Heading_3: 'Überschrift 3', + Heading_4: 'Überschrift 4', + Heading_5: 'Überschrift 5', + Heading_6: 'Überschrift 6', + Preformatted: 'Vorformatiert', + Blockquote: 'Zitat', + Table_Header: 'Tabellenüberschrift', + URL: 'URL', + Title: 'Titel', + Alternative_Text: 'Alternativer Text', + Caption: 'Tabellenüberschrift', + Summary: 'Summary', + Number_Of_Rows: 'Anzahl Zeilen', + Number_Of_Cols: 'Anzahl Spalten', + Submit: 'Absenden', + Cancel: 'Abbrechen', + Choose: 'Auswählen', + Preview: 'Vorschau', + Paste_From_Word: 'Aus Word einfügen', + Tools: 'Werkzeuge', + Containers: 'Inhaltstyp', + Classes: 'Klassen', + Status: 'Status', + Source_Code: 'Quellcode' +}; + diff --git a/public/wymeditor/wymeditor/lang/en.js b/public/wymeditor/wymeditor/lang/en.js new file mode 100644 index 0000000..df7ac51 --- /dev/null +++ b/public/wymeditor/wymeditor/lang/en.js @@ -0,0 +1,45 @@ +WYMeditor.STRINGS.en = { + Strong: 'Strong', + Emphasis: 'Emphasis', + Superscript: 'Superscript', + Subscript: 'Subscript', + Ordered_List: 'Ordered List', + Unordered_List: 'Unordered List', + Indent: 'Indent', + Outdent: 'Outdent', + Undo: 'Undo', + Redo: 'Redo', + Link: 'Link', + Unlink: 'Unlink', + Image: 'Image', + Table: 'Table', + HTML: 'HTML', + Paragraph: 'Paragraph', + Heading_1: 'Heading 1', + Heading_2: 'Heading 2', + Heading_3: 'Heading 3', + Heading_4: 'Heading 4', + Heading_5: 'Heading 5', + Heading_6: 'Heading 6', + Preformatted: 'Preformatted', + Blockquote: 'Blockquote', + Table_Header: 'Table Header', + URL: 'URL', + Title: 'Title', + Relationship: 'Relationship', + Alternative_Text: 'Alternative text', + Caption: 'Caption', + Summary: 'Summary', + Number_Of_Rows: 'Number of rows', + Number_Of_Cols: 'Number of cols', + Submit: 'Submit', + Cancel: 'Cancel', + Choose: 'Choose', + Preview: 'Preview', + Paste_From_Word: 'Paste from Word', + Tools: 'Tools', + Containers: 'Containers', + Classes: 'Classes', + Status: 'Status', + Source_Code: 'Source code' +}; \ No newline at end of file diff --git a/public/wymeditor/wymeditor/lang/es.js b/public/wymeditor/wymeditor/lang/es.js new file mode 100644 index 0000000..4a89b00 --- /dev/null +++ b/public/wymeditor/wymeditor/lang/es.js @@ -0,0 +1,45 @@ +WYMeditor.STRINGS.es = { + Strong: 'Resaltar', + Emphasis: 'Enfatizar', + Superscript: 'Superindice', + Subscript: 'Subindice', + Ordered_List: 'Lista ordenada', + Unordered_List: 'Lista sin ordenar', + Indent: 'Indentado', + Outdent: 'Sin indentar', + Undo: 'Deshacer', + Redo: 'Rehacer', + Link: 'Enlazar', + Unlink: 'Eliminar enlace', + Image: 'Imagen', + Table: 'Tabla', + HTML: 'HTML', + Paragraph: 'Párrafo', + Heading_1: 'Cabecera 1', + Heading_2: 'Cabecera 2', + Heading_3: 'Cabecera 3', + Heading_4: 'Cabecera 4', + Heading_5: 'Cabecera 5', + Heading_6: 'Cabecera 6', + Preformatted: 'Preformateado', + Blockquote: 'Cita', + Table_Header: 'Cabecera de la tabla', + URL: 'URL', + Title: 'Título', + Alternative_Text: 'Texto alternativo', + Caption: 'Leyenda', + Summary: 'Summary', + Number_Of_Rows: 'Número de filas', + Number_Of_Cols: 'Número de columnas', + Submit: 'Enviar', + Cancel: 'Cancelar', + Choose: 'Seleccionar', + Preview: 'Vista previa', + Paste_From_Word: 'Pegar desde Word', + Tools: 'Herramientas', + Containers: 'Contenedores', + Classes: 'Clases', + Status: 'Estado', + Source_Code: 'Código fuente' +}; + diff --git a/public/wymeditor/wymeditor/lang/fa.js b/public/wymeditor/wymeditor/lang/fa.js new file mode 100644 index 0000000..c948f24 --- /dev/null +++ b/public/wymeditor/wymeditor/lang/fa.js @@ -0,0 +1,46 @@ +//Translation To Persian: Ghassem Tofighi (http://ght.ir) +WYMeditor.STRINGS.fa = { + Strong: 'پررنگ',//Strong + Emphasis: 'ایتالیک',//Emphasis + Superscript: 'بالانويس‌ ',//Superscript + Subscript: 'زيرنويس‌',//Subscript + Ordered_List: 'لیست مرتب',//Ordered List + Unordered_List: 'لیست نامرتب',//Unordered List + Indent: 'افزودن دندانه',//Indent + Outdent: 'کاهش دندانه',//Outdent + Undo: 'واگردانی',//Undo + Redo: 'تکرار',//Redo + Link: 'ساختن پیوند',//Link + Unlink: 'برداشتن پیوند',//Unlink + Image: 'تصویر',//Image + Table: 'جدول',//Table + HTML: 'HTML',//HTML + Paragraph: 'پاراگراف',//Paragraph + Heading_1: 'سرتیتر ۱',//Heading 1 + Heading_2: 'سرتیتر ۲',//Heading 2 + Heading_3: 'سرتیتر ۳',//Heading 3 + Heading_4: 'سرتیتر ۴',//Heading 4 + Heading_5: 'سرتیتر ۵',//Heading 5 + Heading_6: 'سرتیتر ۶',//Heading 6 + Preformatted: 'قالب آماده',//Preformatted + Blockquote: 'نقل قول',//Blockquote + Table_Header: 'سرجدول',//Table Header + URL: 'آدرس اینترنتی',//URL + Title: 'عنوان',//Title + Alternative_Text: 'متن جایگزین',//Alternative text + Caption: 'عنوان',//Caption + Summary: 'Summary', + Number_Of_Rows: 'تعداد سطرها',//Number of rows + Number_Of_Cols: 'تعداد ستون‌ها',//Number of cols + Submit: 'فرستادن',//Submit + Cancel: 'لغو',//Cancel + Choose: 'انتخاب',//Choose + Preview: 'پیش‌نمایش',//Preview + Paste_From_Word: 'انتقال از ورد',//Paste from Word + Tools: 'ابزار',//Tools + Containers: '‌قالب‌ها',//Containers + Classes: 'کلاس‌ها',//Classes + Status: 'وضعیت',//Status + Source_Code: 'کد مبدأ'//Source code +}; + diff --git a/public/wymeditor/wymeditor/lang/fi.js b/public/wymeditor/wymeditor/lang/fi.js new file mode 100644 index 0000000..529fcec --- /dev/null +++ b/public/wymeditor/wymeditor/lang/fi.js @@ -0,0 +1,44 @@ +WYMeditor.STRINGS.fi = { + Strong: 'Lihavoitu', + Emphasis: 'Korostus', + Superscript: 'Yläindeksi', + Subscript: 'Alaindeksi', + Ordered_List: 'Numeroitu lista', + Unordered_List: 'Luettelomerkit', + Indent: 'Suurenna sisennystä', + Outdent: 'Pienennä sisennystä', + Undo: 'Kumoa', + Redo: 'Toista', + Link: 'Linkitä', + Unlink: 'Poista linkitys', + Image: 'Kuva', + Table: 'Taulukko', + HTML: 'HTML', + Paragraph: 'Kappale', + Heading_1: 'Otsikko 1', + Heading_2: 'Otsikko 2', + Heading_3: 'Otsikko 3', + Heading_4: 'Otsikko 4', + Heading_5: 'Otsikko 5', + Heading_6: 'Otsikko 6', + Preformatted: 'Esimuotoilu', + Blockquote: 'Sitaatti', + Table_Header: 'Taulukon otsikko', + URL: 'URL', + Title: 'Otsikko', + Alternative_Text: 'Vaihtoehtoinen teksti', + Caption: 'Kuvateksti', + Summary: 'Yhteenveto', + Number_Of_Rows: 'Rivien määrä', + Number_Of_Cols: 'Palstojen määrä', + Submit: 'Lähetä', + Cancel: 'Peruuta', + Choose: 'Valitse', + Preview: 'Esikatsele', + Paste_From_Word: 'Tuo Wordista', + Tools: 'Työkalut', + Containers: 'Muotoilut', + Classes: 'Luokat', + Status: 'Tila', + Source_Code: 'Lähdekoodi' +}; diff --git a/public/wymeditor/wymeditor/lang/fr.js b/public/wymeditor/wymeditor/lang/fr.js new file mode 100644 index 0000000..1b2ab75 --- /dev/null +++ b/public/wymeditor/wymeditor/lang/fr.js @@ -0,0 +1,45 @@ +WYMeditor.STRINGS.fr = { + Strong: 'Mise en évidence', + Emphasis: 'Emphase', + Superscript: 'Exposant', + Subscript: 'Indice', + Ordered_List: 'Liste Ordonnée', + Unordered_List: 'Liste Non-Ordonnée', + Indent: 'Imbriqué', + Outdent: 'Non-imbriqué', + Undo: 'Annuler', + Redo: 'Rétablir', + Link: 'Lien', + Unlink: 'Supprimer le Lien', + Image: 'Image', + Table: 'Tableau', + HTML: 'HTML', + Paragraph: 'Paragraphe', + Heading_1: 'Titre 1', + Heading_2: 'Titre 2', + Heading_3: 'Titre 3', + Heading_4: 'Titre 4', + Heading_5: 'Titre 5', + Heading_6: 'Titre 6', + Preformatted: 'Pré-formatté', + Blockquote: 'Citation', + Table_Header: 'Cellule de titre', + URL: 'URL', + Title: 'Titre', + Alternative_Text: 'Texte alternatif', + Caption: 'Légende', + Summary: 'Résumé', + Number_Of_Rows: 'Nombre de lignes', + Number_Of_Cols: 'Nombre de colonnes', + Submit: 'Envoyer', + Cancel: 'Annuler', + Choose: 'Choisir', + Preview: 'Prévisualisation', + Paste_From_Word: 'Copier depuis Word', + Tools: 'Outils', + Containers: 'Type de texte', + Classes: 'Type de contenu', + Status: 'Infos', + Source_Code: 'Code source' +}; + diff --git a/public/wymeditor/wymeditor/lang/gl.js b/public/wymeditor/wymeditor/lang/gl.js new file mode 100644 index 0000000..58a0af4 --- /dev/null +++ b/public/wymeditor/wymeditor/lang/gl.js @@ -0,0 +1,45 @@ +WYMeditor.STRINGS.gl = { + Strong: 'Moita énfase', + Emphasis: 'Énfase', + Superscript: 'Superíndice', + Subscript: 'Subíndice', + Ordered_List: 'Lista ordenada', + Unordered_List: 'Lista sen ordenar', + Indent: 'Aniñar', + Outdent: 'Desaniñar', + Undo: 'Desfacer', + Redo: 'Refacer', + Link: 'Ligazón', + Unlink: 'Desligar', + Image: 'Imaxe', + Table: 'Táboa', + HTML: 'HTML', + Paragraph: 'Parágrafo', + Heading_1: 'Título 1', + Heading_2: 'Título 2', + Heading_3: 'Título 3', + Heading_4: 'Título 4', + Heading_5: 'Título 5', + Heading_6: 'Título 6', + Preformatted: 'Preformatado', + Blockquote: 'Cita en parágrafo', + Table_Header: 'Cabeceira da táboa', + URL: 'URL', + Title: 'Título', + Alternative_Text: 'Texto alternativo', + Caption: 'Título', + Summary: 'Resumo', + Number_Of_Rows: 'Número de filas', + Number_Of_Cols: 'Número de columnas', + Submit: 'Enviar', + Cancel: 'Cancelar', + Choose: 'Escoller', + Preview: 'Previsualizar', + Paste_From_Word: 'Colar dende Word', + Tools: 'Ferramentas', + Containers: 'Contenedores', + Classes: 'Clases', + Status: 'Estado', + Source_Code: 'Código fonte' +}; + diff --git a/public/wymeditor/wymeditor/lang/he.js b/public/wymeditor/wymeditor/lang/he.js new file mode 100644 index 0000000..3d57c33 --- /dev/null +++ b/public/wymeditor/wymeditor/lang/he.js @@ -0,0 +1,45 @@ +WYMeditor.STRINGS.he = { + Strong: 'חזק', + Emphasis: 'מובלט', + Superscript: 'כתב עילי', + Subscript: 'כתב תחתי', + Ordered_List: 'רשימה ממוספרת', + Unordered_List: 'רשימה לא ממוספרת', + Indent: 'הזחה פנימה', + Outdent: 'הזחה החוצה', + Undo: 'בטל פעולה', + Redo: 'בצע מחדש פעולה', + Link: 'קישור', + Unlink: 'בטל קישור', + Image: 'תמונה', + Table: 'טבלה', + HTML: 'קוד HTML', + Paragraph: 'פסקה', + Heading_1: 'כותרת 1 ; תג <h1>', + Heading_2: 'כותרת 2 ; תג <h2>', + Heading_3: 'כותרת 3 ; תג <h3>', + Heading_4: 'כותרת 4 ; תג <h4>', + Heading_5: 'כותרת 5 ; תג <h5>', + Heading_6: 'כותרת 6 ; תג <h6>', + Preformatted: 'משמר רווחים', + Blockquote: 'ציטוט', + Table_Header: 'כותרת טבלה', + URL: 'קישור (URL)', + Title: 'כותרת', + Alternative_Text: 'טקסט חלופי', + Caption: 'כותרת', + Summary: 'סיכום', + Number_Of_Rows: 'מספר שורות', + Number_Of_Cols: 'מספר טורים', + Submit: 'שלח', + Cancel: 'בטל', + Choose: 'בחר', + Preview: 'תצוגה מקדימה', + Paste_From_Word: 'העתק מ-Word', + Tools: 'כלים', + Containers: 'מיכלים', + Classes: 'מחלקות', + Status: 'מצב', + Source_Code: 'קוד מקור' +}; + diff --git a/public/wymeditor/wymeditor/lang/hr.js b/public/wymeditor/wymeditor/lang/hr.js new file mode 100644 index 0000000..22d6bda --- /dev/null +++ b/public/wymeditor/wymeditor/lang/hr.js @@ -0,0 +1,45 @@ +WYMeditor.STRINGS.hr = { + Strong: 'Podebljano', + Emphasis: 'Naglašeno', + Superscript: 'Iznad', + Subscript: 'Ispod', + Ordered_List: 'Pobrojana lista', + Unordered_List: 'Nepobrojana lista', + Indent: 'Uvuci', + Outdent: 'Izvuci', + Undo: 'Poništi promjenu', + Redo: 'Ponovno promjeni', + Link: 'Hiperveza', + Unlink: 'Ukloni hipervezu', + Image: 'Slika', + Table: 'Tablica', + HTML: 'HTML', + Paragraph: 'Paragraf', + Heading_1: 'Naslov 1', + Heading_2: 'Naslov 2', + Heading_3: 'Naslov 3', + Heading_4: 'Naslov 4', + Heading_5: 'Naslov 5', + Heading_6: 'Naslov 6', + Preformatted: 'Unaprijed formatirano', + Blockquote: 'Citat', + Table_Header: 'Zaglavlje tablice', + URL: 'URL', + Title: 'Naslov', + Alternative_Text: 'Alternativni tekst', + Caption: 'Zaglavlje', + Summary: 'Sažetak', + Number_Of_Rows: 'Broj redova', + Number_Of_Cols: 'Broj kolona', + Submit: 'Snimi', + Cancel: 'Odustani', + Choose: 'Izaberi', + Preview: 'Pregled', + Paste_From_Word: 'Zalijepi iz Word-a', + Tools: 'Alati', + Containers: 'Kontejneri', + Classes: 'Klase', + Status: 'Status', + Source_Code: 'Izvorni kod' +}; + diff --git a/public/wymeditor/wymeditor/lang/hu.js b/public/wymeditor/wymeditor/lang/hu.js new file mode 100644 index 0000000..fcc3a4e --- /dev/null +++ b/public/wymeditor/wymeditor/lang/hu.js @@ -0,0 +1,45 @@ +WYMeditor.STRINGS.hu = { + Strong: 'Félkövér', + Emphasis: 'Kiemelt', + Superscript: 'Felső index', + Subscript: 'Alsó index', + Ordered_List: 'Rendezett lista', + Unordered_List: 'Rendezetlen lista', + Indent: 'Bekezdés', + Outdent: 'Bekezdés törlése', + Undo: 'Visszavon', + Redo: 'Visszaállít', + Link: 'Link', + Unlink: 'Link törlése', + Image: 'Kép', + Table: 'Tábla', + HTML: 'HTML', + Paragraph: 'Bekezdés', + Heading_1: 'Címsor 1', + Heading_2: 'Címsor 2', + Heading_3: 'Címsor 3', + Heading_4: 'Címsor 4', + Heading_5: 'Címsor 5', + Heading_6: 'Címsor 6', + Preformatted: 'Előformázott', + Blockquote: 'Idézet', + Table_Header: 'Tábla Fejléc', + URL: 'Webcím', + Title: 'Megnevezés', + Alternative_Text: 'Alternatív szöveg', + Caption: 'Fejléc', + Summary: 'Summary', + Number_Of_Rows: 'Sorok száma', + Number_Of_Cols: 'Oszlopok száma', + Submit: 'Elküld', + Cancel: 'Mégsem', + Choose: 'Választ', + Preview: 'Előnézet', + Paste_From_Word: 'Másolás Word-ból', + Tools: 'Eszközök', + Containers: 'Tartalmak', + Classes: 'Osztályok', + Status: 'Állapot', + Source_Code: 'Forráskód' +}; + diff --git a/public/wymeditor/wymeditor/lang/it.js b/public/wymeditor/wymeditor/lang/it.js new file mode 100644 index 0000000..d296e0b --- /dev/null +++ b/public/wymeditor/wymeditor/lang/it.js @@ -0,0 +1,45 @@ +WYMeditor.STRINGS.it = { + Strong: 'Grassetto', + Emphasis: 'Corsetto', + Superscript: 'Apice', + Subscript: 'Pedice', + Ordered_List: 'Lista Ordinata', + Unordered_List: 'Lista Puntata', + Indent: 'Indenta', + Outdent: 'Caccia', + Undo: 'Indietro', + Redo: 'Avanti', + Link: 'Inserisci Link', + Unlink: 'Togli Link', + Image: 'Inserisci Immagine', + Table: 'Inserisci Tabella', + HTML: 'HTML', + Paragraph: 'Paragrafo', + Heading_1: 'Heading 1', + Heading_2: 'Heading 2', + Heading_3: 'Heading 3', + Heading_4: 'Heading 4', + Heading_5: 'Heading 5', + Heading_6: 'Heading 6', + Preformatted: 'Preformattato', + Blockquote: 'Blockquote', + Table_Header: 'Header Tabella', + URL: 'Indirizzo', + Title: 'Titolo', + Alternative_Text: 'Testo Alternativo', + Caption: 'Caption', + Summary: 'Summary', + Number_Of_Rows: 'Numero di Righe', + Number_Of_Cols: 'Numero di Colonne', + Submit: 'Invia', + Cancel: 'Cancella', + Choose: 'Scegli', + Preview: 'Anteprima', + Paste_From_Word: 'Incolla', + Tools: 'Tools', + Containers: 'Contenitori', + Classes: 'Classi', + Status: 'Stato', + Source_Code: 'Codice Sorgente' +}; + diff --git a/public/wymeditor/wymeditor/lang/ja.js b/public/wymeditor/wymeditor/lang/ja.js new file mode 100644 index 0000000..b00ef84 --- /dev/null +++ b/public/wymeditor/wymeditor/lang/ja.js @@ -0,0 +1,44 @@ +WYMeditor.STRINGS.ja = { + Strong: '強調', + Emphasis: '強調', + Superscript: '上付き', + Subscript: '下付き', + Ordered_List: '番号付きリスト', + Unordered_List: '番号無リスト', + Indent: 'インデントを増やす', + Outdent: 'インデントを減らす', + Undo: '元に戻す', + Redo: 'やり直す', + Link: 'リンク', + Unlink: 'リンク取消', + Image: '画像', + Table: 'テーブル', + HTML: 'HTML', + Paragraph: '段落', + Heading_1: '見出し 1', + Heading_2: '見出し 2', + Heading_3: '見出し 3', + Heading_4: '見出し 4', + Heading_5: '見出し 5', + Heading_6: '見出し 6', + Preformatted: '整形済みテキスト', + Blockquote: '引用文', + Table_Header: '表見出し', + URL: 'URL', + Title: 'タイトル', + Alternative_Text: '代替テキスト', + Caption: 'キャプション', + Summary: 'サマリー', + Number_Of_Rows: '行数', + Number_Of_Cols: '列数', + Submit: '送信', + Cancel: 'キャンセル', + Choose: '選択', + Preview: 'プレビュー', + Paste_From_Word: '貼り付け', + Tools: 'ツール', + Containers: 'コンテナ', + Classes: 'クラス', + Status: 'ステータス', + Source_Code: 'ソースコード' +}; diff --git a/public/wymeditor/wymeditor/lang/lt.js b/public/wymeditor/wymeditor/lang/lt.js new file mode 100644 index 0000000..b442655 --- /dev/null +++ b/public/wymeditor/wymeditor/lang/lt.js @@ -0,0 +1,45 @@ +WYMeditor.STRINGS.lt = { + Strong: 'Pusjuodis', + Emphasis: 'Kursyvas', + Superscript: 'Viršutinis indeksas', + Subscript: 'Apatinis indeksas', + Ordered_List: 'Numeruotas sąrašas', + Unordered_List: 'Suženklintas sąrašas', + Indent: 'Padidinti įtrauką', + Outdent: 'Sumažinti įtrauką', + Undo: 'Atšaukti', + Redo: 'Atstatyti', + Link: 'Nuoroda', + Unlink: 'Panaikinti nuorodą', + Image: 'Vaizdas', + Table: 'Lentelė', + HTML: 'HTML', + Paragraph: 'Paragrafas', + Heading_1: 'Antraštinis 1', + Heading_2: 'Antraštinis 2', + Heading_3: 'Antraštinis 3', + Heading_4: 'Antraštinis 4', + Heading_5: 'Antraštinis 5', + Heading_6: 'Antraštinis 6', + Preformatted: 'Formuotas', + Blockquote: 'Citata', + Table_Header: 'Lentelės antraštė', + URL: 'URL', + Title: 'Antraštinis tekstas', + Relationship: 'Sąryšis', + Alternative_Text: 'Alternatyvus tekstas', + Caption: 'Antraštė', + Summary: 'Santrauka', + Number_Of_Rows: 'Eilučių skaičius', + Number_Of_Cols: 'Stulpelių skaičius', + Submit: 'Išsaugoti', + Cancel: 'Nutraukti', + Choose: 'Rinktis', + Preview: 'Peržiūra', + Paste_From_Word: 'Įkelti iš MS Word', + Tools: 'Įrankiai', + Containers: 'Stiliai', + Classes: 'Klasės', + Status: 'Statusas', + Source_Code: 'Išeities tekstas' +}; \ No newline at end of file diff --git a/public/wymeditor/wymeditor/lang/nb.js b/public/wymeditor/wymeditor/lang/nb.js new file mode 100644 index 0000000..2ed2d95 --- /dev/null +++ b/public/wymeditor/wymeditor/lang/nb.js @@ -0,0 +1,45 @@ +WYMeditor.STRINGS.nb = { + Strong: 'Fet', + Emphasis: 'Uthevet', + Superscript: 'Opphøyet', + Subscript: 'Nedsenket', + Ordered_List: 'Nummerert liste', + Unordered_List: 'Punktliste', + Indent: 'Rykk inn', + Outdent: 'Rykk ut', + Undo: 'Angre', + Redo: 'Gjenta', + Link: 'Lenke', + Unlink: 'Ta bort lenken', + Image: 'Bilde', + Table: 'Tabell', + HTML: 'HTML', + Paragraph: 'Avsnitt', + Heading_1: 'Overskrift 1', + Heading_2: 'Overskrift 2', + Heading_3: 'Overskrift 3', + Heading_4: 'Overskrift 4', + Heading_5: 'Overskrift 5', + Heading_6: 'Overskrift 6', + Preformatted: 'Preformatert', + Blockquote: 'Sitat', + Table_Header: 'Tabelloverskrift', + URL: 'URL', + Title: 'Tittel', + Alternative_Text: 'Alternativ tekst', + Caption: 'Overskrift', + Summary: 'Sammendrag', + Number_Of_Rows: 'Antall rader', + Number_Of_Cols: 'Antall kolonner', + Submit: 'Ok', + Cancel: 'Avbryt', + Choose: 'Velg', + Preview: 'Forhåndsvis', + Paste_From_Word: 'Lim inn fra Word', + Tools: 'Verktøy', + Containers: 'Formatering', + Classes: 'Klasser', + Status: 'Status', + Source_Code: 'Kildekode' +}; + diff --git a/public/wymeditor/wymeditor/lang/nl.js b/public/wymeditor/wymeditor/lang/nl.js new file mode 100644 index 0000000..a7572f1 --- /dev/null +++ b/public/wymeditor/wymeditor/lang/nl.js @@ -0,0 +1,45 @@ +WYMeditor.STRINGS.nl = { + Strong: 'Sterk benadrukken', + Emphasis: 'Benadrukken', + Superscript: 'Bovenschrift', + Subscript: 'Onderschrift', + Ordered_List: 'Geordende lijst', + Unordered_List: 'Ongeordende lijst', + Indent: 'Inspringen', + Outdent: 'Terugspringen', + Undo: 'Ongedaan maken', + Redo: 'Opnieuw uitvoeren', + Link: 'Linken', + Unlink: 'Ontlinken', + Image: 'Afbeelding', + Table: 'Tabel', + HTML: 'HTML', + Paragraph: 'Paragraaf', + Heading_1: 'Kop 1', + Heading_2: 'Kop 2', + Heading_3: 'Kop 3', + Heading_4: 'Kop 4', + Heading_5: 'Kop 5', + Heading_6: 'Kop 6', + Preformatted: 'Voorgeformatteerd', + Blockquote: 'Citaat', + Table_Header: 'Tabel-kop', + URL: 'URL', + Title: 'Titel', + Relationship: 'Relatie', + Alternative_Text: 'Alternatieve tekst', + Caption: 'Bijschrift', + Summary: 'Summary', + Number_Of_Rows: 'Aantal rijen', + Number_Of_Cols: 'Aantal kolommen', + Submit: 'Versturen', + Cancel: 'Annuleren', + Choose: 'Kiezen', + Preview: 'Voorbeeld bekijken', + Paste_From_Word: 'Plakken uit Word', + Tools: 'Hulpmiddelen', + Containers: 'Teksttypes', + Classes: 'Klassen', + Status: 'Status', + Source_Code: 'Broncode' +}; \ No newline at end of file diff --git a/public/wymeditor/wymeditor/lang/nn.js b/public/wymeditor/wymeditor/lang/nn.js new file mode 100644 index 0000000..c7ea20c --- /dev/null +++ b/public/wymeditor/wymeditor/lang/nn.js @@ -0,0 +1,45 @@ +WYMeditor.STRINGS.nn = { + Strong: 'Feit', + Emphasis: 'Utheva', + Superscript: 'Opphøgd', + Subscript: 'Nedsenka', + Ordered_List: 'Nummerert liste', + Unordered_List: 'Punktliste', + Indent: 'Rykk inn', + Outdent: 'Rykk ut', + Undo: 'Angre', + Redo: 'Gjentaka', + Link: 'Lenkje', + Unlink: 'Ta bort lenkja', + Image: 'Bilete', + Table: 'Tabell', + HTML: 'HTML', + Paragraph: 'Avsnitt', + Heading_1: 'Overskrift 1', + Heading_2: 'Overskrift 2', + Heading_3: 'Overskrift 3', + Heading_4: 'Overskrift 4', + Heading_5: 'Overskrift 5', + Heading_6: 'Overskrift 6', + Preformatted: 'Preformatert', + Blockquote: 'Sitat', + Table_Header: 'Tabelloverskrift', + URL: 'URL', + Title: 'Tittel', + Alternative_Text: 'Alternativ tekst', + Caption: 'Overskrift', + Summary: 'Samandrag', + Number_Of_Rows: 'Tal på rader', + Number_Of_Cols: 'Tal på kolonnar', + Submit: 'Ok', + Cancel: 'Avbryt', + Choose: 'Vel', + Preview: 'Førehandsvis', + Paste_From_Word: 'Lim inn frå Word', + Tools: 'Verkty', + Containers: 'Formatering', + Classes: 'Klassar', + Status: 'Status', + Source_Code: 'Kjeldekode' +}; + diff --git a/public/wymeditor/wymeditor/lang/pl.js b/public/wymeditor/wymeditor/lang/pl.js new file mode 100644 index 0000000..cf01891 --- /dev/null +++ b/public/wymeditor/wymeditor/lang/pl.js @@ -0,0 +1,45 @@ +WYMeditor.STRINGS.pl = { + Strong: 'Nacisk', + Emphasis: 'Emfaza', + Superscript: 'Indeks górny', + Subscript: 'Indeks dolny', + Ordered_List: 'Lista numerowana', + Unordered_List: 'Lista wypunktowana', + Indent: 'Zwiększ wcięcie', + Outdent: 'Zmniejsz wcięcie', + Undo: 'Cofnij', + Redo: 'Ponów', + Link: 'Wstaw link', + Unlink: 'Usuń link', + Image: 'Obraz', + Table: 'Tabela', + HTML: 'Źródło HTML', + Paragraph: 'Akapit', + Heading_1: 'Nagłówek 1', + Heading_2: 'Nagłówek 2', + Heading_3: 'Nagłówek 3', + Heading_4: 'Nagłówek 4', + Heading_5: 'Nagłówek 5', + Heading_6: 'Nagłówek 6', + Preformatted: 'Preformatowany', + Blockquote: 'Cytat blokowy', + Table_Header: 'Nagłówek tabeli', + URL: 'URL', + Title: 'Tytuł', + Alternative_Text: 'Tekst alternatywny', + Caption: 'Tytuł tabeli', + Summary: 'Summary', + Number_Of_Rows: 'Liczba wierszy', + Number_Of_Cols: 'Liczba kolumn', + Submit: 'Wyślij', + Cancel: 'Anuluj', + Choose: 'Wybierz', + Preview: 'Podgląd', + Paste_From_Word: 'Wklej z Worda', + Tools: 'Narzędzia', + Containers: 'Format', + Classes: 'Styl', + Status: 'Status', + Source_Code: 'Kod źródłowy' +}; + diff --git a/public/wymeditor/wymeditor/lang/pt-br.js b/public/wymeditor/wymeditor/lang/pt-br.js new file mode 100644 index 0000000..2ec18fe --- /dev/null +++ b/public/wymeditor/wymeditor/lang/pt-br.js @@ -0,0 +1,45 @@ +WYMeditor.STRINGS['pt-br'] = { + Strong: 'Resaltar', + Emphasis: 'Enfatizar', + Superscript: 'Sobre escrito', + Subscript: 'Sub escrito ', + Ordered_List: 'Lista ordenada', + Unordered_List: 'Lista desordenada', + Indent: 'Indentado', + Outdent: 'Desidentar', + Undo: 'Desfazer', + Redo: 'Refazer', + Link: 'Link', + Unlink: 'Remover Link', + Image: 'Imagem', + Table: 'Tabela', + HTML: 'HTML', + Paragraph: 'Parágrafo', + Heading_1: 'Título 1', + Heading_2: 'Título 2', + Heading_3: 'Título 3', + Heading_4: 'Título 4', + Heading_5: 'Título 5', + Heading_6: 'Título 6', + Preformatted: 'Preformatado', + Blockquote: 'Citação', + Table_Header: 'Título de tabela', + URL: 'URL', + Title: 'Título', + Alternative_Text: 'Texto alternativo', + Caption: 'Legenda', + Summary: 'Summary', + Number_Of_Rows: 'Número de linhas', + Number_Of_Cols: 'Número de colunas', + Submit: 'Enviar', + Cancel: 'Cancelar', + Choose: 'Selecionar', + Preview: 'Previsualizar', + Paste_From_Word: 'Copiar do Word', + Tools: 'Ferramentas', + Containers: 'Conteneiners', + Classes: 'Classes', + Status: 'Estado', + Source_Code: 'Código fonte' +}; + diff --git a/public/wymeditor/wymeditor/lang/pt.js b/public/wymeditor/wymeditor/lang/pt.js new file mode 100644 index 0000000..7ea1d7c --- /dev/null +++ b/public/wymeditor/wymeditor/lang/pt.js @@ -0,0 +1,45 @@ +WYMeditor.STRINGS.pt = { + Strong: 'Negrito', + Emphasis: 'Itálico', + Superscript: 'Sobrescrito', + Subscript: 'Subsescrito', + Ordered_List: 'Lista Numerada', + Unordered_List: 'Lista Marcada', + Indent: 'Aumentar Indentaçã', + Outdent: 'Diminuir Indentaçã', + Undo: 'Desfazer', + Redo: 'Restaurar', + Link: 'Link', + Unlink: 'Tirar link', + Image: 'Imagem', + Table: 'Tabela', + HTML: 'HTML', + Paragraph: 'Parágrafo', + Heading_1: 'Título 1', + Heading_2: 'Título 2', + Heading_3: 'Título 3', + Heading_4: 'Título 4', + Heading_5: 'Título 5', + Heading_6: 'Título 6', + Preformatted: 'Pré-formatado', + Blockquote: 'Citação', + Table_Header: 'Cabeçalho Tabela', + URL: 'URL', + Title: 'Título', + Alternative_Text: 'Texto Alterativo', + Caption: 'Título Tabela', + Summary: 'Summary', + Number_Of_Rows: 'Número de Linhas', + Number_Of_Cols: 'Número de Colunas', + Submit: 'Enviar', + Cancel: 'Cancelar', + Choose: 'Escolha', + Preview: 'Prever', + Paste_From_Word: 'Colar do Word', + Tools: 'Ferramentas', + Containers: 'Containers', + Classes: 'Classes', + Status: 'Status', + Source_Code: 'Código Fonte' +}; + diff --git a/public/wymeditor/wymeditor/lang/ru.js b/public/wymeditor/wymeditor/lang/ru.js new file mode 100644 index 0000000..368a90c --- /dev/null +++ b/public/wymeditor/wymeditor/lang/ru.js @@ -0,0 +1,45 @@ +WYMeditor.STRINGS.ru = { + Strong: 'Жирный', + Emphasis: 'Наклонный', + Superscript: 'Надстрочный', + Subscript: 'Подстрочный', + Ordered_List: 'Нумерованый список', + Unordered_List: 'Ненумерованый список', + Indent: 'Увеличить отступ', + Outdent: 'Уменьшить отступ', + Undo: 'Отменить', + Redo: 'Повторить', + Link: 'Ссылка', + Unlink: 'Удалить ссылку', + Image: 'Изображение', + Table: 'Таблица', + HTML: 'Править HTML', + Paragraph: 'Параграф', + Heading_1: 'Заголовок 1', + Heading_2: 'Заголовок 2', + Heading_3: 'Заголовок 3', + Heading_4: 'Заголовок 4', + Heading_5: 'Заголовок 5', + Heading_6: 'Заголовок 6', + Preformatted: 'Preformatted', + Blockquote: 'Цитата', + Table_Header: 'Заголовок таблицы', + URL: 'URL', + Title: 'Заголовок', + Alternative_Text: 'Альтернативный текст', + Caption: 'Надпись', + Summary: 'Summary', + Number_Of_Rows: 'Кол-во строк', + Number_Of_Cols: 'Кол-во столбцов', + Submit: 'Отправить', + Cancel: 'Отмена', + Choose: 'Выбор', + Preview: 'Просмотр', + Paste_From_Word: 'Вставить из Word', + Tools: 'Инструменты', + Containers: 'Контейнеры', + Classes: 'Классы', + Status: 'Статус', + Source_Code: 'Исходный код' +}; + diff --git a/public/wymeditor/wymeditor/lang/sk.js b/public/wymeditor/wymeditor/lang/sk.js new file mode 100644 index 0000000..83da729 --- /dev/null +++ b/public/wymeditor/wymeditor/lang/sk.js @@ -0,0 +1,45 @@ +WYMeditor.STRINGS.sk = { + Strong: 'Tučné', + Emphasis: 'Kurzíva', + Superscript: 'Horný index', + Subscript: 'Dolný index', + Ordered_List: 'Číslovaný zoznam', + Unordered_List: 'Nečíslovaný zoznam', + Indent: 'Zväčšiť odsadenie', + Outdent: 'Zmenšiť odsadenie', + Undo: 'Vrátiť', + Redo: 'Opakovať', + Link: 'Vytvoriť odkaz', + Unlink: 'Zrušiť odkaz', + Image: 'Obrázok', + Table: 'Tabuľka', + HTML: 'HTML', + Paragraph: 'Odstavec', + Heading_1: 'Nadpis 1. úrovne', + Heading_2: 'Nadpis 2. úrovne', + Heading_3: 'Nadpis 3. úrovne', + Heading_4: 'Nadpis 4. úrovne', + Heading_5: 'Nadpis 5. úrovne', + Heading_6: 'Nadpis 6. úrovne', + Preformatted: 'Predformátovaný text', + Blockquote: 'Citácia', + Table_Header: 'Hlavička tabuľky', + URL: 'URL adresa', + Title: 'Titulok', + Alternative_Text: 'Alternatívny text', + Caption: 'Titulok tabuľky', + Summary: 'Zhrnutie obsahu', + Number_Of_Rows: 'Počet riadkov', + Number_Of_Cols: 'Počet stĺpcov', + Submit: 'Odoslať', + Cancel: 'Zrušiť', + Choose: 'Vybrať', + Preview: 'Náhľad', + Paste_From_Word: 'Vložiť z Wordu', + Tools: 'Nástroje', + Containers: 'Typy obsahu', + Classes: 'Triedy', + Status: 'Stav', + Source_Code: 'Zdrojový kód' +}; + diff --git a/public/wymeditor/wymeditor/lang/sv.js b/public/wymeditor/wymeditor/lang/sv.js new file mode 100644 index 0000000..34081bc --- /dev/null +++ b/public/wymeditor/wymeditor/lang/sv.js @@ -0,0 +1,46 @@ +WYMeditor.STRINGS.sv = { + Strong: 'Viktigt', + Emphasis: 'Betoning', + Superscript: 'Upphöjt', + Subscript: 'Nedsänkt', + Ordered_List: 'Nummerlista', + Unordered_List: 'Punktlista', + Indent: 'Indrag', + Outdent: 'Utdrag', + Undo: 'Ångra', + Redo: 'Gör om', + Link: 'Länk', + Unlink: 'Ta bort länk', + Image: 'Bild', + Table: 'Tabell', + HTML: 'HTML', + Paragraph: 'Paragraf', + Heading_1: 'Rubrik 1', + Heading_2: 'Rubrik 2', + Heading_3: 'Rubrik 3', + Heading_4: 'Rubrik 4', + Heading_5: 'Rubrik 5', + Heading_6: 'Rubrik 6', + Preformatted: 'Förformaterad', + Blockquote: 'Blockcitat', + Table_Header: 'Tabellrubrik', + URL: 'URL', + Title: 'Titel', + Relationship: 'Relation', + Alternative_Text: 'Alternativ text', + Caption: 'Överskrift', + Summary: 'Summary', + Number_Of_Rows: 'Antal rader', + Number_Of_Cols: 'Antal kolumner', + Submit: 'Skicka', + Cancel: 'Avbryt', + Choose: 'Välj', + Preview: 'Förhandsgranska', + Paste_From_Word: 'Klistra in från Word', + Tools: 'Verktyg', + Containers: 'Formatering', + Classes: 'Klasser', + Status: 'Status', + Source_Code: 'Källkod' +}; + diff --git a/public/wymeditor/wymeditor/lang/tr.js b/public/wymeditor/wymeditor/lang/tr.js new file mode 100644 index 0000000..db7981b --- /dev/null +++ b/public/wymeditor/wymeditor/lang/tr.js @@ -0,0 +1,45 @@ +WYMeditor.STRINGS.tr = { + Strong: 'Kalın', + Emphasis: 'Vurgu', + Superscript: 'Üstsimge', + Subscript: 'Altsimge', + Ordered_List: 'Sıralı List', + Unordered_List: 'Sırasız List', + Indent: 'Girintile', + Outdent: 'Çıkıntıla', + Undo: 'Geri Al', + Redo: 'Yinele', + Link: 'Bağlantı', + Unlink: 'Bağlantıyı Kaldır', + Image: 'Resim', + Table: 'Tablo', + HTML: 'HTML', + Paragraph: 'Parağraf', + Heading_1: 'Başlık 1', + Heading_2: 'Başlık 2', + Heading_3: 'Başlık 3', + Heading_4: 'Başlık 4', + Heading_5: 'Başlık 5', + Heading_6: 'Başlık 6', + Preformatted: 'Önceden Formatlı', + Blockquote: 'Alıntı', + Table_Header: 'Tablo Başlığı', + URL: 'URL', + Title: 'Başlık', + Alternative_Text: 'Alternatif Metin', + Caption: 'Etiket', + Summary: 'Özet', + Number_Of_Rows: 'Satır sayısı', + Number_Of_Cols: 'Sütun sayısı', + Submit: 'Gönder', + Cancel: 'İptal', + Choose: 'Seç', + Preview: 'Önizleme', + Paste_From_Word: 'Word\'den yapıştır', + Tools: 'Araçlar', + Containers: 'Kapsayıcılar', + Classes: 'Sınıflar', + Status: 'Durum', + Source_Code: 'Kaynak Kodu' +}; + diff --git a/public/wymeditor/wymeditor/lang/zh_cn.js b/public/wymeditor/wymeditor/lang/zh_cn.js new file mode 100644 index 0000000..3e35678 --- /dev/null +++ b/public/wymeditor/wymeditor/lang/zh_cn.js @@ -0,0 +1,47 @@ +WYMeditor.STRINGS.zh_cn = { + Strong: '加粗', + Emphasis: '斜体', + Superscript: '上标', + Subscript: '下标', + Ordered_List: '有序列表', + Unordered_List: '无序列表', + Indent: '增加缩进', + Outdent: '减少缩进', + Undo: '撤消', + Redo: '重做', + Link: '链接', + Unlink: '取消链接', + Image: '图片', + Table: '表格', + HTML: 'HTML源代码', + Paragraph: '段落', + Heading_1: '标题 1', + Heading_2: '标题 2', + Heading_3: '标题 3', + Heading_4: '标题 4', + Heading_5: '标题 5', + Heading_6: '标题 6', + Preformatted: '原始文本', + Blockquote: '引语', + Table_Header: '表头', + URL: '地址', + Title: '提示文字', + Alternative_Text: '失效文字', + Caption: '标题', + Summary: 'Summary', + Number_Of_Rows: '行数', + Number_Of_Cols: '列数', + Submit: '提交', + Cancel: '放弃', + Choose: '选择', + Preview: '预览', + Paste_From_Word: '从Word粘贴纯文本', + Tools: '工具', + Containers: '容器', + Classes: '预定义样式', + Status: '状态', + Source_Code: '源代码', + Attachment: '附件', + NewParagraph: '新段落' +}; + diff --git a/public/wymeditor/wymeditor/plugins/embed/jquery.wymeditor.embed.js b/public/wymeditor/wymeditor/plugins/embed/jquery.wymeditor.embed.js new file mode 100644 index 0000000..79daa16 --- /dev/null +++ b/public/wymeditor/wymeditor/plugins/embed/jquery.wymeditor.embed.js @@ -0,0 +1,82 @@ +/** + * WYMeditor : what you see is What You Mean web-based editor + * Copyright (c) 2005 - 2009 Jean-Francois Hovinne, http://www.wymeditor.org/ + * Dual licensed under the MIT (MIT-license.txt) + * and GPL (GPL-license.txt) licenses. + * + * For further information visit: + * http://www.wymeditor.org/ + * + * File Name: + * jquery.wymeditor.embed.js + * Experimental embed plugin + * + * File Authors: + * Jonatan Lundin (jonatan.lundin a-t gmail dotcom) + * Roger Hu (roger.hu a-t gmail dotcom) + * Scott Nixon (citadelgrad a-t gmail dotcom) + */ + +(function () { + function removeItem(item, arr) { + for (var i = arr.length; i--;) { + if (arr[i] === item) { + arr.splice(i, 1); + } + } + return arr; + } + if (WYMeditor && WYMeditor.XhtmlValidator._tags.param.attributes) { + + WYMeditor.XhtmlValidator._tags.embed = { + "attributes":[ + "allowscriptaccess", + "allowfullscreen", + "height", + "src", + "type", + "width" + ] + }; + + WYMeditor.XhtmlValidator._tags.param.attributes = { + '0':'name', + '1':'type', + 'valuetype':/^(data|ref|object)$/, + '2':'valuetype', + '3':'value' + }; + + WYMeditor.XhtmlValidator._tags.iframe = { + "attributes":[ + "allowfullscreen", + "width", + "height", + "src", + "title", + "frameborder" + ] + }; + + // Override the XhtmlSaxListener to allow param, embed and iframe. + // + // We have to do an explicit override + // of the function instead of just changing the startup parameters + // because those are only used on creation, and changing them after + // the fact won't affect the existing XhtmlSaxListener + var XhtmlSaxListener = WYMeditor.XhtmlSaxListener; + WYMeditor.XhtmlSaxListener = function () { + var listener = XhtmlSaxListener.call(this); + // param, embed and iframe should be inline tags so that they can + // be nested inside other elements + removeItem('param', listener.block_tags); + listener.inline_tags.push('param'); + listener.inline_tags.push('embed'); + listener.inline_tags.push('iframe'); + + return listener; + }; + + WYMeditor.XhtmlSaxListener.prototype = XhtmlSaxListener.prototype; + } +})(); diff --git a/public/wymeditor/wymeditor/plugins/fullscreen/icon_fullscreen.gif b/public/wymeditor/wymeditor/plugins/fullscreen/icon_fullscreen.gif new file mode 100644 index 0000000..d2a8b0a Binary files /dev/null and b/public/wymeditor/wymeditor/plugins/fullscreen/icon_fullscreen.gif differ diff --git a/public/wymeditor/wymeditor/plugins/fullscreen/jquery.wymeditor.fullscreen.js b/public/wymeditor/wymeditor/plugins/fullscreen/jquery.wymeditor.fullscreen.js new file mode 100644 index 0000000..6b2c071 --- /dev/null +++ b/public/wymeditor/wymeditor/plugins/fullscreen/jquery.wymeditor.fullscreen.js @@ -0,0 +1,126 @@ +/* + * WYMeditor : what you see is What You Mean web-based editor + * Copyright (c) 2005 - 2009 Jean-Francois Hovinne, http://www.wymeditor.org/ + * Dual licensed under the MIT (MIT-license.txt) + * and GPL (GPL-license.txt) licenses. + * + * For further information visit: + * http://www.wymeditor.org/ + * + * File Name: + * jquery.wymeditor.fullscreen.js + * Fullscreen plugin for WYMeditor + * + * File Authors: + * Luis Santos (luis.santos a-t openquest dotpt) + * Jonatan Lundin (jonatan.lundin a-t gmail dotcom) + * Gerd Riesselmann (gerd a-t gyro-php dot org) : Fixed issue with new skin layout + * Philipp Cordes (pc a-t irgendware dotnet) + */ + +//Extend WYMeditor +WYMeditor.editor.prototype.fullscreen = function() { + var wym = this, + $box = jQuery(this._box), + $iframe = jQuery(this._iframe), + $overlay = null, + $window = jQuery(window), + + editorMargin = 15; // Margin from window (without padding) + + + //construct the button's html + var html = '' + + "
      3. " + + "" + + "Fullscreen" + + "" + + "
      4. "; + //add the button to the tools box + $box.find(wym._options.toolsSelector + wym._options.toolsListSelector) + .append(html); + + function resize () { + // Calculate margins + var uiHeight = $box.outerHeight(true) - $iframe.outerHeight(true); + var editorPadding = $box.outerWidth() - $box.width(); + + // Calculate heights + var screenHeight = $window.height(); + var iframeHeight = (screenHeight - uiHeight - (editorMargin * 2)) + 'px'; + + // Calculate witdths + var screenWidth = $window.width(); + var boxWidth = (screenWidth - editorPadding - (editorMargin * 2)) + 'px'; + + $box.css('width', boxWidth); + $iframe.css('height', iframeHeight); + $overlay.css({ + 'height': screenHeight + 'px', + 'width': screenWidth + 'px' + }); + } + + //handle click event + $box.find('li.wym_tools_fullscreen a').click(function() { + if ($box.css('position') != 'fixed') { + // Store previous inline styles + $box.data('wym-inline-css', $box.attr('style')); + $iframe.data('wym-inline-css', $iframe.attr('style')); + + // Create overlay + $overlay = jQuery('
        ') + .appendTo('body').css({ + 'position': 'fixed', + 'background-color': 'rgb(0, 0, 0)', + 'opacity': '0.75', + 'z-index': '98', + 'top': '0px', + 'left': '0px' + }); + + // Possition the editor + $box.css({ + 'position': 'fixed', + 'z-index': '99', + 'top': editorMargin + 'px', + 'left': editorMargin + 'px' + }); + + // Bind event listeners + $window.bind('resize', resize); + $box.find('li.wym_tools_html a').bind('click', resize); + + // Force resize + resize(); + } else { + // Unbind event listeners + $window.unbind('resize', resize); + $box.find('li.wym_tools_html a').unbind('click', resize); + + // Remove inline styles + $box.css({ + 'position': 'static', + 'z-index': '', + 'width': '', + 'top': '', + 'left': '' + }); + $iframe.css('height', ''); + + // Remove overlay + $overlay.remove(); + $overlay = null; + + // Retore previous inline styles + $box.attr('style', $box.data('wym-inline-css')); + $iframe.attr('style', $iframe.data('wym-inline-css')); + } + + return false; + }); +}; diff --git a/public/wymeditor/wymeditor/plugins/hovertools/jquery.wymeditor.hovertools.js b/public/wymeditor/wymeditor/plugins/hovertools/jquery.wymeditor.hovertools.js new file mode 100644 index 0000000..ba55c17 --- /dev/null +++ b/public/wymeditor/wymeditor/plugins/hovertools/jquery.wymeditor.hovertools.js @@ -0,0 +1,49 @@ +/*jslint evil: true */ +/** + WYMeditor.hovertools + ==================== + + A hovertools plugin. +*/ + +WYMeditor.editor.prototype.hovertools = function() { + var wym = this; + + wym.status(' '); + + // Bind events on buttons + jQuery(this._box).find(this._options.toolSelector).hover( + function() { + wym.status(jQuery(this).html()); + }, + function() { + wym.status(' '); + } + ); + + // Classes: add/remove a style attr to matching elems + // while mouseover/mouseout + jQuery(this._box).find(this._options.classSelector).hover( + function() { + var aClasses = eval(wym._options.classesItems); + var sName = jQuery(this).attr(WYMeditor.NAME); + var oClass = WYMeditor.Helper.findByName(aClasses, sName); + + if (oClass){ + jqexpr = oClass.expr; + // Don't use jQuery.find() on the iframe body + // because of MSIE + jQuery + expando issue (#JQ1143) + if (!jQuery.browser.msie) { + jQuery(wym._doc).find(jqexpr).css('background-color','#cfc'); + } + } + }, + function() { + // Don't use jQuery.find() on the iframe body + // because of MSIE + jQuery + expando issue (#JQ1143) + if (!jQuery.browser.msie) { + jQuery(wym._doc).find('*').removeAttr('style'); + } + } + ); +}; diff --git a/public/wymeditor/wymeditor/plugins/list/jquery.wymeditor.list.js b/public/wymeditor/wymeditor/plugins/list/jquery.wymeditor.list.js new file mode 100644 index 0000000..6f43c89 --- /dev/null +++ b/public/wymeditor/wymeditor/plugins/list/jquery.wymeditor.list.js @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2011 PolicyStat LLC. + * MIT licensed (MIT-license.txt) + * + * This plugin adds the ability to use tab and shift+tab to indent/outdent + * lists, mimicking a user's expected behavior when inside an editor. + * + * @author Wes Winham (winhamwr@gmail.com) + */ + +function ListPlugin(options, wym) { + this._options = jQuery.extend({}, options); + this._wym = wym; + + this.init(); +} + +ListPlugin.prototype.init = function() { + this._wym.listPlugin = this; + + this.bindEvents(); +}; + +ListPlugin.prototype.bindEvents = function() { + var listPlugin = this; + var wym = this._wym; + + // Bind a key listener so we can handle tabs + // With jQuery 1.3, live() can be used to simplify handler logic + jQuery(wym._doc).bind('keydown', listPlugin.handleKeyDown); +}; + +/** + * Handle any tab presses when inside list items and indent/outdent. + */ +ListPlugin.prototype.handleKeyDown = function(evt) { + //'this' is the editor._doc + var wym = WYMeditor.INSTANCES[this.title]; + var listPlugin = wym.listPlugin; + + var container = wym.selected(); + var name = container.tagName.toLowerCase(); + // We only care about tabs when we're inside a list + if (name != "li") { + return null; + } + + // Handle tab presses + if (evt.which == WYMeditor.KEY.TAB) { + if (evt.shiftKey) { + wym.exec(WYMeditor.OUTDENT); + return false; // Short-circuit normal tab behavior + } else { + wym.exec(WYMeditor.INDENT); + return false; + } + } + + return null; +}; diff --git a/public/wymeditor/wymeditor/plugins/rdfa/jquery.wymeditor.rdfa.js b/public/wymeditor/wymeditor/plugins/rdfa/jquery.wymeditor.rdfa.js new file mode 100644 index 0000000..89cece6 --- /dev/null +++ b/public/wymeditor/wymeditor/plugins/rdfa/jquery.wymeditor.rdfa.js @@ -0,0 +1,186 @@ +/* + * WYMeditor : what you see is What You Mean web-based editor + * Copyright (c) 2005 - 2011 Jean-Francois Hovinne, http://www.wymeditor.org/ + * Dual licensed under the MIT (MIT-license.txt) + * and GPL (GPL-license.txt) licenses. + * + * For further information visit: + * http://www.wymeditor.org/ + * + * File Name: + * jquery.wymeditor.rdfa.js + * RDFa plugin for WYMeditor + * + * File Authors: + * Jean-Francois Hovinne (@jfhovinne) + */ + +//Extend WYMeditor +WYMeditor.editor.prototype.rdfa = function (options) { + var rdfa = new WYMeditor.RDFa(options, this); + return rdfa; +}; + +//RDFa constructor +WYMeditor.RDFa = function (options, wym) { + options = jQuery.extend({ + setStdNameSpaces: true, + extendXHTMLParser: true, + buttons: {} + }, options); + + this._options = options; + this._wym = wym; + this.init(); +}; + +//RDFa plugin init +WYMeditor.RDFa.prototype.init = function () { + if (this._options.setStdNameSpaces) { + this.setStdNameSpaces(); + } + if (this._options.extendXHTMLParser) { + this.extendXHTMLParser(); + } + this.setButtons(); +}; + +//Adding the namespaces to the document +WYMeditor.RDFa.prototype.setStdNameSpaces = function () { + this.addNameSpace('xmlns', 'http://www.w3.org/1999/xhtml'); + this.addNameSpace('version', 'XHTML+RDFa 1.0'); +}; + +WYMeditor.RDFa.prototype.addNameSpace = function (attr, value) { + jQuery('html', this._wym._doc) + .attr(attr, value); +}; + +WYMeditor.RDFa.prototype.extendXHTMLParser = function () { + this.extendAttributes(); + this.setStdVocabularies(); + this.extendLinkAttributes(); +}; + +WYMeditor.RDFa.prototype.extendAttributes = function () { + //Add the RDFa attributes + WYMeditor.XhtmlValidator._attributes.core.attributes.push( + 'rel', + 'rev', + 'content', + 'href', + 'src', + 'about', + 'property', + 'resource', + 'datatype', + 'typeof' + ); +}; + +WYMeditor.RDFa.prototype.setStdVocabularies = function () { + var _this = this; + //Add the 'standard' vocabularies + vocabularies = [ + 'xmlns:biblio', + 'xmlns:cc', + 'xmlns:dbp', + 'xmlns:dbr', + 'xmlns:dc', + 'xmlns:ex', + 'xmlns:foaf', + 'xmlns:rdf', + 'xmlns:rdfs', + 'xmlns:taxo', + 'xmlns:xhv', + 'xmlns:xsd' + ]; + jQuery.each(vocabularies, function (index, vocabulary) { + _this.addVocabulary(vocabulary); + }); +}; + +WYMeditor.RDFa.prototype.addVocabulary = function (vocabulary) { + WYMeditor.XhtmlValidator._attributes.core.attributes.push(vocabulary); +}; + +WYMeditor.RDFa.prototype.extendLinkAttributes = function () { + //Overwrite the attributes 'rel' and 'rev' + WYMeditor.XhtmlValidator._tags.a = { + "attributes": { + "0": "charset", + "1": "coords", + "2": "href", + "3": "hreflang", + "4": "name", + "5": "rel", + "6": "rev", + "shape": /^(rect|rectangle|circ|circle|poly|polygon)$/, + "7": "type" + } + }; +}; + +WYMeditor.RDFa.prototype.setButtons = function () { + var _this = this, + list = jQuery(this._wym._box).find('div.wym_classes ul'); + jQuery.each(this._options.buttons, function (index, button) { + list + .append('
      5. ') + .children(':last') + .append('
        ') + .children(':last') + .attr('href', '#') + .text(button.title) + .bind('click', + { + instance: _this._wym, + button: button, + ns: button.ns, + attr: button.attr, + value: button.value + }, + _this.clickButtonHandler); + }); +}; + +WYMeditor.RDFa.prototype.clickButtonHandler = function (evt) { + var wym = evt.data.instance, + selected = wym.selected(); + + //the attribute already exists, remove it + if (typeof jQuery(selected).attr(evt.data.attr) !== 'undefined' && + jQuery(selected).attr(evt.data.attr) !== '') { + WYMeditor.console.log( + 'attribute already exists, remove it:', + evt.data.attr, + jQuery(selected).attr(evt.data.attr) + ); + jQuery(selected) + .removeAttr(evt.data.attr) + .removeClass(evt.data.ns) + .removeClass(evt.data.attr) + .removeClass(evt.data.value); + + //else, add it + } else { + WYMeditor.console.log('attribute does not exist, add it:', evt.data.attr, evt.data.value); + if (evt.data.value) { //value available + jQuery(selected) + .attr(evt.data.attr, evt.data.ns + ':' + evt.data.value) + .addClass(evt.data.ns) + .addClass(evt.data.attr) + .addClass(evt.data.value); + } else { //value not available + evt.data.value = prompt('Value', ''); + if (evt.data.value !== null) { + jQuery(selected) + .attr(evt.data.attr, evt.data.value) + .addClass(evt.data.ns) + .addClass(evt.data.attr) + .addClass(evt.data.value); + } + } + } + return false; +}; diff --git a/public/wymeditor/wymeditor/plugins/resizable/jquery.wymeditor.resizable.js b/public/wymeditor/wymeditor/plugins/resizable/jquery.wymeditor.resizable.js new file mode 100644 index 0000000..cb6032d --- /dev/null +++ b/public/wymeditor/wymeditor/plugins/resizable/jquery.wymeditor.resizable.js @@ -0,0 +1,77 @@ +/* + * WYMeditor : what you see is What You Mean web-based editor + * Copyright (c) 2005 - 2009 Jean-Francois Hovinne, http://www.wymeditor.org/ + * Dual licensed under the MIT (MIT-license.txt) + * and GPL (GPL-license.txt) licenses. + * + * For further information visit: + * http://www.wymeditor.org/ + * + * File Name: + * jquery.wymeditor.resizable.js + * resize plugin for WYMeditor + * + * File Authors: + * Peter Eschler (peschler _at_ gmail.com) + * Jean-Francois Hovinne - http://www.hovinne.com/ + * + * Version: + * 0.4 + * + * Changelog: + * + * 0.4 + * - Removed UI and UI.resizable scripts loading - see #167 (jfh). + * + * 0.3 + * - Added 'iframeOriginalSize' and removed 'ui.instance' calls (jfh). + * + * 0.2 + * - Added full support for all jQueryUI resizable plugin options. + * - Refactored and documented code. + * 0.1 + * - Initial release. + */ + +/** + * The resizable plugin makes the wymeditor box vertically resizable. + * It it based on the ui.resizable.js plugin of the jQuery UI library. + * + * The WYMeditor resizable plugin supports all parameters of the jQueryUI + * resizable plugin. The parameters are passed like this: + * + * wym.resizable({ handles: "s,e", + * maxHeight: 600 }); + * + * DEPENDENCIES: jQuery UI, jQuery UI resizable + * + * @param options options for the plugin + */ +WYMeditor.editor.prototype.resizable = function(options) { + + var wym = this; + var $iframe = jQuery(wym._box).find('iframe'); + var $iframe_div = jQuery(wym._box).find('.wym_iframe'); + var iframeOriginalSize = {}; + + // Define some default options + var default_options = { + resize: function() { + $iframe_div.height($iframe.height()); + }, + alsoResize: $iframe, + handles: "s,e,se", + minHeight: 250 + }; + + // Merge given options with default options. Given options override + // default ones. + var final_options = jQuery.extend(default_options, options); + + if (jQuery.isFunction(jQuery.fn.resizable)) { + jQuery(wym._box).resizable(final_options); + } else { + WYMeditor.console.error('Oops, jQuery UI.resizable unavailable.'); + } + +}; diff --git a/public/wymeditor/wymeditor/plugins/resizable/readme.txt b/public/wymeditor/wymeditor/plugins/resizable/readme.txt new file mode 100644 index 0000000..2a0444e --- /dev/null +++ b/public/wymeditor/wymeditor/plugins/resizable/readme.txt @@ -0,0 +1,124 @@ + + +resizable plugin for WYMeditor +############################## + +The ``resizable`` plugin for WYMeditor_ enables vertical resizing of the +editor area. The plugin is based on the jQuery UI library. + +Requirements +============ +The following packages are required for using the WYMeditor ``resizable`` +plugin: + +* jQuery (tested with jQuery ``jquery-1.2.4a.js`` from ``jquery.ui`` package) +* WYMeditor SVN trunk (Revision: 482) +* jQuery-UI (tested with ``jquery.ui-1.5b2``) + +It should be possible to use this plugin with ``WYMeditor-0.4`` but I have not +tried. + +Download +======== +You can download the WYMeditor ``resizable`` plugin here: + +* wymeditor-resizable-plugin-0.2.tgz_ +* wymeditor-resizable-plugin-0.1.tgz_ + +See the Changelog_ for more infos about the releases. + +.. _wymeditor-resizable-plugin-0.2.tgz: http://pyjax.net/download/wymeditor-resizable-plugin-0.2.tgz +.. _wymeditor-resizable-plugin-0.1.tgz: http://pyjax.net/download/wymeditor-resizable-plugin-0.1.tgz + +Installation +============ +Just extract the downloaded archive into your WYMeditor's ``plugin`` +directory. + +Usage +===== +For general instructions on WYMeditor plugins please refer to the `WYMeditor +plugin page`_. + +To use the ``resizable`` plugin simply include the plugin's JavaScript file in +your code. You **do not** need to include the jQuery UI files - this is done +automatically by the plugin (see `Internals`_):: + + + +Make sure to adjust the ``src`` attribute to your needs, then initialize the +plugin in WYMeditor's ``postInit`` function:: + + wymeditor({postInit: function(wym) { + wym.hovertools(); // other plugins... + wym.resizable({handles: "s,e", + maxHeight: 600}); + } + }) + +The ``resizable`` plugin takes exactly one parameter, which is an object literal +containing the options of the plugin. The WYMeditor ``resizable`` plugin +supports all options of the jQuery UI ``resizable`` plugin. These are the +default values used by the plugin:: + + handles: "s,e,se", + minHeight: 250, + maxHeight: 600 + +See the `jQuery UI resizable plugin docs`_ for a list of all options. + +That's it! You are now able to resize the WYMeditor vertically, horizontally or +both, depending on your options. + +.. _jQuery UI resizable plugin docs: http://docs.jquery.com/UI/Resizables + +Internals +========= +The plugin takes care of loading the necessary jQuery UI files (``base`` and +``resizable``) from the same path the jQuery library was loaded. Here's how +it's done:: + + // Get the jQuery path from the editor, stripping away the jQuery file. + // see http://www.oreilly.com/catalog/regex/chapter/ch04.html + // The match result array contains the path and the filename. + var jQueryPath = wym.computeJqueryPath().match(/^(.*)\/(.*)$/)[1]; + + // Make an array of the external JavaScript files required by the plugin. + var jQueryPlugins = [jQueryPath + '/ui.base.js', + jQueryPath + '/ui.resizable.js']; + + // First get the jQuery UI base file + $.getScript(jQueryPlugins[0]); + + // Get the jQuery UI resizeable plugin and then init the wymeditor resizable + // plugin. It is import to do the initialisation after loading the + // necessary jQuery UI files has finished, otherwise the "resizable" method + // would not be available. + $.getScript(jQueryPlugins[1], function() { + jQuery(wym._box).resizable(final_options); + }); + +An alternative approach would be to use an AJAX queue when getting the script +files to ensure that all jQuery files are loaded before the initialisation code +of the plugin is executed. There is an `jQuery AJAX queue plugin`_ which does +that. + +.. _jQuery AJAX queue plugin: http://plugins.jquery.com/project/ajaxqueue + +Changelog +========= + +0.2 +--- +- Added full support for all jQuery UI resizable plugin options. +- Refactored and documented code. +- Now contains a packed version (775 bytes). + +0.1 +--- +- Initial release. + +.. _WYMeditor: http://www.wymeditor.org/ +.. _WYMeditor plugin page: http://trac.wymeditor.org/trac/wiki/0.4/Plugins diff --git a/public/wymeditor/wymeditor/plugins/structured_headings/jquery.wymeditor.structured_headings.js b/public/wymeditor/wymeditor/plugins/structured_headings/jquery.wymeditor.structured_headings.js new file mode 100644 index 0000000..95118c9 --- /dev/null +++ b/public/wymeditor/wymeditor/plugins/structured_headings/jquery.wymeditor.structured_headings.js @@ -0,0 +1,615 @@ +// In case the script is included on a page without WYMeditor, define the +// WYMeditor and WYMeditor.editor objects to hold the constants used. +if (typeof (WYMeditor) === 'undefined') { + WYMeditor = {}; + WYMeditor.HEADING_ELEMENTS = ["h1", "h2", "h3", "h4", "h5", "h6"]; + WYMeditor.KEY = { + BACKSPACE: 8, + ENTER: 13, + DELETE: 46 + }; +} +if (typeof (WYMeditor.editor) === 'undefined') { + WYMeditor.editor = {}; + WYMeditor.editor.prototype = {}; +} + +WYMeditor.STRUCTURED_HEADINGS_POLYFILL_REQUIRED = + jQuery.browser.msie && parseInt(jQuery.browser.version, 10) < 8.0; + +// Constants for class names used in structuring the headings +WYMeditor.STRUCTURED_HEADINGS_START_NODE_CLASS = 'wym-structured-headings-start'; +WYMeditor.STRUCTURED_HEADINGS_LEVEL_CLASSES = ['wym-structured-heading-level1', + 'wym-structured-heading-level2', + 'wym-structured-heading-level3', + 'wym-structured-heading-level4', + 'wym-structured-heading-level5', + 'wym-structured-heading-level6']; +WYMeditor.STRUCTURED_HEADINGS_NUMBERING_SPAN_CLASS = 'wym-structured-heading-numbering'; + +// Key codes for the keyup events that the heading numberings should be +// recalculated on +WYMeditor.STRUCTURED_HEADINGS_POTENTIAL_HEADING_MODIFICATION_KEYS = + [WYMeditor.KEY.BACKSPACE, WYMeditor.KEY.DELETE, WYMeditor.KEY.ENTER]; + +/** + structuredHeadings + ================== + + Construct and return a heading structure object using the given options + object. This should be called in the `postInit` function when initializing + a wymeditor instance. + + @param options A configuration object. +*/ +WYMeditor.editor.prototype.structuredHeadings = function (options) { + var structuredHeadingsManager = new StructuredHeadingsManager(options, this); + this.structuredHeadingsManager = structuredHeadingsManager; + + return structuredHeadingsManager; +}; + +/** + StructuredHeadingsManager + ========================= + + A heading structure management object that makes it easier for a user to + structure the headings in a document by simplifying the user interface and + adding features such as heading numbering. + + @param options A configuration object. + @param wym The WYMeditor instance to which the StructuredHeadingsManager + object should attach. + @class +*/ +function StructuredHeadingsManager(options, wym) { + options = jQuery.extend({ + headingIndentToolSelector: "li.wym_tools_indent a", + headingOutdentToolSelector: "li.wym_tools_outdent a", + + enableFixHeadingStructureButton: false, + fixHeadingStructureButtonHtml: String() + + '
      6. ' + + '' + + 'Fix Heading Structure' + + '' + + '
      7. ', + fixHeadingStructureSelector: "li.wym_tools_fix_heading_structure a", + + headingContainerPanelHtml: String() + + '
      8. ' + + 'Heading' + + '
      9. ', + headingContainerPanelSelector: "li.wym_containers_heading a", + + highestAllowableHeadingLevel: 1, + lowestAllowableHeadingLevel: 6 + + }, options); + + this._headingElements = WYMeditor.HEADING_ELEMENTS + .slice(options.highestAllowableHeadingLevel - 1, + options.lowestAllowableHeadingLevel); + this._limitedHeadingSel = this._headingElements.join(", "); + this._fullHeadingSel = WYMeditor.HEADING_ELEMENTS.join(", "); + this._options = options; + this._wym = wym; + + this.init(); +} + +/** + init + ==== + + Initializes the heading structure object used in the plugin for the + wymeditor instance. Creates the user interface adjustments, binds any + required listeners, applies the necessary CSS stylesheets, and enables the + IE7 heading numbering polyfill if necessary. +*/ +StructuredHeadingsManager.prototype.init = function () { + this.createUI(); + this.bindEvents(); + this.addCssStylesheet(); + + if (WYMeditor.STRUCTURED_HEADINGS_POLYFILL_REQUIRED) { + this.enableIE7Polyfill(); + } +}; + +/** + createUI + ======== + + Creates the structured headings user interface by adding the tools to the + tool bar and modifying the container selection panel. +*/ +StructuredHeadingsManager.prototype.createUI = function () { + var wym = this._wym, + $tools = jQuery(wym._box).find( + wym._options.toolsSelector + wym._options.toolsListSelector + ), + $containerItems, + $containerLink, + $newHeadingItem, + newHeadingLink, + i; + + // Add tool panel buttons if necessary + if (this._options.enableFixHeadingStructureButton) { + $tools.append(this._options.fixHeadingStructureButtonHtml); + } + + // Remove normal heading links from the containers panel list + $containerItems = jQuery(wym._box).find(wym._options.containersSelector) + .find('li'); + for (i = 0; i < $containerItems.length; ++i) { + $containerLink = $containerItems.eq(i).find('a'); + if (jQuery.inArray($containerLink[0].name.toLowerCase(), + WYMeditor.HEADING_ELEMENTS) > -1) { + $containerItems.eq(i).remove(); + } + } + + // Add new single heading container to the containers panel list + $containerItems.eq(0).after(this._options.headingContainerPanelHtml); +}; + +/** + bindEvents + ========== + + Binds the click events for the buttons in the tool bar and the container + link in the containers panel. +*/ +StructuredHeadingsManager.prototype.bindEvents = function () { + var headingManager = this, + wym = this._wym, + $box = jQuery(wym._box); + + // Bind click events to tool buttons + $box.find(this._options.headingOutdentToolSelector).click(function () { + var heading = wym.findUp(wym.container(), WYMeditor.HEADING_ELEMENTS); + headingManager.changeHeadingLevel(heading, "up"); + }); + $box.find(this._options.headingIndentToolSelector).click(function () { + var heading = wym.findUp(wym.container(), WYMeditor.HEADING_ELEMENTS); + headingManager.changeHeadingLevel(heading, "down"); + }); + if (this._options.enableFixHeadingStructureButton) { + $box.find(this._options.fixHeadingStructureSelector).click(function () { + headingManager.fixHeadingStructure(); + }); + } + + // Bind click event to the new single heading link + $box.find(this._options.headingContainerPanelSelector).click(function () { + var container = wym.findUp(wym.container(), WYMeditor.MAIN_CONTAINERS); + headingManager.switchToHeading(container); + }); +}; + +/** + addCssStylesheet + ================ + + Adds the CSS stylesheet for the heading numbering to the wymeditor iframe + and stores the CSS for access through the printCss function. +*/ +StructuredHeadingsManager.prototype.addCssStylesheet = function () { + var wym = this._wym, + iframeHead = jQuery(wym._doc).find('head')[0], + stylesheetHref, + cssLink, + cssRequest; + + cssLink = wym._doc.createElement('link'); + cssLink.rel = 'stylesheet'; + cssLink.type = 'text/css'; + + if (WYMeditor.STRUCTURED_HEADINGS_POLYFILL_REQUIRED) { + stylesheetHref = '/plugins/structured_headings/' + + 'structured_headings_ie7_editor.css'; + cssLink.href = '../..' + stylesheetHref; // Adjust path for iframe + iframeHead.appendChild(cssLink); + + // Change href to user stylesheet to store in WYMeditor + stylesheetHref = stylesheetHref.replace(/editor.css$/, 'user.css'); + + } else { + stylesheetHref = '/plugins/structured_headings/structured_headings.css'; + cssLink.href = '../..' + stylesheetHref; // Adjust path for iframe + iframeHead.appendChild(cssLink); + } + + // Get stylesheet CSS and store it in WYMeditor so that it can be accessed + // to put on other pages. + cssRequest = new XMLHttpRequest(); + cssRequest.open('GET', wym._options.basePath + stylesheetHref, false); + cssRequest.send(''); + WYMeditor.structuredHeadingsCSS = cssRequest.responseText; +}; + +/** + canRaiseHeadingLevel + ==================== + + Checks the context of the passed heading DOM node to see if it can validly + have its heading level raised. Returns true if the heading's level can + validly be raised, false if otherwise. + + @param heading A heading DOM node for checking if it can validly have its + heading level raised +*/ +StructuredHeadingsManager.prototype.canRaiseHeadingLevel = function (heading) { + var headingLevel = getHeadingLevel(heading), + headingLevelDifference, + nextHeading, + nextHeadingLevel; + + // The level of a heading cannot be raised if it is already at the highest + // allowable level. + if (headingLevel === this._options.highestAllowableHeadingLevel) { + return false; + } + + // The level of a heading cannot be raised if the heading level is any + // higher than the level of its following heading. + nextHeading = jQuery(heading).next(this._fullHeadingSel)[0]; + if (nextHeading) { + nextHeadingLevel = getHeadingLevel(nextHeading); + headingLevelDifference = headingLevel - nextHeadingLevel; + if (headingLevelDifference < 0) { + return false; + } + } + + return true; +}; + +/** + canLowerHeadingLevel + ==================== + + Checks the context of the passed heading DOM node to see if it can validly + have its heading level lowered. Returns true if the heading's level can + validly be lowered, false if otherwise. + + @param heading A heading DOM node for checking if it can validly have its + heading level lowered +*/ +StructuredHeadingsManager.prototype.canLowerHeadingLevel = function (heading) { + var headingLevel = getHeadingLevel(heading), + headingLevelDifference, + prevHeading, + prevHeadingLevel; + + // The level of a heading cannot be lowered if it is already at the lowest + // allowable level. + if (headingLevel === this._options.lowestAllowableHeadingLevel) { + return false; + } + + // The user cannot lower the level of a heading if the heading level is any + // lower than the level of its previous heading. + prevHeading = jQuery(heading).prev(this._fullHeadingSel)[0]; + if (prevHeading) { + prevHeadingLevel = getHeadingLevel(prevHeading); + headingLevelDifference = prevHeadingLevel - headingLevel; + if (headingLevelDifference < 0) { + return false; + } + } + + return true; +}; + +/** + changeHeadingLevel + ================== + + If the passed heading DOM node exists in the documet, changes the level of + that heading up or down by one level if it is allowable. The heading will not + have its level moved up if the heading following it is at a lower level + than the passed heading's current level. A heading will not have its + level moved down if the heading preceding it is at a higher level than the + passed heading's current level. + + @param heading The DOM node of a heading element in the document. + @param upOrDown A string either being "up" or "down" that indicates if the + heading level should be increased or decreased. +*/ +StructuredHeadingsManager.prototype.changeHeadingLevel = function (heading, upOrDown) { + var wym = this._wym, + changeLevelUp = (upOrDown === "up"), + levelAdjustment = (changeLevelUp ? -1 : 1), + headingLevel; + + // If the heading doesn't exist, don't do anything. + if (!heading) { + return; + } + + // Check if the requested change in the heading level is valid. If it is + // not valid, don't modify the heading. + headingLevel = getHeadingLevel(heading); + if (changeLevelUp && !this.canRaiseHeadingLevel(heading)) { + return; + } + if (!changeLevelUp && !this.canLowerHeadingLevel(heading)) { + return; + } + + wym.switchTo(heading, 'h' + (headingLevel + levelAdjustment)); + if (WYMeditor.STRUCTURED_HEADINGS_POLYFILL_REQUIRED) { + this.numberHeadingsIE7(); + } +}; + +/** + switchToHeading + =============== + + Switches the passed DOM node (if it exists) to a heading with the same + heading level as the preceding heading to the node. If there is no + preceding heading to the node, the node is switched to a heading with the + specified highest allowable heading level in the options. + + @param node The DOM node to be switched to a heading. +*/ +StructuredHeadingsManager.prototype.switchToHeading = function (node) { + var wym = this._wym, + $prevHeading; + + // If the node doesn't exist, don't do anything. + if (!node) { + return; + } + + $prevHeading = jQuery(node).prev(this._fullHeadingSel); + if ($prevHeading.length) { + wym.switchTo(node, $prevHeading[0].nodeName); + } else { + wym.switchTo(node, 'h' + this._options.highestAllowableHeadingLevel); + } +}; + +/** + fixHeadingStructure + =================== + + Fixes the structure of the headings in the editor if needed so that they + follow proper standards of heading usage. The main fix this applies is + preventing headings from being more than one level apart while descending + (e.g. an H1 followed by an H3 or an H2 followed by an H4). + + This function is pretty simple now and will need more work in the future to + make it smarter. +*/ +StructuredHeadingsManager.prototype.fixHeadingStructure = function () { + var wym = this._wym, + $headings = jQuery(wym._doc).find('body.wym_iframe') + .find(this._limitedHeadingSel), + heading, + headingLevel, + prevHeadingLevel, + i; + + // If there are no headings in the document, don't do anything. + if (!$headings.length) { + return; + } + + prevHeadingLevel = getHeadingLevel($headings[0]); + for (i = 1; i < $headings.length; ++i) { + heading = $headings[i]; + headingLevel = getHeadingLevel(heading); + if (headingLevel - prevHeadingLevel > 1) { + wym.switchTo(heading, 'h' + (prevHeadingLevel + 1)); + ++prevHeadingLevel; + } else { + prevHeadingLevel = headingLevel; + } + } +}; + +/** + enableIE7Polyfill + ================= + + Enables Javascript polyfill to add heading numbering to IE versions 7 + and lower. +*/ +StructuredHeadingsManager.prototype.enableIE7Polyfill = function () { + var wym = this._wym, + headingManager = this, + $body = jQuery(wym._doc).find('body.wym_iframe'), + $containersPanelLinks = jQuery(wym._box) + .find(wym._options.containersSelector + ' li > a'), + prevHeadingTotal = 0, + prevSpanCharTotal = 0; + + $body.keyup(function (evt) { + if (jQuery.inArray(evt.which, + WYMeditor.STRUCTURED_HEADINGS_POTENTIAL_HEADING_MODIFICATION_KEYS) > -1) { + + var headingTotal = $body.find(headingManager._limitedHeadingSel).length, + spanCharTotal = 0; + + $body.find('.' + WYMeditor.STRUCTURED_HEADINGS_NUMBERING_SPAN_CLASS) + .each(function () { + + spanCharTotal += this.innerHTML.length; + }); + + if (headingTotal !== prevHeadingTotal || + spanCharTotal !== prevSpanCharTotal) { + + prevSpanCharTotal = headingManager.numberHeadingsIE7(); + } + + prevHeadingTotal = headingTotal; + } + }); + + $containersPanelLinks.click(function (evt) { + headingManager.numberHeadingsIE7(); + }); +}; + +/** + numberHeadingsIE7 + ================= + + A method of Structured Headings Manager objects that wraps the + numberHeadingsIE7 function in the plugin file for easier use. +*/ +StructuredHeadingsManager.prototype.numberHeadingsIE7 = function () { + numberHeadingsIE7(this._wym._doc, true); +} + +/** + WYMeditor.printStructuredHeadingsCss + ==================================== + + Function to output the plugin CSS to the console log so that it can be + copied over to other pages. +*/ +WYMeditor.printStructuredHeadingsCSS = function () { + WYMeditor.console.log(WYMeditor.structuredHeadingsCSS); +}; + +/* + getHeadingLevel + =============== + + Returns the integer heading level of the passed heading DOM element. For + example, if the passed heading was an `h2` element, the function would + return the integer `2`. +*/ +function getHeadingLevel(heading) { + return parseInt(heading.nodeName.slice(-1), 10); +} + + +/* + numberHeadingsIE7 + ================= + + Stand-alone function from WYMeditor that manually numbers the headings in a + document using javascript to mimic the heading numbering generated by the + structured headings plugin using CSS in browsers that support CSS counters + and :before pseudo-elements. Meant in particular to add structured heading + support to IE7. + + The doc parameter specifies the document which contains the headings to be + numbered. It defaults to the document object of the page if the parameter + isn't given. The addClass parameter specifies whether the structured + headings classes need to be added to the headings as the numbering is added + to the headings. It defaults to false if the parameter isn't given. + + Both of these parameters are optional so that, in most cases, if a user is + calling this function on a page to number a document's headings outside of + the editor, they can call the function with no parameters. + + The function returns the total number of characters in all of the added + heading numbering spans so that it can be monitored if the headings need to + be corrected if the total number of characters in the numbering spans + changes. + + NOTE: Although this function is stand-alone from WYMeditor, it still + requires jQuery. +*/ +function numberHeadingsIE7(doc, addClass) { + doc = typeof doc !== 'undefined' ? doc : document; + + var $doc = jQuery(doc), + + $startNode = $doc.find('.' + + WYMeditor.STRUCTURED_HEADINGS_START_NODE_CLASS), + startHeadingLevel, + headingSel = WYMeditor.HEADING_ELEMENTS.join(', '), + + $allHeadings, + $heading, + headingLabel, + + span, + spanCharTotal = 0, + + counters = [0, 0, 0, 0, 0, 0], + counterIndex, + i, + j; + + // If no start node is set and addClass is true, set the start node as the + // first heading in doc by default. + if (addClass) { + $startNode = $doc.find(headingSel); + if ($startNode.length) { + $startNode = $startNode.eq(0); + $startNode.addClass(WYMeditor.STRUCTURED_HEADINGS_START_NODE_CLASS); + } + } + // If there are no headings in the document or if no start node is defined + // and addClass is false, do nothing. + if (!$startNode.length) { + return; + } + + // startHeadingType is the level of the heading that is the start node. + // This is found out by looking at the last character of its nodeName. + startHeadingLevel = getHeadingLevel($startNode[0]); + $allHeadings = $startNode.nextAll(headingSel).add($startNode); + + // Remove any previously calculated heading numbering + $doc.find('.' + WYMeditor.STRUCTURED_HEADINGS_NUMBERING_SPAN_CLASS).remove(); + + for (i = 0; i < $allHeadings.length; ++i) { + $heading = $allHeadings.eq(i); + counterIndex = getHeadingLevel($heading[0]) - startHeadingLevel; + + // If the counterIndex is negative, it means the level of the current + // heading is above the level of the start node, so heading numbering + // should stop at this point. + if (counterIndex < 0) { + break; + } + + // Calculate the heading label + ++counters[counterIndex]; + headingLabel = ''; + for (j = 0; j <= counterIndex; ++j) { + if (j === counterIndex) { + headingLabel += counters[j]; + } else { + headingLabel += counters[j] + '.'; + } + } + if (addClass) { + $heading.addClass( + WYMeditor.STRUCTURED_HEADINGS_LEVEL_CLASSES[counterIndex]); + } + + // Prepend span containing the heading's label to heading + span = doc.createElement('span'); + span.innerHTML = headingLabel; + span.className = WYMeditor.STRUCTURED_HEADINGS_NUMBERING_SPAN_CLASS; + if (addClass) { + span.className += ' ' + WYMeditor.EDITOR_ONLY_CLASS; + } + $heading.prepend(span); + spanCharTotal += (counterIndex * 2) + 1; + + // Reset counters below the heading's level + for (j = counterIndex + 1; j < counters.length; ++j) { + counters[j] = 0; + } + } + + return spanCharTotal; +} + diff --git a/public/wymeditor/wymeditor/plugins/structured_headings/local_test.html b/public/wymeditor/wymeditor/plugins/structured_headings/local_test.html new file mode 100644 index 0000000..679e200 --- /dev/null +++ b/public/wymeditor/wymeditor/plugins/structured_headings/local_test.html @@ -0,0 +1,22 @@ + + + + Test + + + + + + +

        Top

        +

        Sub

        +

        SubSub

        + + + diff --git a/public/wymeditor/wymeditor/plugins/structured_headings/ruler_arrow.png b/public/wymeditor/wymeditor/plugins/structured_headings/ruler_arrow.png new file mode 100644 index 0000000..a11b94f Binary files /dev/null and b/public/wymeditor/wymeditor/plugins/structured_headings/ruler_arrow.png differ diff --git a/public/wymeditor/wymeditor/plugins/structured_headings/structured_headings.css b/public/wymeditor/wymeditor/plugins/structured_headings/structured_headings.css new file mode 100644 index 0000000..3def5cb --- /dev/null +++ b/public/wymeditor/wymeditor/plugins/structured_headings/structured_headings.css @@ -0,0 +1,55 @@ +/* Structured Headings Plugin CSS */ +body { + counter-reset: cntr-h1; +} + +h1 { + counter-reset: cntr-h2; +} + +h1:before { + content: counter(cntr-h1) " "; + counter-increment: cntr-h1; +} + +h2 { + counter-reset: cntr-h3; +} + +h2:before { + content: counter(cntr-h1) "." counter(cntr-h2) " "; + counter-increment: cntr-h2; +} + +h3 { + counter-reset: cntr-h4; +} + +h3:before { + content: counter(cntr-h1) "." counter(cntr-h2) "." counter(cntr-h3) " "; + counter-increment: cntr-h3; +} + +h4 { + counter-reset: cntr-h5; +} + +h4:before { + content: counter(cntr-h1) "." counter(cntr-h2) "." counter(cntr-h3) "." counter(cntr-h4) " "; + counter-increment: cntr-h4; +} + +h5 { + counter-reset: cntr-h6; +} + +h5:before { + content: counter(cntr-h1) "." counter(cntr-h2) "." counter(cntr-h3) "." counter(cntr-h4) "." counter(cntr-h5) " "; + counter-increment: cntr-h5; +} + +h6:before { + content: counter(cntr-h1) "." counter(cntr-h2) "." counter(cntr-h3) "." counter(cntr-h4) "." counter(cntr-h5) "." counter(cntr-h6) " "; + counter-increment: cntr-h6; +} + diff --git a/public/wymeditor/wymeditor/plugins/structured_headings/structured_headings_ie7_editor.css b/public/wymeditor/wymeditor/plugins/structured_headings/structured_headings_ie7_editor.css new file mode 100644 index 0000000..99a3df6 --- /dev/null +++ b/public/wymeditor/wymeditor/plugins/structured_headings/structured_headings_ie7_editor.css @@ -0,0 +1,4 @@ +span.wym-structured-heading-numbering { + background-color: #FFFFFF; + margin-right: 8px; +} diff --git a/public/wymeditor/wymeditor/plugins/structured_headings/structured_headings_ie7_user.css b/public/wymeditor/wymeditor/plugins/structured_headings/structured_headings_ie7_user.css new file mode 100644 index 0000000..ae685ae --- /dev/null +++ b/public/wymeditor/wymeditor/plugins/structured_headings/structured_headings_ie7_user.css @@ -0,0 +1,3 @@ +span.wym-structured-heading-numbering { + margin-right: 8px; +} diff --git a/public/wymeditor/wymeditor/plugins/table/jquery.wymeditor.table.js b/public/wymeditor/wymeditor/plugins/table/jquery.wymeditor.table.js new file mode 100644 index 0000000..799f1a7 --- /dev/null +++ b/public/wymeditor/wymeditor/plugins/table/jquery.wymeditor.table.js @@ -0,0 +1,721 @@ +/** + * Copyright (c) 2011 PolicyStat LLC. + * MIT licensed (MIT-license.txt) + * + * @author Wes Winham (winhamwr@gmail.com) + */ + +// Fugue icons by Yusuke Kamiyamane http://p.yusukekamiyamane.com/ +// and licensed under Creative Commons Attribution + +/** + * A Table editing plugin that gives the user ability to add and remove + * rows and columns as well as merge rows and columns. + * + * @param options A configuration object. + * @param wym The WYMeditor instance to which the TableEditor should attach. + * @class + */ +function TableEditor(options, wym) { + options = jQuery.extend({ + sMergeRowButtonHtml: String() + + '
      10. ' + + '' + + 'Merge Table Row' + + '' + + '
      11. ', + + sMergeRowButtonSelector: "li.wym_tools_merge_row a", + + sAddRowButtonHtml: String() + + "
      12. " + + "" + + "Add Table Row" + + "" + + "
      13. ", + sAddRowButtonSelector: "li.wym_tools_add_row a", + + sRemoveRowButtonHtml: String() + + "
      14. " + + "" + + "Remove Table Row" + + "" + + "
      15. ", + sRemoveRowButtonSelector: "li.wym_tools_remove_row a", + + sAddColumnButtonHtml: String() + + "
      16. " + + "" + + "Add Table Column" + + "" + + "
      17. ", + sAddColumnButtonSelector: "li.wym_tools_add_column a", + + sRemoveColumnButtonHtml: String() + + "
      18. " + + "" + + "Remove Table Column" + + "" + + "
      19. ", + sRemoveColumnButtonSelector: "li.wym_tools_remove_column a", + + enableCellTabbing: true + + }, options); + + this._options = options; + this._wym = wym; + + this.init(); +} + +/** + * Construct and return a table objects using the given options object. + * + * @param options The configuration object. + */ +WYMeditor.editor.prototype.table = function (options) { + var tableEditor = new TableEditor(options, this); + this.tableEditor = tableEditor; + + return tableEditor; +}; + +/** + * Initialize the TableEditor object by adding appropriate toolbar buttons and + * binding any required event listeners. + */ +TableEditor.prototype.init = function () { + var wym = this._wym, + tableEditor = this, + // Add the tool panel buttons + tools = jQuery(wym._box).find( + wym._options.toolsSelector + wym._options.toolsListSelector + ); + + tools.append(tableEditor._options.sMergeRowButtonHtml); + tools.append(tableEditor._options.sAddRowButtonHtml); + tools.append(tableEditor._options.sRemoveRowButtonHtml); + tools.append(tableEditor._options.sAddColumnButtonHtml); + tools.append(tableEditor._options.sRemoveColumnButtonHtml); + + tableEditor.bindEvents(); + rangy.init(); +}; + +/** + * Bind all required event listeners, including button listeners and support for + * tabbing through table cells if enableCellTabbing is true. + */ +TableEditor.prototype.bindEvents = function () { + var wym = this._wym, + tableEditor = this; + + // Handle tool button click + jQuery(wym._box).find(tableEditor._options.sMergeRowButtonSelector).click(function () { + var sel = rangy.getIframeSelection(wym._iframe); + tableEditor.mergeRow(sel); + return false; + }); + jQuery(wym._box).find(tableEditor._options.sAddRowButtonSelector).click(function () { + return tableEditor.addRow(wym.selected()); + }); + jQuery(wym._box).find(tableEditor._options.sRemoveRowButtonSelector).click(function () { + return tableEditor.removeRow(wym.selected()); + }); + jQuery(wym._box).find(tableEditor._options.sAddColumnButtonSelector).click(function () { + return tableEditor.addColumn(wym.selected()); + }); + jQuery(wym._box).find(tableEditor._options.sRemoveColumnButtonSelector).click(function () { + return tableEditor.removeColumn(wym.selected()); + }); + + // Handle tab clicks + if (tableEditor._options.enableCellTabbing) { + jQuery(wym._doc).bind('keydown', tableEditor.keyDown); + } +}; + +/** + * Get the number of columns in a given tr element, accounting for colspan and + * rowspan. This function assumes that the table structure is valid, and will + * return incorrect results for uneven tables. + * + * @param tr The node whose number of columns we need to count. + * + * @returns {Number} The number of columns in the given tr, accounting for + * colspan and rowspan. + */ +TableEditor.prototype.getNumColumns = function (tr) { + var wym = this._wym, + numColumns = 0, + table, + firstTr; + + table = wym.findUp(tr, 'table'); + firstTr = jQuery(table).find('tr:eq(0)'); + + // Count the tds and ths in the FIRST ROW of this table, accounting for + // colspan. We count the first td because it won't have any rowspan's before + // it to complicate things + jQuery(firstTr).children('td,th').each(function (index, elmnt) { + numColumns += TableEditor.GET_COLSPAN_PROP(elmnt); + }); + + return numColumns; +}; + +/** + TableEditor.GET_COLSPAN_PROP + ============================ + + Get the integer value of the inferred colspan property on the given cell in + a cross-browser compatible way that's also compatible across jquery versions. + + jquery 1.6 changed the way .attr works, which affected certain browsers + differently with regard to colspan and rowspan for cells that didn't explcility + have that attribue set. +*/ +TableEditor.GET_COLSPAN_PROP = function (cell) { + var colspan = jQuery(cell).attr('colspan'); + if (typeof colspan === 'undefined') { + colspan = 1; + } + return parseInt(colspan, 10); +}; + +/** + TableEditor.GET_ROWSPAN_PROP + ============================ + + Get the integer value of the inferred rowspan property on the given cell in + a cross-browser compatible way that's also compatible across jquery versions. + + See GET_COLSPAN_PROP for details +*/ +TableEditor.GET_ROWSPAN_PROP = function (cell) { + var rowspan = jQuery(cell).attr('rowspan'); + if (typeof rowspan === 'undefined') { + rowspan = 1; + } + return parseInt(rowspan, 10); +}; +/** + * Get the X grid index of the given td or th table cell (0-indexed). This takes + * in to account all colspans and rowspans. + * + * @param cell The td or th node whose X index we're returning. + */ +TableEditor.prototype.getCellXIndex = function (cell) { + var tableEditor = this, + i, + parentTr, + baseRowColumns, + rowColCount, + missingCells, + rowspanIndexes, + checkTr, + rowOffset, + trChildren, + elmnt, + colspan, + indexCounter, + cellIndex; + parentTr = jQuery(cell).parent('tr')[0]; + + baseRowColumns = this.getNumColumns(parentTr); + + // Figure out how many explicit cells are missing which is how many rowspans + // we're affected by + rowColCount = 0; + jQuery(parentTr).children('td,th').each(function (index, elmnt) { + rowColCount += TableEditor.GET_COLSPAN_PROP(elmnt); + }); + + missingCells = baseRowColumns - rowColCount; + rowspanIndexes = []; + checkTr = parentTr; + rowOffset = 1; + + // If this cell is affected by a rowspan from farther up the table, + // we need to take in to account any possible colspan attributes on that + // cell. Store the real X index of the cells to the left of our cell to use + // in the colspan calculation. + while (missingCells > 0) { + checkTr = jQuery(checkTr).prev('tr'); + rowOffset += 1; + trChildren = jQuery(checkTr).children('td,th'); + for (i = 0; i < trChildren.length; i++) { + elmnt = trChildren[i]; + if (TableEditor.GET_ROWSPAN_PROP(elmnt) >= rowOffset) { + // Actually affects our source row + missingCells -= 1; + colspan = TableEditor.GET_COLSPAN_PROP(elmnt); + rowspanIndexes[tableEditor.getCellXIndex(elmnt)] = colspan; + } + } + } + + indexCounter = 0; + cellIndex = null; + // Taking in to account the real X indexes of all of the columns to the left + // of this cell, determine the real X index. + jQuery(parentTr).children('td,th').each(function (index, elmnt) { + if (cellIndex !== null) { + // We've already iterated to the cell we're checking + return; + } + // Account for an inferred colspan created by a rowspan from above + while (typeof rowspanIndexes[indexCounter] !== 'undefined') { + indexCounter += parseInt(rowspanIndexes[indexCounter], 10); + } + if (elmnt === cell) { + // We're at our cell, no need to keep moving to the right. + // Signal this by setting the cellIndex + cellIndex = indexCounter; + return; + } + // Account for an explicit colspan on this cell + indexCounter += TableEditor.GET_COLSPAN_PROP(elmnt); + }); + + if (cellIndex === null) { + // Somehow, we never found the cell when iterating over its row. + throw "Cell index not found"; + } + return cellIndex; +}; + +/** + * Get the number of columns represented by the given array of contiguous cell + * (td/th) nodes. + * Accounts for colspan and rowspan attributes. + * + * @param cells An array of td/th nodes whose total column span we're checking. + * + * @return {Number} The number of columns represented by the "cells" + */ +TableEditor.prototype.getTotalColumns = function (cells) { + var tableEditor = this, + rootTr = this.getCommonParentTr(cells), + baseRowColumns, + colspanCount, + rowColCount; + + if (rootTr === null) { + // Non-contiguous columns + throw "getTotalColumns only allowed for contiguous cells"; + } + + baseRowColumns = this.getNumColumns(rootTr); + + // Count the number of simple columns, not accounting for rowspans + colspanCount = 0; + jQuery(cells).each(function (index, elmnt) { + colspanCount += TableEditor.GET_COLSPAN_PROP(elmnt); + }); + + // Determine if we're affected by rowspans. If the number of simple columns + // in the row equals the number of columns in the first row, we don't have + // any rowspans + rowColCount = 0; + jQuery(rootTr).children('td,th').each(function (index, elmnt) { + rowColCount += TableEditor.GET_COLSPAN_PROP(elmnt); + }); + + if (rowColCount === baseRowColumns) { + // Easy case. No rowspans to deal with + return colspanCount; + } else { + if (cells.length === 1) { + // Easy. Just the colspan + return TableEditor.GET_COLSPAN_PROP(cells[0]); + } else { + var lastCell = jQuery(cells).eq(cells.length - 1)[0], + firstCell = jQuery(cells).eq(0)[0]; + // On jQuery 1.4 upgrade, jQuery(cells).eq(-1) + return 1 + tableEditor.getCellXIndex(lastCell) - + tableEditor.getCellXIndex(firstCell); + } + } +}; + +/** + * Merge the table cells in the given selection using a colspan. + * + * @param sel A rangy selection object across which to row merge. + * + * @return {Boolean} true if changes are made, false otherwise + */ +TableEditor.prototype.mergeRow = function (sel) { + var wym = this._wym, + tableEditor = this, + i, + // Get all of the affected nodes in the range + nodes = [], + range = null, + cells, + rootTr, + mergeCell, + $elmnt, + rowspanProp; + + for (i = 0; i < sel.rangeCount; i++) { + range = sel.getRangeAt(i); + nodes = nodes.concat(range.getNodes(false)); + } + + // Just use the td and th nodes + cells = jQuery(nodes).filter('td,th'); + if (cells.length === 0) { + return false; + } + + // If the selection is across multiple tables, don't merge + rootTr = tableEditor.getCommonParentTr(cells); + if (rootTr === null) { + return false; + } + + mergeCell = cells[0]; + // If any of the cells have a rowspan, create the inferred cells + jQuery(cells).each(function (i, elmnt) { + $elmnt = jQuery(elmnt); + rowspanProp = TableEditor.GET_ROWSPAN_PROP(elmnt); + if (rowspanProp <= 1) { + // We don't care about cells without a rowspan + return; + } + + // This cell has an actual rowspan, we need to account for it + // Figure out the x index for this cell in the table grid + var prevCells = $elmnt.prevAll('td,th'), + index = tableEditor.getCellXIndex(elmnt), + // Create the previously-inferred cell in the appropriate index + // with one less rowspan + newRowspan = rowspanProp - 1, + newTd; + if (newRowspan === 1) { + newTd = '' + $elmnt.html() + ''; + } else { + newTd = String() + + '' + + $elmnt.html() + + ''; + } + if (index === 0) { + $elmnt.parent('tr') + .next('tr') + .prepend(newTd); + } else { + // TODO: account for colspan/rowspan with insertion + // Account for colspan/rowspan by walking from right to left looking + // for the cell closest to the desired index to APPEND to + var insertionIndex = index - 1, + insertionCells = $elmnt.parent('tr').next('tr') + .find('td,th'), + cellInserted = false; + for (i = insertionCells.length - 1; i >= 0; i--) { + var xIndex = tableEditor.getCellXIndex(insertionCells[i]); + if (xIndex <= insertionIndex) { + jQuery(insertionCells[i]).append(newTd); + cellInserted = true; + break; + } + } + if (!cellInserted) { + // Bail out now before we clear HTML and break things + throw "Cell rowspan invalid"; + } + } + + // Clear the cell's html, since we just moved it down + $elmnt.html(''); + }); + + // Remove any rowspan from the mergecell now that we've shifted rowspans + // down + // ie fails when we try to remove a rowspan for some reason + try { + jQuery(mergeCell).removeAttr('rowspan'); + } catch (err) { + jQuery(mergeCell).attr('rowspan', 1); + } + + // Build the content of the new combined cell from all of the included cells + var newContent = ''; + jQuery(cells).each(function (index, elmnt) { + newContent += jQuery(elmnt).html(); + }); + + // Add a colspan to the farthest-left cell + var combinedColspan = this.getTotalColumns(cells); + if (jQuery.browser.msie) { + // jQuery.attr doesn't work for colspan in ie + mergeCell.colSpan = combinedColspan; + } else { + jQuery(mergeCell).attr('colspan', combinedColspan); + } + + // Delete the rest of the cells + jQuery(cells).each(function (index, elmnt) { + if (index !== 0) { + jQuery(elmnt).remove(); + } + }); + + // Change the content in our newly-merged cell + jQuery(mergeCell).html(newContent); + + tableEditor.selectElement(mergeCell); + + return true; +}; + +/** + * Add a row to the given elmnt (representing a or a child of a ). + * + * @param The node which will have a row appended after its parent row. + */ +TableEditor.prototype.addRow = function (elmnt) { + var wym = this._wym, + tr = this._wym.findUp(elmnt, 'tr'), + numColumns, + tdHtml, + i; + + if (tr === null) { + return false; + } + + numColumns = this.getNumColumns(tr); + + tdHtml = ''; + for (i = 0; i < numColumns; i++) { + tdHtml += ' '; + } + jQuery(tr).after('' + tdHtml + ''); + + return false; +}; + +/** + * Remove the given table if it doesn't have any rows/columns. + * + * @param table The table to delete if it is empty. + */ +TableEditor.prototype.removeEmptyTable = function (table) { + var cells = jQuery(table).find('td,th'); + if (cells.length === 0) { + jQuery(table).remove(); + } +}; + +/** + * Remove the row for the given element (representing a or a child + * of a ). + * + * @param elmnt The node whose parent tr will be removed. + */ +TableEditor.prototype.removeRow = function (elmnt) { + var wym = this._wym, + tr = this._wym.findUp(elmnt, 'tr'), + table; + + if (tr === null) { + return false; + } + table = wym.findUp(elmnt, 'table'); + jQuery(tr).remove(); + this.removeEmptyTable(table); + + return false; +}; + +/** + * Add a column to the given elmnt (representing a or a child of a ). + * + * @param elmnt The node which will have a column appended afterward. + */ +TableEditor.prototype.addColumn = function (elmnt) { + var wym = this._wym, + td = this._wym.findUp(elmnt, ['td', 'th']), + prevTds, + tdIndex, + tr, + newTd = ' ', + newTh = ' ', + insertionElement; + + if (td === null) { + return false; + } + prevTds = jQuery(td).prevAll(); + tdIndex = prevTds.length; + + tr = wym.findUp(td, 'tr'); + jQuery(tr).siblings('tr').andSelf().each(function (index, element) { + insertionElement = newTd; + if (jQuery(element).find('th').length > 0) { + // The row has a TH, so insert a th + insertionElement = newTh; + } + + jQuery(element).find('td,th').eq(tdIndex).after(insertionElement); + }); + + return false; +}; + +/** + * Remove the column to the right of the given elmnt (representing a or a + * child of a ). + */ +TableEditor.prototype.removeColumn = function (elmnt) { + var wym = this._wym, + td = this._wym.findUp(elmnt, ['td', 'th']), + table, + prevTds, + tdIndex, + tr; + if (td === null) { + return false; + } + table = wym.findUp(elmnt, 'table'); + prevTds = jQuery(td).prevAll(); + tdIndex = prevTds.length; + + tr = wym.findUp(td, 'tr'); + jQuery(tr).siblings('tr').each(function (index, element) { + jQuery(element).find('td,th').eq(tdIndex).remove(); + }); + jQuery(td).remove(); + this.removeEmptyTable(table); + + return false; +}; + +/** + * keyDown event handler used for consistent tab key cell movement. + */ +TableEditor.prototype.keyDown = function (evt) { + //'this' is the doc + var wym = WYMeditor.INSTANCES[this.title], + tableEditor = wym.tableEditor; + + if (evt.which === WYMeditor.KEY.TAB) { + return tableEditor.selectNextCell(wym.selected()); + } + + return null; +}; + +/** + * Move the focus to the next cell. + */ +TableEditor.prototype.selectNextCell = function (elmnt) { + var wym = this._wym, + tableEditor = this, + cell = wym.findUp(elmnt, ['td', 'th']), + nextCells, + tr, + nextRows; + + if (cell === null) { + return null; + } + + // Try moving to the next cell to the right + nextCells = jQuery(cell).next('td,th'); + if (nextCells.length > 0) { + tableEditor.selectElement(nextCells[0]); + return false; + } + + // There was no cell to the right, use the first cell in the next row + tr = wym.findUp(cell, 'tr'); + nextRows = jQuery(tr).next('tr'); + if (nextRows.length !== 0) { + nextCells = jQuery(nextRows).children('td,th'); + if (nextCells.length > 0) { + tableEditor.selectElement(nextCells[0]); + return false; + } + } + + // There is no next row. Do a normal tab + return null; +}; + +/** + * Select the given element using rangy selectors. + */ +TableEditor.prototype.selectElement = function (elmnt) { + var sel = rangy.getIframeSelection(this._wym._iframe), + range = rangy.createRange(this._wym._doc); + + range.setStart(elmnt, 0); + range.setEnd(elmnt, 0); + range.collapse(false); + + try { + sel.setSingleRange(range); + } catch (err) { + // ie8 can raise an "unkown runtime error" trying to empty the range + } + // IE selection hack + if (jQuery.browser.msie) { + this._wym.saveCaret(); + } +}; + +/** + * Get the common parent tr for the given table cell nodes. If the closest parent + * tr for each cell isn't the same, returns null. + */ +TableEditor.prototype.getCommonParentTr = function (cells) { + var firstCell, + parentTrList, + rootTr; + + cells = jQuery(cells).filter('td,th'); + if (cells.length === 0) { + return null; + } + firstCell = cells[0]; + parentTrList = jQuery(firstCell).parent('tr'); + + if (parentTrList.length === 0) { + return null; + } + rootTr = parentTrList[0]; + + // Ensure that all of the cells have the same parent tr + jQuery(cells).each(function (index, elmnt) { + parentTrList = jQuery(elmnt).parent('tr'); + if (parentTrList.length === 0 || parentTrList[0] !== rootTr) { + return null; + } + }); + + return rootTr; +}; diff --git a/public/wymeditor/wymeditor/plugins/table/table_delete_column.png b/public/wymeditor/wymeditor/plugins/table/table_delete_column.png new file mode 100644 index 0000000..9022d68 Binary files /dev/null and b/public/wymeditor/wymeditor/plugins/table/table_delete_column.png differ diff --git a/public/wymeditor/wymeditor/plugins/table/table_delete_row.png b/public/wymeditor/wymeditor/plugins/table/table_delete_row.png new file mode 100644 index 0000000..f9d956f Binary files /dev/null and b/public/wymeditor/wymeditor/plugins/table/table_delete_row.png differ diff --git a/public/wymeditor/wymeditor/plugins/table/table_insert_column.png b/public/wymeditor/wymeditor/plugins/table/table_insert_column.png new file mode 100644 index 0000000..49226ec Binary files /dev/null and b/public/wymeditor/wymeditor/plugins/table/table_insert_column.png differ diff --git a/public/wymeditor/wymeditor/plugins/table/table_insert_row.png b/public/wymeditor/wymeditor/plugins/table/table_insert_row.png new file mode 100644 index 0000000..da435c3 Binary files /dev/null and b/public/wymeditor/wymeditor/plugins/table/table_insert_row.png differ diff --git a/public/wymeditor/wymeditor/plugins/table/table_join_row.png b/public/wymeditor/wymeditor/plugins/table/table_join_row.png new file mode 100644 index 0000000..70e5983 Binary files /dev/null and b/public/wymeditor/wymeditor/plugins/table/table_join_row.png differ diff --git a/public/wymeditor/wymeditor/plugins/tidy/README b/public/wymeditor/wymeditor/plugins/tidy/README new file mode 100644 index 0000000..acc7ffd --- /dev/null +++ b/public/wymeditor/wymeditor/plugins/tidy/README @@ -0,0 +1,19 @@ +WYMeditor : what you see is What You Mean web-based editor +Copyright (c) 2005 - 2009 Jean-Francois Hovinne, http://www.wymeditor.org/ +Dual licensed under the MIT (MIT-license.txt) +and GPL (GPL-license.txt) licenses. + +For further information visit: + http://www.wymeditor.org/ + +File Name: + README - HTML Tidy plugin for WYMeditor + +File Authors: + Jean-François Hovinne (jf.hovinne a-t wymeditor dotorg) + +Credits: + 'HTML Tidy' by Dave Ragget - http://tidy.sourceforge.net/ + Icon 'wand' by Mark James - http://famfamfam.com/ + +WYMeditor documentation is available online at http://www.wymeditor.org/ diff --git a/public/wymeditor/wymeditor/plugins/tidy/jquery.wymeditor.tidy.js b/public/wymeditor/wymeditor/plugins/tidy/jquery.wymeditor.tidy.js new file mode 100644 index 0000000..39e161d --- /dev/null +++ b/public/wymeditor/wymeditor/plugins/tidy/jquery.wymeditor.tidy.js @@ -0,0 +1,78 @@ +/* + * WYMeditor : what you see is What You Mean web-based editor + * Copyright (c) 2005 - 2009 Jean-Francois Hovinne, http://www.wymeditor.org/ + * Dual licensed under the MIT (MIT-license.txt) + * and GPL (GPL-license.txt) licenses. + * + * For further information visit: + * http://www.wymeditor.org/ + * + * File Name: + * jquery.wymeditor.tidy.js + * HTML Tidy plugin for WYMeditor + * + * File Authors: + * Jean-Francois Hovinne (jf.hovinne a-t wymeditor dotorg) + */ + +//WymTidy constructor +function WymTidy(options, wym) { + var wand_url = wym._options.basePath + "plugins/tidy/wand.png"; + options = jQuery.extend({ + sUrl: wym._options.basePath + "plugins/tidy/tidy.php", + sButtonHtml: "" + + "
      20. " + + "" + + "Clean up HTML" + + "" + + "
      21. ", + + sButtonSelector: "li.wym_tools_tidy a" + + }, options); + + this._options = options; + this._wym = wym; +} + +//Extend WYMeditor +WYMeditor.editor.prototype.tidy = function(options) { + var tidy = new WymTidy(options, this); + return tidy; +}; + + +//WymTidy initialization +WymTidy.prototype.init = function() { + var tidy = this; + + jQuery(this._wym._box).find( + this._wym._options.toolsSelector + this._wym._options.toolsListSelector) + .append(this._options.sButtonHtml); + + //handle click event + jQuery(this._wym._box).find(this._options.sButtonSelector).click(function() { + tidy.cleanup(); + return(false); + }); +}; + +//WymTidy cleanup +WymTidy.prototype.cleanup = function() { + var wym = this._wym; + var html = "" + wym.xhtml() + ""; + + jQuery.post(this._options.sUrl, { html: html}, function(data) { + if (data.length > 0 && data != '0') { + if (data.indexOf(" tags, onclick, onblur, onload, onmouseover... attributes + if you *really* need this functionality. Or, even better, you could allow only the tags and + attributes that you explicitly allow (future versions of WYMeditor might provide this + functionality by default - please contribute). + + Be warned that providing authentication is NOT enough to guard you agains the attack. When + someone is authenticated in your page and visits www.badsite.com, the IFRAME has the proper + authentication so nothing has changed. + +*/ + +if (get_magic_quotes_gpc()) $html = stripslashes($_REQUEST['html']); +else $html = $_REQUEST['html']; + +if(strlen($html) > 0) { + + // Specify configuration + $config = array( + 'bare' => true, + 'clean' => true, + 'doctype' => 'strict', + 'drop-empty-paras' => true, + 'drop-font-tags' => true, + 'drop-proprietary-attributes' => true, + 'enclose-block-text' => true, + 'indent' => false, + 'join-classes' => true, + 'join-styles' => true, + 'logical-emphasis' => true, + 'output-xhtml' => true, + 'show-body-only' => true, + 'wrap' => 0); + + // Tidy + $tidy = new tidy; + $tidy->parseString($html, $config, 'utf8'); + $tidy->cleanRepair(); + + // Output + echo $tidy; +} else { + +echo ('0'); +} +?> diff --git a/public/wymeditor/wymeditor/plugins/tidy/wand.png b/public/wymeditor/wymeditor/plugins/tidy/wand.png new file mode 100644 index 0000000..bb55eea Binary files /dev/null and b/public/wymeditor/wymeditor/plugins/tidy/wand.png differ diff --git a/public/wymeditor/wymeditor/skins/compact/icons.png b/public/wymeditor/wymeditor/skins/compact/icons.png new file mode 100644 index 0000000..c6eb463 Binary files /dev/null and b/public/wymeditor/wymeditor/skins/compact/icons.png differ diff --git a/public/wymeditor/wymeditor/skins/compact/skin.css b/public/wymeditor/wymeditor/skins/compact/skin.css new file mode 100644 index 0000000..2becf0b --- /dev/null +++ b/public/wymeditor/wymeditor/skins/compact/skin.css @@ -0,0 +1,134 @@ +/* + * WYMeditor : what you see is What You Mean web-based editor + * Copyright (c) 2005 - 2009 Jean-Francois Hovinne, http://www.wymeditor.org/ + * Dual licensed under the MIT (MIT-license.txt) + * and GPL (GPL-license.txt) licenses. + * + * For further information visit: + * http://www.wymeditor.org/ + * + * File Name: + * screen.css + * main stylesheet for the WYMeditor skin + * See the documentation for more info. + * + * File Authors: + * Daniel Reszka (d.reszka a-t wymeditor dotorg) + * Jean-Francois Hovinne (jf.hovinne a-t wymeditor dotorg) +*/ + +/*TRYING TO RESET STYLES THAT MAY INTERFERE WITH WYMEDITOR*/ + .wym_skin_compact p, .wym_skin_compact h2, .wym_skin_compact h3, + .wym_skin_compact ul, .wym_skin_compact li { background: transparent url(); margin: 0; padding: 0; border-width:0; list-style: none; } + + +/*HIDDEN BY DEFAULT*/ + .wym_skin_compact .wym_area_left { display: none; } + .wym_skin_compact .wym_area_right { display: none; } + + +/*TYPO*/ + .wym_skin_compact { font-size: 10px; font-family: Verdana, Arial, sans-serif; } + .wym_skin_compact h2 { font-size: 110%; /* = 11px */} + .wym_skin_compact h3 { font-size: 100%; /* = 10px */} + .wym_skin_compact li { font-size: 100%; /* = 10px */} + + +/*WYM_BOX*/ + .wym_skin_compact { border: 1px solid gray; padding: 5px} + + /*auto-clear the wym_box*/ + .wym_skin_compact:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } + * html .wym_skin_compact { height: 1%;} + + +/*WYM_HTML*/ + .wym_skin_compact .wym_html { width: 98%;} + .wym_skin_compact .wym_html textarea { font-size: 120%; width: 100%; height: 200px; border: 1px solid gray; background: white; } + + +/*WYM_IFRAME*/ + .wym_skin_compact .wym_iframe { width: 98%;} + .wym_skin_compact .wym_iframe iframe { width: 100%; height: 200px; border: 1px solid gray; background: white; display: block; } + + +/*AREAS*/ + .wym_skin_compact .wym_area_left { width: 100px; float: left;} + .wym_skin_compact .wym_area_right { width: 150px; float: right;} + .wym_skin_compact .wym_area_bottom { height: 1%; clear: both;} + * html .wym_skin_compact .wym_area_main { height: 1%;} + * html .wym_skin_compact .wym_area_top { height: 1%;} + *+html .wym_skin_compact .wym_area_top { height: 1%;} + +/*SECTIONS SYSTEM*/ + + /*common defaults for all sections*/ + .wym_skin_compact .wym_section { margin-bottom: 5px; } + .wym_skin_compact .wym_section h2, + .wym_skin_compact .wym_section h3 { padding: 1px 3px; margin: 0; } + .wym_skin_compact .wym_section a { padding: 0 3px; display: block; text-decoration: none; color: black; } + .wym_skin_compact .wym_section a:hover { background-color: yellow; } + /*hide section titles by default*/ + .wym_skin_compact .wym_section h2 { display: none; } + /*disable any margin-collapse*/ + .wym_skin_compact .wym_section { padding-top: 1px; padding-bottom: 1px; } + /*auto-clear sections*/ + .wym_skin_compact .wym_section ul:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } + * html .wym_skin_compact .wym_section ul { height: 1%;} + + /*option: add this class to a section to make it render as a panel*/ + .wym_skin_compact .wym_panel { } + .wym_skin_compact .wym_panel h2 { display: block; } + + /*option: add this class to a section to make it render as a dropdown menu*/ + .wym_skin_compact .wym_dropdown h2 { display: block; } + .wym_skin_compact .wym_dropdown ul { display: none; position: absolute; background: white; } + .wym_skin_compact .wym_dropdown:hover ul, + .wym_skin_compact .wym_dropdown.hover ul { display: block; } + + /*option: add this class to a section to make its elements render buttons (icons are only available for the wym_tools section for now)*/ + .wym_skin_compact .wym_buttons li { float:left;} + .wym_skin_compact .wym_buttons a { width: 20px; height: 20px; overflow: hidden; padding: 2px } + /*image replacements*/ + .wym_skin_compact .wym_buttons li a { background: url(icons.png) no-repeat; text-indent: -9999px;} + .wym_skin_compact .wym_buttons li.wym_tools_strong a { background-position: 0 -382px;} + .wym_skin_compact .wym_buttons li.wym_tools_emphasis a { background-position: 0 -22px;} + .wym_skin_compact .wym_buttons li.wym_tools_superscript a { background-position: 0 -430px;} + .wym_skin_compact .wym_buttons li.wym_tools_subscript a { background-position: 0 -454px;} + .wym_skin_compact .wym_buttons li.wym_tools_ordered_list a { background-position: 0 -48px;} + .wym_skin_compact .wym_buttons li.wym_tools_unordered_list a{ background-position: 0 -72px;} + .wym_skin_compact .wym_buttons li.wym_tools_indent a { background-position: 0 -574px;} + .wym_skin_compact .wym_buttons li.wym_tools_outdent a { background-position: 0 -598px;} + .wym_skin_compact .wym_buttons li.wym_tools_undo a { background-position: 0 -502px;} + .wym_skin_compact .wym_buttons li.wym_tools_redo a { background-position: 0 -526px;} + .wym_skin_compact .wym_buttons li.wym_tools_link a { background-position: 0 -96px;} + .wym_skin_compact .wym_buttons li.wym_tools_unlink a { background-position: 0 -168px;} + .wym_skin_compact .wym_buttons li.wym_tools_image a { background-position: 0 -121px;} + .wym_skin_compact .wym_buttons li.wym_tools_table a { background-position: 0 -144px;} + .wym_skin_compact .wym_buttons li.wym_tools_paste a { background-position: 0 -552px;} + .wym_skin_compact .wym_buttons li.wym_tools_html a { background-position: 0 -193px;} + .wym_skin_compact .wym_buttons li.wym_tools_preview a { background-position: 0 -408px;} + +/*DECORATION*/ + .wym_skin_compact .wym_section h2 { background: #f0f0f0; border: solid gray; border-width: 0 0 1px;} + .wym_skin_compact .wym_section h2 span { color: gray;} + .wym_skin_compact .wym_panel { padding: 0; border: solid gray; border-width: 1px; background: white;} + .wym_skin_compact .wym_panel ul { margin: 2px 0 5px; } + .wym_skin_compact .wym_dropdown { padding: 0; border: solid gray; border-width: 1px 1px 0 1px; } + .wym_skin_compact .wym_dropdown ul { border: solid gray; border-width: 0 1px 1px 1px; margin-left: -1px; padding: 5px 10px 5px 3px;} + +/*DIALOGS*/ + .wym_dialog div.row { margin-bottom: 5px;} + .wym_dialog div.row input { margin-right: 5px;} + .wym_dialog div.row label { float: left; width: 150px; display: block; text-align: right; margin-right: 10px; } + .wym_dialog div.row-indent { padding-left: 160px; } + /*autoclearing*/ + .wym_dialog div.row:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } + .wym_dialog div.row { display: inline-block; } + /* Hides from IE-mac \*/ + * html .wym_dialog div.row { height: 1%; } + .wym_dialog div.row { display: block; } + /* End hide from IE-mac */ + +/*WYMEDITOR_LINK*/ + a.wym_wymeditor_link { text-indent: -9999px; float: right; display: block; width: 50px; height: 15px; background: url(../wymeditor_icon.png); overflow: hidden; text-decoration: none; } diff --git a/public/wymeditor/wymeditor/skins/compact/skin.js b/public/wymeditor/wymeditor/skins/compact/skin.js new file mode 100644 index 0000000..2fa961b --- /dev/null +++ b/public/wymeditor/wymeditor/skins/compact/skin.js @@ -0,0 +1,37 @@ +WYMeditor.SKINS.compact = { + + init: function(wym) { + + //move the containers panel to the top area + jQuery(wym._options.containersSelector + ', ' + + wym._options.classesSelector, wym._box) + .appendTo( jQuery("div.wym_area_top", wym._box) ) + .addClass("wym_dropdown") + .css({"margin-right": "10px", "width": "120px", "float": "left"}); + + //render following sections as buttons + jQuery(wym._options.toolsSelector, wym._box) + .addClass("wym_buttons") + .css({"margin-right": "10px", "float": "left"}); + + //make hover work under IE < 7 + jQuery(".wym_section", wym._box).hover(function(){ + jQuery(this).addClass("hover"); + },function(){ + jQuery(this).removeClass("hover"); + }); + + var postInit = wym._options.postInit; + wym._options.postInit = function(wym) { + + if (postInit) { + postInit.call(wym, wym); + } + var rule = { + name: 'body', + css: 'background-color: #f0f0f0;' + }; + wym.addCssRule( wym._doc.styleSheets[0], rule); + }; + } +}; diff --git a/public/wymeditor/wymeditor/skins/default/icons.png b/public/wymeditor/wymeditor/skins/default/icons.png new file mode 100644 index 0000000..c6eb463 Binary files /dev/null and b/public/wymeditor/wymeditor/skins/default/icons.png differ diff --git a/public/wymeditor/wymeditor/skins/default/skin.css b/public/wymeditor/wymeditor/skins/default/skin.css new file mode 100644 index 0000000..bbffd6e --- /dev/null +++ b/public/wymeditor/wymeditor/skins/default/skin.css @@ -0,0 +1,133 @@ +/* + * WYMeditor : what you see is What You Mean web-based editor + * Copyright (c) 2005 - 2009 Jean-Francois Hovinne, http://www.wymeditor.org/ + * Dual licensed under the MIT (MIT-license.txt) + * and GPL (GPL-license.txt) licenses. + * + * For further information visit: + * http://www.wymeditor.org/ + * + * File Name: + * skin.css + * main stylesheet for the default WYMeditor skin + * See the documentation for more info. + * + * File Authors: + * Daniel Reszka (d.reszka a-t wymeditor dotorg) +*/ + +/*TRYING TO RESET STYLES THAT MAY INTERFERE WITH WYMEDITOR*/ + .wym_skin_default p, .wym_skin_default h2, .wym_skin_default h3, + .wym_skin_default ul, .wym_skin_default li { background: transparent url(); margin: 0; padding: 0; border-width:0; list-style: none; } + + +/*HIDDEN BY DEFAULT*/ + .wym_skin_default .wym_area_left { display: none; } + .wym_skin_default .wym_area_right { display: block; } + + +/*TYPO*/ + .wym_skin_default { font-size: 62.5%; font-family: Verdana, Arial, sans-serif; } + .wym_skin_default h2 { font-size: 110%; /* = 11px */} + .wym_skin_default h3 { font-size: 100%; /* = 10px */} + .wym_skin_default li { font-size: 100%; /* = 10px */} + + +/*WYM_BOX*/ + .wym_skin_default { border: 1px solid gray; background: #f2f2f2; padding: 5px} + + /*auto-clear the wym_box*/ + .wym_skin_default:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } + * html .wym_skin_default { height: 1%;} + + +/*WYM_HTML*/ + .wym_skin_default .wym_html { width: 98%;} + .wym_skin_default .wym_html textarea { width: 100%; height: 200px; border: 1px solid gray; background: white; } + + +/*WYM_IFRAME*/ + .wym_skin_default .wym_iframe { width: 98%;} + .wym_skin_default .wym_iframe iframe { width: 100%; height: 200px; border: 1px solid gray; background: white } + + +/*AREAS*/ + .wym_skin_default .wym_area_left { width: 150px; float: left;} + .wym_skin_default .wym_area_right { width: 150px; float: right;} + .wym_skin_default .wym_area_bottom { height: 1%; clear: both;} + * html .wym_skin_default .wym_area_main { height: 1%;} + * html .wym_skin_default .wym_area_top { height: 1%;} + *+html .wym_skin_default .wym_area_top { height: 1%;} + +/*SECTIONS SYSTEM*/ + + /*common defaults for all sections*/ + .wym_skin_default .wym_section { margin-bottom: 5px; } + .wym_skin_default .wym_section h2, + .wym_skin_default .wym_section h3 { padding: 1px 3px; margin: 0; } + .wym_skin_default .wym_section a { padding: 0 3px; display: block; text-decoration: none; color: black; } + .wym_skin_default .wym_section a:hover { background-color: yellow; } + /*hide section titles by default*/ + .wym_skin_default .wym_section h2 { display: none; } + /*disable any margin-collapse*/ + .wym_skin_default .wym_section { padding-top: 1px; padding-bottom: 1px; } + /*auto-clear sections*/ + .wym_skin_default .wym_section ul:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } + * html .wym_skin_default .wym_section ul { height: 1%;} + + /*option: add this class to a section to make it render as a panel*/ + .wym_skin_default .wym_panel { } + .wym_skin_default .wym_panel h2 { display: block; } + + /*option: add this class to a section to make it render as a dropdown menu*/ + .wym_skin_default .wym_dropdown h2 { display: block; } + .wym_skin_default .wym_dropdown ul { display: none; position: absolute; background: white; } + .wym_skin_default .wym_dropdown:hover ul, + .wym_skin_default .wym_dropdown.hover ul { display: block; } + + /*option: add this class to a section to make its elements render buttons (icons are only available for the wym_tools section for now)*/ + .wym_skin_default .wym_buttons li { float:left;} + .wym_skin_default .wym_buttons a { width: 20px; height: 20px; overflow: hidden; padding: 2px } + /*image replacements*/ + .wym_skin_default .wym_buttons li a { background: url(icons.png) no-repeat; text-indent: -9999px;} + .wym_skin_default .wym_buttons li.wym_tools_strong a { background-position: 0 -382px;} + .wym_skin_default .wym_buttons li.wym_tools_emphasis a { background-position: 0 -22px;} + .wym_skin_default .wym_buttons li.wym_tools_superscript a { background-position: 0 -430px;} + .wym_skin_default .wym_buttons li.wym_tools_subscript a { background-position: 0 -454px;} + .wym_skin_default .wym_buttons li.wym_tools_ordered_list a { background-position: 0 -48px;} + .wym_skin_default .wym_buttons li.wym_tools_unordered_list a{ background-position: 0 -72px;} + .wym_skin_default .wym_buttons li.wym_tools_indent a { background-position: 0 -574px;} + .wym_skin_default .wym_buttons li.wym_tools_outdent a { background-position: 0 -598px;} + .wym_skin_default .wym_buttons li.wym_tools_undo a { background-position: 0 -502px;} + .wym_skin_default .wym_buttons li.wym_tools_redo a { background-position: 0 -526px;} + .wym_skin_default .wym_buttons li.wym_tools_link a { background-position: 0 -96px;} + .wym_skin_default .wym_buttons li.wym_tools_unlink a { background-position: 0 -168px;} + .wym_skin_default .wym_buttons li.wym_tools_image a { background-position: 0 -121px;} + .wym_skin_default .wym_buttons li.wym_tools_table a { background-position: 0 -144px;} + .wym_skin_default .wym_buttons li.wym_tools_paste a { background-position: 0 -552px;} + .wym_skin_default .wym_buttons li.wym_tools_html a { background-position: 0 -193px;} + .wym_skin_default .wym_buttons li.wym_tools_preview a { background-position: 0 -408px;} + +/*DECORATION*/ + .wym_skin_default .wym_section h2 { background: #ddd; border: solid gray; border-width: 0 0 1px;} + .wym_skin_default .wym_section h2 span { color: gray;} + .wym_skin_default .wym_panel { padding: 0; border: solid gray; border-width: 1px; background: white;} + .wym_skin_default .wym_panel ul { margin: 2px 0 5px; } + .wym_skin_default .wym_dropdown { padding: 0; border: solid gray; border-width: 1px 1px 0 1px; } + .wym_skin_default .wym_dropdown ul { border: solid gray; border-width: 0 1px 1px 1px; margin-left: -1px; padding: 5px 10px 5px 3px;} + +/*DIALOGS*/ + .wym_dialog div.row { margin-bottom: 5px;} + .wym_dialog div.row input { margin-right: 5px;} + .wym_dialog div.row label { float: left; width: 120px; display: block; text-align: right; margin-right: 10px; } + .wym_dialog div.row-indent { padding-left: 160px; } + /*autoclearing*/ + .wym_dialog div.row:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } + .wym_dialog div.row { display: inline-block; } + /* Hides from IE-mac \*/ + * html .wym_dialog div.row { height: 1%; } + .wym_dialog div.row { display: block; } + /* End hide from IE-mac */ + +/*WYMEDITOR_LINK*/ + a.wym_wymeditor_link { text-indent: -9999px; float: right; display: block; width: 50px; height: 15px; background: url(../wymeditor_icon.png); overflow: hidden; text-decoration: none; } diff --git a/public/wymeditor/wymeditor/skins/default/skin.js b/public/wymeditor/wymeditor/skins/default/skin.js new file mode 100644 index 0000000..5f6d97e --- /dev/null +++ b/public/wymeditor/wymeditor/skins/default/skin.js @@ -0,0 +1,40 @@ +WYMeditor.SKINS['default'] = { + + init: function(wym) { + + //render following sections as panels + jQuery(wym._box).find(wym._options.classesSelector) + .addClass("wym_panel"); + + //render following sections as buttons + jQuery(wym._box).find(wym._options.toolsSelector) + .addClass("wym_buttons"); + + //render following sections as dropdown menus + jQuery(wym._box).find(wym._options.containersSelector) + .addClass("wym_dropdown") + .find(WYMeditor.H2) + .append(" >"); + + // auto add some margin to the main area sides if left area + // or right area are not empty (if they contain sections) + jQuery(wym._box).find("div.wym_area_right ul") + .parents("div.wym_area_right").show() + .parents(wym._options.boxSelector) + .find("div.wym_area_main") + .css({"margin-right": "155px"}); + + jQuery(wym._box).find("div.wym_area_left ul") + .parents("div.wym_area_left").show() + .parents(wym._options.boxSelector) + .find("div.wym_area_main") + .css({"margin-left": "155px"}); + + //make hover work under IE < 7 + jQuery(wym._box).find(".wym_section").hover(function(){ + jQuery(this).addClass("hover"); + },function(){ + jQuery(this).removeClass("hover"); + }); + } +}; diff --git a/public/wymeditor/wymeditor/skins/minimal/images/bg.header.gif b/public/wymeditor/wymeditor/skins/minimal/images/bg.header.gif new file mode 100644 index 0000000..b2d2907 Binary files /dev/null and b/public/wymeditor/wymeditor/skins/minimal/images/bg.header.gif differ diff --git a/public/wymeditor/wymeditor/skins/minimal/images/bg.selector.silver.gif b/public/wymeditor/wymeditor/skins/minimal/images/bg.selector.silver.gif new file mode 100644 index 0000000..e65976b Binary files /dev/null and b/public/wymeditor/wymeditor/skins/minimal/images/bg.selector.silver.gif differ diff --git a/public/wymeditor/wymeditor/skins/minimal/images/bg.wymeditor.png b/public/wymeditor/wymeditor/skins/minimal/images/bg.wymeditor.png new file mode 100644 index 0000000..1e84813 Binary files /dev/null and b/public/wymeditor/wymeditor/skins/minimal/images/bg.wymeditor.png differ diff --git a/public/wymeditor/wymeditor/skins/minimal/images/icons.silver.gif b/public/wymeditor/wymeditor/skins/minimal/images/icons.silver.gif new file mode 100644 index 0000000..8c6a4fb Binary files /dev/null and b/public/wymeditor/wymeditor/skins/minimal/images/icons.silver.gif differ diff --git a/public/wymeditor/wymeditor/skins/minimal/skin.css b/public/wymeditor/wymeditor/skins/minimal/skin.css new file mode 100644 index 0000000..cea8d84 --- /dev/null +++ b/public/wymeditor/wymeditor/skins/minimal/skin.css @@ -0,0 +1,131 @@ +/* + * WYMeditor : what you see is What You Mean web-based editor + * Copyright (c) 2005 - 2009 Jean-Francois Hovinne, http://www.wymeditor.org/ + * Dual licensed under the MIT (MIT-license.txt) + * and GPL (GPL-license.txt) licenses. + * + * For further information visit: + * http://www.wymeditor.org/ + * + * File Name: + * skin.css + * main stylesheet for the minimal WYMeditor skin + * See the documentation for more info. + * + * File Authors: + * Jean-Francois Hovinne + * Scott Lewis (see Silver skin) +*/ + +/* Set iframe */ +.wym_skin_minimal div.wym_iframe iframe { + width: 90%; + height: 200px; +} + +/* Hide h2 by default */ +.wym_skin_minimal h2 { + display: none; +} + +/* Show specific h2 */ +.wym_skin_minimal div.wym_tools h2, +.wym_skin_minimal div.wym_containers h2, +.wym_skin_minimal div.wym_classes h2 { + display: block; +} + +.wym_skin_minimal div.wym_section ul { + margin: 0; +} + +.wym_skin_minimal div.wym_section ul li { + float: left; + list-style-type: none; + margin-right: 5px; +} + +.wym_skin_minimal div.wym_area_top, +.wym_skin_minimal div.wym_area_right, +.wym_skin_minimal div.wym_containers, +.wym_skin_minimal div.wym_classes { + float: left; +} + +.wym_skin_minimal div.wym_area_main { + clear: both; +} + +.wym_skin_minimal div.wym_html { + width: 90%; +} + +.wym_skin_minimal textarea.wym_html_val { + width: 100%; + height: 100px; +} + +/* DROPDOWNS (see Silver skin) */ +.wym_skin_minimal div.wym_dropdown { + cursor: pointer; + margin: 0px 4px 10px 0px; + padding: 0px; + z-index: 1001; + display: block; +} + +.wym_skin_minimal div.wym_dropdown ul { + display: none; + width: 124px; + padding: 0px; + margin: 0px; + list-style-type: none; + list-style-image: none; + z-index: 1002; + position: absolute; + border-top: 1px solid #AAA; +} + +.wym_skin_minimal div.wym_dropdown ul li { + width: 146px; + height: 20px; + padding: 0px; + margin: 0px; + border: 1px solid #777; + border-top: none; + background: #EEE; + list-style-image: none; +} + +.wym_skin_minimal div.wym_dropdown h2 { + width: 138px; + height: 16px; + color: #000; + background-image: url(images/bg.selector.silver.gif); + background-position: 0px -18px; + background-repeat: no-repeat; + border: none; + font-family: "Trebuchet MS", Verdana, Arial, Helvetica, Sanserif; + font-size: 12px; + font-weight: bold; + padding: 2px 0px 0px 10px; + margin: 0px; +} + +.wym_skin_minimal div.wym_dropdown a { + text-decoration: none; + font-family: "Trebuchet MS", Verdana, Arial, Helvetica, Sanserif; + font-size: 12px; + padding: 5px 0px 0px 10px; + display: block; + width: 136px; + height: 15px; + color: #000; + text-align: left; + margin-left: 0px; +} + +.wym_skin_minimal div.wym_dropdown a:hover { + background: #BBB; + border-bottom: none; +} diff --git a/public/wymeditor/wymeditor/skins/minimal/skin.js b/public/wymeditor/wymeditor/skins/minimal/skin.js new file mode 100644 index 0000000..d2536ce --- /dev/null +++ b/public/wymeditor/wymeditor/skins/minimal/skin.js @@ -0,0 +1,30 @@ +jQuery.fn.selectify = function() { + return this.each(function() { + jQuery(this).hover( + function() { + jQuery("h2", this).css("background-position", "0px -18px"); + jQuery("ul", this).fadeIn("fast"); + }, + function() { + jQuery("h2", this).css("background-position", ""); + jQuery("ul", this).fadeOut("fast"); + } + ); + }); +}; + +WYMeditor.SKINS.minimal = { + //placeholder for the skin JS, if needed + + //init the skin + //wym is the WYMeditor.editor instance + init: function(wym) { + + //render following sections as dropdown menus + jQuery(wym._box).find(wym._options.toolsSelector + ', ' + wym._options.containersSelector + ', ' + wym._options.classesSelector) + .addClass("wym_dropdown") + .selectify(); + + + } +}; diff --git a/public/wymeditor/wymeditor/skins/silver/COPYING b/public/wymeditor/wymeditor/skins/silver/COPYING new file mode 100755 index 0000000..94a9ed0 --- /dev/null +++ b/public/wymeditor/wymeditor/skins/silver/COPYING @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/public/wymeditor/wymeditor/skins/silver/README b/public/wymeditor/wymeditor/skins/silver/README new file mode 100755 index 0000000..130dc46 --- /dev/null +++ b/public/wymeditor/wymeditor/skins/silver/README @@ -0,0 +1,27 @@ +/** +* @version Alpha 0.1 2008-05-10 23:28:43 $ +* @package Silver skin for WYMeditor +* @copyright Copyright (C) 2008 Scott Edwin Lewis. All rights reserved. +* @license GNU/GPL, see COPYING +* Silver skin for WYMeditor is free software and is licensed under the +* GNU General Public License. See COPYING for copyright notices and details. +*/ + +Adds custom buttons and color palette to the WYMeditor XHTML Editor. + +INSTALLATION: + +1. Copy the entire /silver/ directory to /wymeditor/skins/ +2. Initialize the WYMeditor 'skin' option as below: + + + +That's it. You're done. diff --git a/public/wymeditor/wymeditor/skins/silver/images/bg.header.gif b/public/wymeditor/wymeditor/skins/silver/images/bg.header.gif new file mode 100644 index 0000000..b2d2907 Binary files /dev/null and b/public/wymeditor/wymeditor/skins/silver/images/bg.header.gif differ diff --git a/public/wymeditor/wymeditor/skins/silver/images/bg.selector.silver.gif b/public/wymeditor/wymeditor/skins/silver/images/bg.selector.silver.gif new file mode 100644 index 0000000..e65976b Binary files /dev/null and b/public/wymeditor/wymeditor/skins/silver/images/bg.selector.silver.gif differ diff --git a/public/wymeditor/wymeditor/skins/silver/images/bg.wymeditor.png b/public/wymeditor/wymeditor/skins/silver/images/bg.wymeditor.png new file mode 100644 index 0000000..1e84813 Binary files /dev/null and b/public/wymeditor/wymeditor/skins/silver/images/bg.wymeditor.png differ diff --git a/public/wymeditor/wymeditor/skins/silver/images/icons.silver.gif b/public/wymeditor/wymeditor/skins/silver/images/icons.silver.gif new file mode 100644 index 0000000..8c6a4fb Binary files /dev/null and b/public/wymeditor/wymeditor/skins/silver/images/icons.silver.gif differ diff --git a/public/wymeditor/wymeditor/skins/silver/skin.css b/public/wymeditor/wymeditor/skins/silver/skin.css new file mode 100644 index 0000000..8284d81 --- /dev/null +++ b/public/wymeditor/wymeditor/skins/silver/skin.css @@ -0,0 +1,297 @@ +/* + * WYMeditor : what you see is What You Mean web-based editor + * Copyright (c) 2005 - 2009 Jean-Francois Hovinne, http://www.wymeditor.org/ + * Dual licensed under the MIT (MIT-license.txt) + * and GPL (GPL-license.txt) licenses. + * + * For further information visit: + * http://www.wymeditor.org/ + * + * File Name: + * screen.css + * main stylesheet for the default WYMeditor skin + * See the documentation for more info. + * + * File Authors: + * Daniel Reszka (d.reszka a-t wymeditor dotorg) + * Scott Edwin Lewis +*/ + +/*TRYING TO RESET STYLES THAT MAY INTERFERE WITH WYMEDITOR*/ + .wym_skin_silver p, .wym_skin_silver h2, .wym_skin_silver h3, + .wym_skin_silver ul, .wym_skin_silver li { background: transparent url(); margin: 0; padding: 0; border-width:0; list-style: none; } + + +/*HIDDEN BY DEFAULT*/ + .wym_skin_silver .wym_area_left { display: none; } + .wym_skin_silver .wym_area_right { display: block; } + + +/*TYPO*/ + .wym_skin_silver { font-size: 62.5%; font-family: Verdana, Arial, sans-serif; } + .wym_skin_silver h2 { font-size: 110%; /* = 11px */} + .wym_skin_silver h3 { font-size: 100%; /* = 10px */} + .wym_skin_silver li { font-size: 100%; /* = 10px */} + + +/*WYM_BOX*/ + .wym_skin_silver { border: 1px solid gray; background: #f2f2f2; padding: 0px; margin: 0px;} + + /*auto-clear the wym_box*/ + .wym_skin_silver:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } + * html .wym_skin_silver { height: 1%;} + + +/*WYM_HTML*/ + .wym_skin_silver .wym_html { width: 98%;} + .wym_skin_silver .wym_html textarea { width: 100%; height: 200px; border: 1px solid gray; background: white; } + + +/*WYM_IFRAME*/ + .wym_skin_silver .wym_iframe { width: 98%;} + .wym_skin_silver .wym_iframe iframe { width: 100%; height: 200px; border: 1px solid gray; background: white } + + +/*AREAS*/ + .wym_skin_silver .wym_area_left { width: 150px; float: left;} + .wym_skin_silver .wym_area_right { width: 150px; float: right;} + .wym_skin_silver .wym_area_bottom { height: 1%; clear: both;} + * html .wym_skin_silver .wym_area_main { height: 1%;} + * html .wym_skin_silver .wym_area_top { height: 1%;} + *+html .wym_skin_silver .wym_area_top { height: 1%;} + +/*SECTIONS SYSTEM*/ + + /*common defaults for all sections*/ + .wym_skin_silver .wym_section { margin-bottom: 5px; } + .wym_skin_silver .wym_section h2, + .wym_skin_silver .wym_section h3 { padding: 1px 3px; margin: 0; cursor: pointer; } + .wym_skin_silver .wym_section a { padding: 5px 0px 0px 10px; display: block; text-decoration: none; color: black; } + .wym_skin_silver .wym_section a:hover { /*background-color: #DDD;*/} + /*hide section titles by default*/ + .wym_skin_silver .wym_section h2 { display: none; } + /*disable any margin-collapse*/ + .wym_skin_silver .wym_section { padding-top: 1px; padding-bottom: 1px; } + /*auto-clear sections*/ + .wym_skin_silver .wym_section ul:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; padding: 0px; } + * html .wym_skin_silver .wym_section ul { height: 1%;} + .wym_skin_silver .wym_section li {} + + /*option: add this class to a section to make it render as a panel*/ + .wym_skin_silver .wym_panel { } + .wym_skin_silver .wym_panel h2 { display: block; font-size: 11px; } + + /*option: add this class to a section to make it render as a dropdown menu*/ + .wym_skin_silver .wym_dropdown h2 { display: block; font-size: 11px;} + .wym_skin_silver .wym_dropdown ul { position: absolute; background: white; padding: 0px;} + .wym_skin_silver .wym_dropdown:hover ul, + .wym_skin_silver .wym_dropdown.hover ul { cursor: pointer;} + .wym_skin_silver .wym_dropdown ul li a {/*border-bottom: 1px solid #AAA;*/} + + /*option: add this class to a section to make its elements render buttons (icons are only available for the wym_tools section for now)*/ + .wym_skin_silver .wym_buttons li { float:left;} + .wym_skin_silver .wym_buttons a { width: 20px; height: 20px; overflow: hidden; padding: 2px; text-decoration: none !important; border: 1px solid #666; } + .wym_skin_silver .wym_buttons a:hover { text-decoration: none !important; border: 1px solid #000;} + /*image replacements*/ + .wym_skin_silver .wym_buttons li a { background: url(images/icons.silver.gif) no-repeat; text-indent: -9999px;} + .wym_skin_silver .wym_buttons li.wym_tools_strong a { background-position: 0 -384px;} + .wym_skin_silver .wym_buttons li.wym_tools_emphasis a { background-position: 0 -24px;} + .wym_skin_silver .wym_buttons li.wym_tools_superscript a { background-position: 0 -432px;} + .wym_skin_silver .wym_buttons li.wym_tools_subscript a { background-position: 0 -456px;} + .wym_skin_silver .wym_buttons li.wym_tools_ordered_list a { background-position: 0 -48px;} + .wym_skin_silver .wym_buttons li.wym_tools_unordered_list a{ background-position: 0 -72px;} + .wym_skin_silver .wym_buttons li.wym_tools_indent a { background-position: 0 -600px;} + .wym_skin_silver .wym_buttons li.wym_tools_outdent a { background-position: 0 -624px;} + .wym_skin_silver .wym_buttons li.wym_tools_undo a { background-position: 0 -504px;} + .wym_skin_silver .wym_buttons li.wym_tools_redo a { background-position: 0 -528px;} + .wym_skin_silver .wym_buttons li.wym_tools_link a { background-position: 0 -96px;} + .wym_skin_silver .wym_buttons li.wym_tools_unlink a { background-position: 0 -168px;} + .wym_skin_silver .wym_buttons li.wym_tools_image a { background-position: 0 -120px;} + .wym_skin_silver .wym_buttons li.wym_tools_table a { background-position: 0 -144px;} + .wym_skin_silver .wym_buttons li.wym_tools_paste a { background-position: 0 -552px;} + .wym_skin_silver .wym_buttons li.wym_tools_html a { background-position: 0 -192px;} + .wym_skin_silver .wym_buttons li.wym_tools_preview a { background-position: 0 -408px;} + .wym_skin_silver .wym_buttons li.wym_tools_gadget a { background-position: 0 -576px;} + + .wym_skin_silver .wym_buttons li.wym_tools_strong a:hover { background-position: -24px -384px;} + .wym_skin_silver .wym_buttons li.wym_tools_emphasis a:hover { background-position: -24px -24px;} + .wym_skin_silver .wym_buttons li.wym_tools_superscript a:hover { background-position: -24px -432px;} + .wym_skin_silver .wym_buttons li.wym_tools_subscript a:hover { background-position: -24px -456px;} + .wym_skin_silver .wym_buttons li.wym_tools_ordered_list a:hover { background-position: -24px -48px;} + .wym_skin_silver .wym_buttons li.wym_tools_unordered_list a:hover{ background-position: -24px -72px;} + .wym_skin_silver .wym_buttons li.wym_tools_indent a:hover { background-position: -24px -600px;} + .wym_skin_silver .wym_buttons li.wym_tools_outdent a:hover { background-position: -24px -624px;} + .wym_skin_silver .wym_buttons li.wym_tools_undo a:hover { background-position: -24px -504px;} + .wym_skin_silver .wym_buttons li.wym_tools_redo a:hover { background-position: -24px -528px;} + .wym_skin_silver .wym_buttons li.wym_tools_link a:hover { background-position: -24px -96px;} + .wym_skin_silver .wym_buttons li.wym_tools_unlink a:hover { background-position: -24px -168px;} + .wym_skin_silver .wym_buttons li.wym_tools_image a:hover { background-position: -24px -120px;} + .wym_skin_silver .wym_buttons li.wym_tools_table a:hover { background-position: -24px -144px;} + .wym_skin_silver .wym_buttons li.wym_tools_paste a:hover { background-position: -24px -552px;} + .wym_skin_silver .wym_buttons li.wym_tools_html a:hover { background-position: -24px -192px;} + .wym_skin_silver .wym_buttons li.wym_tools_preview a:hover { background-position: -24px -408px;} + .wym_skin_silver .wym_buttons li.wym_tools_gadget a:hover { background-position: -24px -576px;} + +/*DECORATION*/ + .wym_skin_silver .wym_section h2 { background: #ddd; border: none;} + .wym_skin_silver .wym_section h2 span { color: gray;} + .wym_skin_silver .wym_panel { padding: 0; border: solid gray; border-width: 0px;} + .wym_skin_silver .wym_panel ul { margin: 2px 0 5px; } + .wym_skin_silver .wym_dropdown { padding: 0; border: none; } + .wym_skin_silver .wym_dropdown ul { border: none; margin-left: -1px; padding: 0px;} + +/*DIALOGS*/ + .wym_dialog div.row { margin-bottom: 5px;} + .wym_dialog div.row input { margin-right: 5px;} + .wym_dialog div.row label { float: left; width: 150px; display: block; text-align: right; margin-right: 10px; } + .wym_dialog div.row-indent { padding-left: 160px; } + /*autoclearing*/ + .wym_dialog div.row:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } + .wym_dialog div.row { display: inline-block; } + /* Hides from IE-mac \*/ + * html .wym_dialog div.row { height: 1%; } + .wym_dialog div.row { display: block; } + /* End hide from IE-mac */ + +/*WYMEDITOR_LINK*/ + a.wym_wymeditor_link + { + text-indent: -9999px; + float: right; + display: block; + width: 50px; + height: 15px; + background: url(../wymeditor_icon.png); + background-position: 1px 1px; + background-repeat: no-repeat; + overflow: hidden; + text-decoration: none; + padding: 1px !important; + border: 1px solid #333 !important; + background-color: #FFF !important; + } + +.wym_box +{ + padding: 0px !important; + margin: 0px; +} +.wym_inner +{ + border-left: 1px solid #FFF; + border-top: 1px solid #FFF; + border-right: 1px solid #FFF; + border-bottom: 1px solid #FFF; + padding: 5px; + background-color: #B8C1C4; + height: auto; +} + +.clear {clear: both;} + +div.wym_dropdown +{ + cursor: pointer; + width: 138px !important; + margin: 0px 4px 10px 0px !important; + padding: 0px; + z-index: 1001; + display: block; + border: 1px solid red; +} + +div.wym_dropdown ul +{ + display: none; + width: 124px; + padding: 0px !important; + margin: 0px !important; + list-style-type: none; + list-style-image: none; + z-index: 1002; + position: absolute; + border-top: 1px solid #AAA; +} + +div.wym_dropdown ul li +{ + width: 146px; + height: 20px; + padding: 0px !important; + margin: 0px; + border: 1px solid #777; + border-top: none; + background: #DDD; + list-style-image: none; +} + +div.wym_dropdown h2 +{ + width: 138px; + height: 16px; + color: #000 !important; + background-image: url(images/bg.selector.silver.gif) !important; + background-position: 0px -18px; + background-repeat: no-repeat; + border: none; + font-family: "Trebuchet MS", Verdana, Arial, Helvetica, Sanserif; + font-size: 12px !important; + font-weight: bold !important; + padding: 2px 0px 0px 10px !important; + margin: 0px !important; +} + +.wym_skin_silver .wym_panel h2 +{ + width: 138px; + height: 16px; + color: #000 !important; + background-image: url(images/bg.header.gif) !important; + background-position: 0px 0px; + background-repeat: no-repeat; + border: none; + font-family: "Trebuchet MS", Verdana, Arial, Helvetica, Sanserif; + font-size: 12px !important; + font-weight: bold !important; + padding: 2px 0px 0px 10px !important; + margin: 0px !important; +} + +.wym_skin_silver .wym_panel ul +{ + margin-top: 0px !important; +} + +.wym_skin_silver .wym_panel ul li +{ + width: 146px; + height: 20px; + padding: 0px !important; + margin: 0px; + border: 1px solid #777; + border-top: none; + background: #DDD; + list-style-image: none; +} + +.wym_skin_silver .wym_panel a, +div.wym_dropdown a +{ + text-decoration: none; + font-family: "Trebuchet MS", Verdana, Arial, Helvetica, Sanserif; + font-size: 12px; + padding: 5px 0px 0px 10px !important; + display: block; + width: 136px; + height: 15px; + color: #000; + text-align: left !important; + margin-left: 0px !important; +} + +div.wym_dropdown a:hover, +.wym_skin_silver .wym_panel a:hover +{ + background: #BBB; + border-bottom: none !important; +} diff --git a/public/wymeditor/wymeditor/skins/silver/skin.js b/public/wymeditor/wymeditor/skins/silver/skin.js new file mode 100644 index 0000000..7d94094 --- /dev/null +++ b/public/wymeditor/wymeditor/skins/silver/skin.js @@ -0,0 +1,61 @@ +/* This file is part of the Silver skin for WYMeditor by Scott Edwin Lewis */ + +jQuery.fn.selectify = function() { + return this.each(function() { + jQuery(this).hover( + function() { + jQuery("h2", this).css("background-position", "0px -18px"); + jQuery("ul", this).fadeIn("fast"); + }, + function() { + jQuery("h2", this).css("background-position", ""); + jQuery("ul", this).fadeOut("fast"); + } + ); + }); +}; + +WYMeditor.SKINS.silver = { + + init: function(wym) { + + //add some elements to improve the rendering + jQuery(wym._box) + .append('
        ') + .wrapInner('
        '); + + //render following sections as panels + jQuery(wym._box).find(wym._options.classesSelector) + .addClass("wym_panel"); + + //render following sections as buttons + jQuery(wym._box).find(wym._options.toolsSelector) + .addClass("wym_buttons"); + + //render following sections as dropdown menus + jQuery(wym._box).find(wym._options.containersSelector) + .addClass("wym_dropdown") + .selectify(); + + // auto add some margin to the main area sides if left area + // or right area are not empty (if they contain sections) + jQuery(wym._box).find("div.wym_area_right ul") + .parents("div.wym_area_right").show() + .parents(wym._options.boxSelector) + .find("div.wym_area_main") + .css({"margin-right": "155px"}); + + jQuery(wym._box).find("div.wym_area_left ul") + .parents("div.wym_area_left").show() + .parents(wym._options.boxSelector) + .find("div.wym_area_main") + .css({"margin-left": "155px"}); + + //make hover work under IE < 7 + jQuery(wym._box).find(".wym_section").hover(function(){ + jQuery(this).addClass("hover"); + },function(){ + jQuery(this).removeClass("hover"); + }); + } +}; diff --git a/public/wymeditor/wymeditor/skins/twopanels/icons.png b/public/wymeditor/wymeditor/skins/twopanels/icons.png new file mode 100644 index 0000000..c6eb463 Binary files /dev/null and b/public/wymeditor/wymeditor/skins/twopanels/icons.png differ diff --git a/public/wymeditor/wymeditor/skins/twopanels/skin.css b/public/wymeditor/wymeditor/skins/twopanels/skin.css new file mode 100644 index 0000000..7e6b8fd --- /dev/null +++ b/public/wymeditor/wymeditor/skins/twopanels/skin.css @@ -0,0 +1,134 @@ +/* + * WYMeditor : what you see is What You Mean web-based editor + * Copyright (c) 2005 - 2009 Jean-Francois Hovinne, http://www.wymeditor.org/ + * Dual licensed under the MIT (MIT-license.txt) + * and GPL (GPL-license.txt) licenses. + * + * For further information visit: + * http://www.wymeditor.org/ + * + * File Name: + * screen.css + * main stylesheet for the WYMeditor skin + * See the documentation for more info. + * + * File Authors: + * Daniel Reszka (d.reszka a-t wymeditor dotorg) + * Jean-Francois Hovinne +*/ + +/*TRYING TO RESET STYLES THAT MAY INTERFERE WITH WYMEDITOR*/ + .wym_skin_twopanels p, .wym_skin_twopanels h2, .wym_skin_twopanels h3, + .wym_skin_twopanels ul, .wym_skin_twopanels li { background: transparent url(); margin: 0; padding: 0; border-width:0; list-style: none; } + + +/*HIDDEN BY DEFAULT*/ + .wym_skin_twopanels .wym_area_left { display: block; } + .wym_skin_twopanels .wym_area_right { display: block; } + + +/*TYPO*/ + .wym_skin_twopanels { font-size: 62.5%; font-family: Verdana, Arial, sans-serif; } + .wym_skin_twopanels h2 { font-size: 110%; /* = 11px */} + .wym_skin_twopanels h3 { font-size: 100%; /* = 10px */} + .wym_skin_twopanels li { font-size: 100%; /* = 10px */} + + +/*WYM_BOX*/ + .wym_skin_twopanels { border: 1px solid gray; background: #f2f2f2; padding: 5px} + + /*auto-clear the wym_box*/ + .wym_skin_twopanels:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } + * html .wym_skin_twopanels { height: 1%;} + + +/*WYM_HTML*/ + .wym_skin_twopanels .wym_html { width: 98%;} + .wym_skin_twopanels .wym_html textarea { width: 100%; height: 200px; border: 1px solid gray; background: white; } + + +/*WYM_IFRAME*/ + .wym_skin_twopanels .wym_iframe { width: 98%;} + .wym_skin_twopanels .wym_iframe iframe { width: 100%; height: 200px; border: 1px solid gray; background: white } + + +/*AREAS*/ + .wym_skin_twopanels .wym_area_left { width: 100px; float: left;} + .wym_skin_twopanels .wym_area_right { width: 150px; float: right;} + .wym_skin_twopanels .wym_area_bottom { height: 1%; clear: both;} + * html .wym_skin_twopanels .wym_area_main { height: 1%;} + * html .wym_skin_twopanels .wym_area_top { height: 1%;} + *+html .wym_skin_twopanels .wym_area_top { height: 1%;} + +/*SECTIONS SYSTEM*/ + + /*common defaults for all sections*/ + .wym_skin_twopanels .wym_section { margin-bottom: 5px; } + .wym_skin_twopanels .wym_section h2, + .wym_skin_twopanels .wym_section h3 { padding: 1px 3px; margin: 0; } + .wym_skin_twopanels .wym_section a { padding: 0 3px; display: block; text-decoration: none; color: black; } + .wym_skin_twopanels .wym_section a:hover { background-color: yellow; } + /*hide section titles by default*/ + .wym_skin_twopanels .wym_section h2 { display: none; } + /*disable any margin-collapse*/ + .wym_skin_twopanels .wym_section { padding-top: 1px; padding-bottom: 1px; } + /*auto-clear sections*/ + .wym_skin_twopanels .wym_section ul:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } + * html .wym_skin_twopanels .wym_section ul { height: 1%;} + + /*option: add this class to a section to make it render as a panel*/ + .wym_skin_twopanels .wym_panel { } + .wym_skin_twopanels .wym_panel h2 { display: block; } + + /*option: add this class to a section to make it render as a dropdown menu*/ + .wym_skin_twopanels .wym_dropdown h2 { display: block; } + .wym_skin_twopanels .wym_dropdown ul { display: none; position: absolute; background: white; } + .wym_skin_twopanels .wym_dropdown:hover ul, + .wym_skin_twopanels .wym_dropdown.hover ul { display: block; } + + /*option: add this class to a section to make its elements render buttons (icons are only available for the wym_tools section for now)*/ + .wym_skin_twopanels .wym_buttons li { float:left;} + .wym_skin_twopanels .wym_buttons a { width: 20px; height: 20px; overflow: hidden; padding: 2px } + /*image replacements*/ + .wym_skin_twopanels .wym_buttons li a { background: url(icons.png) no-repeat; text-indent: -9999px;} + .wym_skin_twopanels .wym_buttons li.wym_tools_strong a { background-position: 0 -382px;} + .wym_skin_twopanels .wym_buttons li.wym_tools_emphasis a { background-position: 0 -22px;} + .wym_skin_twopanels .wym_buttons li.wym_tools_superscript a { background-position: 0 -430px;} + .wym_skin_twopanels .wym_buttons li.wym_tools_subscript a { background-position: 0 -454px;} + .wym_skin_twopanels .wym_buttons li.wym_tools_ordered_list a { background-position: 0 -48px;} + .wym_skin_twopanels .wym_buttons li.wym_tools_unordered_list a{ background-position: 0 -72px;} + .wym_skin_twopanels .wym_buttons li.wym_tools_indent a { background-position: 0 -574px;} + .wym_skin_twopanels .wym_buttons li.wym_tools_outdent a { background-position: 0 -598px;} + .wym_skin_twopanels .wym_buttons li.wym_tools_undo a { background-position: 0 -502px;} + .wym_skin_twopanels .wym_buttons li.wym_tools_redo a { background-position: 0 -526px;} + .wym_skin_twopanels .wym_buttons li.wym_tools_link a { background-position: 0 -96px;} + .wym_skin_twopanels .wym_buttons li.wym_tools_unlink a { background-position: 0 -168px;} + .wym_skin_twopanels .wym_buttons li.wym_tools_image a { background-position: 0 -121px;} + .wym_skin_twopanels .wym_buttons li.wym_tools_table a { background-position: 0 -144px;} + .wym_skin_twopanels .wym_buttons li.wym_tools_paste a { background-position: 0 -552px;} + .wym_skin_twopanels .wym_buttons li.wym_tools_html a { background-position: 0 -193px;} + .wym_skin_twopanels .wym_buttons li.wym_tools_preview a { background-position: 0 -408px;} + +/*DECORATION*/ + .wym_skin_twopanels .wym_section h2 { background: #ddd; border: solid gray; border-width: 0 0 1px;} + .wym_skin_twopanels .wym_section h2 span { color: gray;} + .wym_skin_twopanels .wym_panel { padding: 0; border: solid gray; border-width: 1px; background: white;} + .wym_skin_twopanels .wym_panel ul { margin: 2px 0 5px; } + .wym_skin_twopanels .wym_dropdown { padding: 0; border: solid gray; border-width: 1px 1px 0 1px; } + .wym_skin_twopanels .wym_dropdown ul { border: solid gray; border-width: 0 1px 1px 1px; margin-left: -1px; padding: 5px 10px 5px 3px;} + +/*DIALOGS*/ + .wym_dialog div.row { margin-bottom: 5px;} + .wym_dialog div.row input { margin-right: 5px;} + .wym_dialog div.row label { float: left; width: 150px; display: block; text-align: right; margin-right: 10px; } + .wym_dialog div.row-indent { padding-left: 160px; } + /*autoclearing*/ + .wym_dialog div.row:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } + .wym_dialog div.row { display: inline-block; } + /* Hides from IE-mac \*/ + * html .wym_dialog div.row { height: 1%; } + .wym_dialog div.row { display: block; } + /* End hide from IE-mac */ + +/*WYMEDITOR_LINK*/ + a.wym_wymeditor_link { text-indent: -9999px; float: right; display: block; width: 50px; height: 15px; background: url(../wymeditor_icon.png); overflow: hidden; text-decoration: none; } diff --git a/public/wymeditor/wymeditor/skins/twopanels/skin.js b/public/wymeditor/wymeditor/skins/twopanels/skin.js new file mode 100644 index 0000000..79ccdf9 --- /dev/null +++ b/public/wymeditor/wymeditor/skins/twopanels/skin.js @@ -0,0 +1,39 @@ +WYMeditor.SKINS.twopanels = { + + init: function(wym) { + + //move the containers panel to the left area + jQuery(wym._box).find(wym._options.containersSelector) + .appendTo("div.wym_area_left"); + + //render following sections as panels + jQuery(wym._box).find(wym._options.classesSelector + ', ' + + wym._options.containersSelector) + .addClass("wym_panel"); + + //render following sections as buttons + jQuery(wym._box).find(wym._options.toolsSelector) + .addClass("wym_buttons"); + + // auto add some margin to the main area sides if left area + // or right area are not empty (if they contain sections) + jQuery(wym._box).find("div.wym_area_right ul") + .parents("div.wym_area_right").show() + .parents(wym._options.boxSelector) + .find("div.wym_area_main") + .css({"margin-right": "155px"}); + + jQuery(wym._box).find("div.wym_area_left ul") + .parents("div.wym_area_left").show() + .parents(wym._options.boxSelector) + .find("div.wym_area_main") + .css({"margin-left": "115px"}); + + //make hover work under IE < 7 + jQuery(wym._box).find(".wym_section").hover(function(){ + jQuery(this).addClass("hover"); + },function(){ + jQuery(this).removeClass("hover"); + }); + } +}; diff --git a/public/wymeditor/wymeditor/skins/wymeditor_icon.png b/public/wymeditor/wymeditor/skins/wymeditor_icon.png new file mode 100644 index 0000000..d4fc155 Binary files /dev/null and b/public/wymeditor/wymeditor/skins/wymeditor_icon.png differ diff --git a/recompile_assets.sh b/recompile_assets.sh index 5681754..9419435 100755 --- a/recompile_assets.sh +++ b/recompile_assets.sh @@ -1,4 +1,4 @@ -sudo rake assets:clean RAILS_ENV=production -sudo rake assets:precompile RAILS_ENV=production -sudo touch tmp/restart.txt +rake assets:clean RAILS_ENV=production +rake assets:precompile RAILS_ENV=production +touch tmp/restart.txt diff --git a/test/fixtures/ipns.yml b/test/fixtures/ipns.yml new file mode 100644 index 0000000..6d2f6e4 --- /dev/null +++ b/test/fixtures/ipns.yml @@ -0,0 +1,7 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/Fixtures.html + +one: + data: MyText + +two: + data: MyText diff --git a/test/fixtures/paypal_csvs.yml b/test/fixtures/paypal_csvs.yml new file mode 100644 index 0000000..c103612 --- /dev/null +++ b/test/fixtures/paypal_csvs.yml @@ -0,0 +1,41 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/Fixtures.html + +one: + date: MyString + _time: MyString + _time_zone: MyString + _name: MyString + _type: MyString + _status: MyString + _currency: MyString + _gross: MyString + _fee: MyString + _net: MyString + _from_email_address: MyString + _to_email_address: MyString + _transaction_id: MyString + _counterparty_status: MyString + _address_status: MyString + _item_title: MyString + _item_id: MyString + string: MyString + +two: + date: MyString + _time: MyString + _time_zone: MyString + _name: MyString + _type: MyString + _status: MyString + _currency: MyString + _gross: MyString + _fee: MyString + _net: MyString + _from_email_address: MyString + _to_email_address: MyString + _transaction_id: MyString + _counterparty_status: MyString + _address_status: MyString + _item_title: MyString + _item_id: MyString + string: MyString diff --git a/test/functional/ipn_controller_test.rb b/test/functional/ipn_controller_test.rb new file mode 100644 index 0000000..0d5ac04 --- /dev/null +++ b/test/functional/ipn_controller_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class IpnControllerTest < ActionController::TestCase + # test "the truth" do + # assert true + # end +end diff --git a/test/unit/helpers/ipn_helper_test.rb b/test/unit/helpers/ipn_helper_test.rb new file mode 100644 index 0000000..be0cbae --- /dev/null +++ b/test/unit/helpers/ipn_helper_test.rb @@ -0,0 +1,4 @@ +require 'test_helper' + +class IpnHelperTest < ActionView::TestCase +end diff --git a/test/unit/ipn_test.rb b/test/unit/ipn_test.rb new file mode 100644 index 0000000..fbdc7fa --- /dev/null +++ b/test/unit/ipn_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class IpnTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end diff --git a/test/unit/paypal_csv_test.rb b/test/unit/paypal_csv_test.rb new file mode 100644 index 0000000..c610e02 --- /dev/null +++ b/test/unit/paypal_csv_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class PaypalCsvTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end