├── lib ├── tasks │ ├── .gitkeep │ └── cucumber.rake ├── setup.rb ├── popup.rb └── tryruby.rb ├── public ├── favicon.ico ├── stylesheets │ ├── .gitkeep │ ├── reset.css │ ├── scaffold.css │ ├── facebox.css │ └── site.css ├── images │ ├── tile.png │ ├── footer.png │ ├── header.png │ ├── rails.png │ ├── background.png │ └── index.html ├── javascripts │ ├── application.js │ ├── index.html │ ├── json2.js │ ├── irb.js │ ├── jquery.console.min.js │ ├── mouseirb_2.js │ ├── rails.js │ ├── console.js │ ├── facebox.js │ ├── jquery.console.js │ ├── mouseapp_2.js │ └── dragdrop.js ├── robots.txt ├── 422.html ├── 404.html └── 500.html ├── db ├── development.sqlite3 └── seeds.rb ├── vendor └── plugins │ └── .gitkeep ├── tmp └── pids │ └── server.pid ├── app ├── views │ ├── index │ │ ├── index.html.erb │ │ └── terminal.html.erb │ ├── layouts │ │ ├── blank.html.erb │ │ ├── tryruby.rhtml │ │ ├── tryruby_2.html.erb │ │ └── application.html.erb │ ├── tryruby │ │ ├── index.html.erb │ │ ├── run.html.erb │ │ └── _donate.html.erb │ └── irb │ │ ├── new.html.erb │ │ ├── edit.html.erb │ │ ├── show.html.erb │ │ ├── _form.html.erb │ │ └── index.html.erb ├── helpers │ ├── index_helper.rb │ ├── irb_helper.rb │ ├── classic_helper.rb │ ├── public_helper.rb │ ├── tutorials_helper.rb │ └── application_helper.rb └── controllers │ ├── classic_controller.rb │ ├── public_controller.rb │ ├── tutorials_controller.rb │ ├── index_controller.rb │ ├── application_controller.rb │ ├── tryruby_controller.rb │ └── irb_controller.rb ├── .gitignore ├── test ├── unit │ └── helpers │ │ ├── index_helper_test.rb │ │ └── tutorials_helper_test.rb ├── functional │ ├── index_controller_test.rb │ └── tutorials_controller_test.rb ├── performance │ └── browsing_test.rb └── test_helper.rb ├── config.ru ├── config ├── environment.rb ├── boot.rb ├── initializers │ ├── mime_types.rb │ ├── inflections.rb │ ├── backtrace_silencers.rb │ ├── session_store.rb │ └── secret_token.rb ├── locales │ └── en.yml ├── database.yml ├── environments │ ├── development.rb │ ├── test.rb │ └── production.rb ├── application.rb └── routes.rb ├── doc └── README_FOR_APP ├── Rakefile ├── script └── rails ├── README.md └── Gemfile /lib/tasks/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /db/development.sqlite3: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vendor/plugins/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/stylesheets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tmp/pids/server.pid: -------------------------------------------------------------------------------- 1 | 47518 -------------------------------------------------------------------------------- /app/views/index/index.html.erb: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/views/index/terminal.html.erb: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/views/layouts/blank.html.erb: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/views/tryruby/index.html.erb: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/views/tryruby/run.html.erb: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/helpers/index_helper.rb: -------------------------------------------------------------------------------- 1 | module IndexHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/irb_helper.rb: -------------------------------------------------------------------------------- 1 | module IrbHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/classic_helper.rb: -------------------------------------------------------------------------------- 1 | module ClassicHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/public_helper.rb: -------------------------------------------------------------------------------- 1 | module PublicHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/tutorials_helper.rb: -------------------------------------------------------------------------------- 1 | module TutorialsHelper 2 | end 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS* 2 | ._* 3 | .bundle 4 | *.lock 5 | tryruby/log/* 6 | tmp/* 7 | tmp/* 8 | -------------------------------------------------------------------------------- /app/controllers/classic_controller.rb: -------------------------------------------------------------------------------- 1 | class ClassicController < ApplicationController 2 | end 3 | -------------------------------------------------------------------------------- /app/controllers/public_controller.rb: -------------------------------------------------------------------------------- 1 | class PublicController < ApplicationController 2 | end 3 | -------------------------------------------------------------------------------- /public/images/tile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sophrinix/TryRuby/HEAD/public/images/tile.png -------------------------------------------------------------------------------- /public/images/footer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sophrinix/TryRuby/HEAD/public/images/footer.png -------------------------------------------------------------------------------- /public/images/header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sophrinix/TryRuby/HEAD/public/images/header.png -------------------------------------------------------------------------------- /public/images/rails.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sophrinix/TryRuby/HEAD/public/images/rails.png -------------------------------------------------------------------------------- /public/images/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sophrinix/TryRuby/HEAD/public/images/background.png -------------------------------------------------------------------------------- /app/views/irb/new.html.erb: -------------------------------------------------------------------------------- 1 |

New irb

2 | 3 | <%= render 'form' %> 4 | 5 | <%= link_to 'Back', irb_path %> 6 | -------------------------------------------------------------------------------- /test/unit/helpers/index_helper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class IndexHelperTest < ActionView::TestCase 4 | end 5 | -------------------------------------------------------------------------------- /test/unit/helpers/tutorials_helper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class TutorialsHelperTest < ActionView::TestCase 4 | end 5 | -------------------------------------------------------------------------------- /app/controllers/tutorials_controller.rb: -------------------------------------------------------------------------------- 1 | class TutorialsController < ApplicationController 2 | 3 | def intro 4 | 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /app/views/irb/edit.html.erb: -------------------------------------------------------------------------------- 1 |

Editing irb

2 | 3 | <%= render 'form' %> 4 | 5 | <%= link_to 'Show', @irb %> | 6 | <%= link_to 'Back', irb_path %> 7 | -------------------------------------------------------------------------------- /app/controllers/index_controller.rb: -------------------------------------------------------------------------------- 1 | class IndexController < ApplicationController 2 | def terminal 3 | end 4 | 5 | def index 6 | 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /app/views/irb/show.html.erb: -------------------------------------------------------------------------------- 1 |

<%= notice %>

2 | 3 | 4 | <%= link_to 'Edit', edit_irb_path(@irb) %> | 5 | <%= link_to 'Back', irb_path %> 6 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path('../config/environment', __FILE__) 4 | run Tryruby::Application 5 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the rails application 2 | require File.expand_path('../application', __FILE__) 3 | 4 | # Initialize the rails application 5 | Tryruby::Application.initialize! 6 | -------------------------------------------------------------------------------- /public/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // Place your application-specific JavaScript functions and classes here 2 | // This file is automatically included by javascript_include_tag :defaults 3 | -------------------------------------------------------------------------------- /public/images/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

you shouldn't be here.. kicking you out!

7 | 8 | 9 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | 3 | # Set up gems listed in the Gemfile. 4 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 5 | 6 | require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE']) 7 | -------------------------------------------------------------------------------- /public/javascripts/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

you shouldn't be here.. kicking you out!

7 | 8 | 9 | -------------------------------------------------------------------------------- /doc/README_FOR_APP: -------------------------------------------------------------------------------- 1 | Use this README file to introduce your application and point to useful places in the API for learning more. 2 | Run "rake doc:app" to generate API documentation for your models, controllers, helpers, and libraries. 3 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-Agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | # Mime::Type.register_alias "text/html", :iphone 6 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Sample localization file for English. Add more files in this directory for other locales. 2 | # See http://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. 3 | 4 | en: 5 | hello: "Hello world" 6 | -------------------------------------------------------------------------------- /test/functional/index_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class IndexControllerTest < ActionController::TestCase 4 | test "should get terminal" do 5 | get :terminal 6 | assert_response :success 7 | end 8 | 9 | end 10 | -------------------------------------------------------------------------------- /test/functional/tutorials_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class TutorialsControllerTest < ActionController::TestCase 4 | test "should get intro" do 5 | get :intro 6 | assert_response :success 7 | end 8 | 9 | end 10 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require File.expand_path('../config/application', __FILE__) 5 | require 'rake' 6 | 7 | Tryruby::Application.load_tasks 8 | -------------------------------------------------------------------------------- /test/performance/browsing_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'rails/performance_test_help' 3 | 4 | # Profiling results for each test method are written to tmp/performance. 5 | class BrowsingTest < ActionDispatch::PerformanceTest 6 | def test_homepage 7 | get '/' 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /script/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. 3 | 4 | APP_PATH = File.expand_path('../../config/application', __FILE__) 5 | require File.expand_path('../../config/boot', __FILE__) 6 | require 'rails/commands' 7 | -------------------------------------------------------------------------------- /db/seeds.rb: -------------------------------------------------------------------------------- 1 | # This file should contain all the record creation needed to seed the database with its default values. 2 | # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). 3 | # 4 | # Examples: 5 | # 6 | # cities = City.create([{ :name => 'Chicago' }, { :name => 'Copenhagen' }]) 7 | # Mayor.create(:name => 'Daley', :city => cities.first) 8 | -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format 4 | # (all these examples are active by default): 5 | # ActiveSupport::Inflector.inflections do |inflect| 6 | # inflect.plural /^(ox)$/i, '\1en' 7 | # inflect.singular /^(ox)en/i, '\1' 8 | # inflect.irregular 'person', 'people' 9 | # inflect.uncountable %w( fish sheep ) 10 | # end 11 | -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Tryruby::Application.config.session_store :cookie_store, :key => '_tryruby_session' 4 | 5 | # Use the database for sessions instead of the cookie-based default, 6 | # which shouldn't be used to store highly confidential information 7 | # (create the session table with "rails generate session_migration") 8 | # Tryruby::Application.config.session_store :active_record_store 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## TryRuby! version 2 (Obsolete) 2 | This is the Source code to the TryRuby! version 2. 3 | If you want to see the original running TryRuby! version 2 code, then checkout the legacy branch. 4 | ## This codebase is obsolete. Thankfully a new version lives on in TryRuby! version 4 which is based on opalrb. 5 | 6 | Version 4 of TryRuby! lives at 7 | https://github.com/easydatawarehousing/tryruby 8 | 9 | The link to the live site is https://ruby.github.io/TryRuby/ 10 | -------------------------------------------------------------------------------- /app/views/irb/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_for(@irb) do |f| %> 2 | <% if @irb.errors.any? %> 3 |
4 |

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

5 | 6 | 11 |
12 | <% end %> 13 | 14 |
15 | <%= f.submit %> 16 |
17 | <% end %> 18 | -------------------------------------------------------------------------------- /app/views/irb/index.html.erb: -------------------------------------------------------------------------------- 1 |

Listing irb

2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | <% @irb.each do |irb| %> 11 | 12 | 13 | 14 | 15 | 16 | <% end %> 17 |
<%= link_to 'Show', irb %><%= link_to 'Edit', edit_irb_path(irb) %><%= link_to 'Destroy', irb, :confirm => 'Are you sure?', :method => :delete %>
18 | 19 |
20 | 21 | <%= link_to 'New Irb', new_irb_path %> 22 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | ENV["RAILS_ENV"] = "test" 2 | require File.expand_path('../../config/environment', __FILE__) 3 | require 'rails/test_help' 4 | 5 | class ActiveSupport::TestCase 6 | # Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order. 7 | # 8 | # Note: You'll currently still have to declare fixtures explicitly in integration tests 9 | # -- they do not yet inherit this setting 10 | fixtures :all 11 | 12 | # Add more helper methods to be used by all tests here... 13 | end 14 | -------------------------------------------------------------------------------- /config/initializers/secret_token.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | # Make sure the secret is at least 30 characters and all random, 6 | # no regular words or you'll be exposed to dictionary attacks. 7 | Tryruby::Application.config.secret_token = '0b9697ad5c8979de943825eeb25418d1f8cf963ad4e3a6758acc2558ae77b0c097997142fea5c9cb5969f2126ee433aefccbae922c04943ca4074d2c31e9c608' 8 | -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | 3 | def google_analytics_js 4 | ua_code = "UA-2365371-3" 5 | '' 15 | 16 | end 17 | 18 | end 19 | -------------------------------------------------------------------------------- /config/database.yml: -------------------------------------------------------------------------------- 1 | 2 | development: 3 | adapter: mysql2 4 | database: tryruby_development 5 | username: root 6 | password: 7 | pool: 5 8 | timeout: 5000 9 | host: 127.0.0.1 10 | 11 | # Warning: The database defined as "test" will be erased and 12 | # re-generated from your development database when you run "rake". 13 | # Do not set this db to the same as development or production. 14 | test: 15 | adapter: mysql2 16 | database: tryruby_test 17 | username: root 18 | password: 19 | pool: 5 20 | timeout: 5000 21 | host: 127.0.0.1 22 | 23 | production: 24 | adapter: mysql2 25 | database: tryruby_production 26 | username: root 27 | password: 28 | pool: 5 29 | timeout: 5000 30 | host: localhost 31 | cucumber: 32 | <<: *test 33 | -------------------------------------------------------------------------------- /public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

The change you wanted was rejected.

23 |

Maybe you tried to change something you didn't have access to.

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

The page you were looking for doesn't exist.

23 |

You may have mistyped the address or the page may have moved.

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

We're sorry, but something went wrong.

23 |

We've been notified about this issue and we'll take a look at it shortly.

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | # Filters added to this controller apply to all controllers in the application. 2 | # Likewise, all the methods added will be available for all controllers. 3 | #require File.dirname(__FILE__) + '/../../lib/tryruby' 4 | 5 | class ApplicationController < ActionController::Base 6 | 7 | 8 | 9 | 10 | layout 'tryruby' 11 | 12 | # attr_accessor :past_commands, :current_statement, :start_time 13 | 14 | 15 | # attr_accessor :past_commands, :current_statement, :start_time 16 | 17 | 18 | 19 | 20 | 21 | =begin 22 | #attr_accessor :session 23 | TryRuby.session = session 24 | 25 | TryRuby.session['start_time'] ||= Time.now 26 | TryRuby.session['current_statement'] ||= '' 27 | TryRuby.session['past_commands'] ||= '' 28 | 29 | helper :all # include all helpers, all the time 30 | protect_from_forgery # See ActionController::RequestForgeryProtection for details 31 | 32 | # Scrub sensitive parameters from your log 33 | # filter_parameter_logging :password 34 | #class << self 35 | =end 36 | # not needed 37 | #end 38 | end 39 | -------------------------------------------------------------------------------- /app/controllers/tryruby_controller.rb: -------------------------------------------------------------------------------- 1 | 2 | class TryrubyController < ApplicationController 3 | 4 | layout 'tryruby' 5 | def index 6 | end 7 | 8 | def run 9 | eval(params[:cmd]) 10 | 11 | #@cmd=params[:cmd] 12 | # @a= run_script(@cmd) 13 | # @b = "handleJSON({\"type\": #{@a.type.to_json}, \"output\":#{@a.output.to_json},\"result\":#{@a.result.inspect.to_json}, \"error\": #{@a.error.inspect.to_json}})" 14 | 15 | begin 16 | render :json => @b 17 | rescue 18 | end 19 | end 20 | 21 | 22 | def run_script(command) 23 | #output = begin 24 | eval(command) 25 | # rescue StandardError => e 26 | # e.message + ". On the " 27 | begin 28 | # Tryrubyengine.session ||= TRSession.new 29 | #TryRuby.run_line(TryRuby.session.cgi['cmd']).format 30 | #Tryrubyengine.new 31 | # @c= Tryrubyengine.session 32 | # @c.inspect 33 | # Tryrubyengine.run_line(command) 34 | rescue 35 | 36 | end 37 | # end 38 | 39 | # return "=> #{output}" + ", says yoda" 40 | end 41 | 42 | end 43 | -------------------------------------------------------------------------------- /config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Tryruby::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the webserver when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Log error messages when you accidentally call methods on nil. 10 | config.whiny_nils = true 11 | 12 | # Show full error reports and disable caching 13 | config.consider_all_requests_local = true 14 | config.action_view.debug_rjs = true 15 | config.action_controller.perform_caching = false 16 | 17 | # Don't care if the mailer can't send 18 | config.action_mailer.raise_delivery_errors = false 19 | 20 | # Print deprecation notices to the Rails logger 21 | config.active_support.deprecation = :log 22 | 23 | # Only use best-standards-support built into browsers 24 | config.action_dispatch.best_standards_support = :builtin 25 | end 26 | 27 | -------------------------------------------------------------------------------- /public/stylesheets/reset.css: -------------------------------------------------------------------------------- 1 | html, body, div, span, applet, object, iframe, 2 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 3 | a, abbr, acronym, address, big, cite, code, 4 | del, dfn, em, font, img, ins, kbd, q, s, samp, 5 | small, strike, strong, sub, sup, tt, var, 6 | dl, dt, dd, ol, ul, li, 7 | fieldset, form, label, legend, 8 | table, caption, tbody, tfoot, thead, tr, th, td { 9 | margin: 0; 10 | padding: 0; 11 | border: 0; 12 | outline: 0; 13 | font-weight: inherit; 14 | font-style: inherit; 15 | font-size: 100%; 16 | font-family: inherit; 17 | vertical-align: baseline; 18 | } 19 | /* remember to define focus styles! */ 20 | :focus { 21 | outline: 0; 22 | } 23 | body { 24 | line-height: 1; 25 | color: black; 26 | background: white; 27 | } 28 | ol, ul { 29 | list-style: none; 30 | } 31 | /* tables still need 'cellspacing="0"' in the markup */ 32 | table { 33 | border-collapse: separate; 34 | border-spacing: 0; 35 | } 36 | caption, th, td { 37 | text-align: left; 38 | font-weight: normal; 39 | } 40 | blockquote:before, blockquote:after, 41 | q:before, q:after { 42 | content: ""; 43 | } 44 | blockquote, q { 45 | quotes: "" ""; 46 | } 47 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'http://rubygems.org' 2 | 3 | gem 'rails', '3.0.9' 4 | 5 | gem 'mysql2', '< 0.3' 6 | gem 'fakefs', '0.2.1', :git => "http://github.com/defunkt/fakefs.git", :ref => "aa0cb96b8ebc81287a2e", :require => 'fakefs/safe' 7 | # Use unicorn as the web server 8 | # gem 'unicorn' 9 | gem 'i18n' 10 | # Deploy with Capistrano 11 | # gem 'capistrano' 12 | 13 | gem 'devise' 14 | 15 | # To use debugger (ruby-debug for Ruby 1.8.7+, ruby-debug19 for Ruby 1.9.2+) 16 | # gem 'ruby-debug' 17 | # gem 'ruby-debug19', :require => 'ruby-debug' 18 | gem 'jquery-rails' 19 | gem 'ruby_parser' 20 | gem 'racc' 21 | 22 | #gem 'ruby2ruby' 23 | #gem 'newrelic_rpm' 24 | #gem 'madmimi' 25 | #gem 'delayed_job' 26 | #gem 'dalli' 27 | # Bundle the extra gems: 28 | # gem 'bj' 29 | 30 | gem 'nokogiri' 31 | # gem 'sqlite3-ruby', :require => 'sqlite3' 32 | # gem 'aws-s3', :require => 'aws/s3' 33 | 34 | # Bundle gems for the local environment. Make sure to 35 | # put test-only gems in this group so their generators 36 | # and rake tasks are available in development mode: 37 | # group :development, :test do 38 | gem 'capybara' 39 | gem 'rspec-rails' 40 | gem 'cucumber-rails' 41 | #gem 'factory-girl' 42 | -------------------------------------------------------------------------------- /public/stylesheets/scaffold.css: -------------------------------------------------------------------------------- 1 | body { background-color: #fff; color: #333; } 2 | 3 | body, p, ol, ul, td { 4 | font-family: verdana, arial, helvetica, sans-serif; 5 | font-size: 13px; 6 | line-height: 18px; 7 | } 8 | 9 | pre { 10 | background-color: #eee; 11 | padding: 10px; 12 | font-size: 11px; 13 | } 14 | 15 | a { color: #000; } 16 | a:visited { color: #666; } 17 | a:hover { color: #fff; background-color:#000; } 18 | 19 | div.field, div.actions { 20 | margin-bottom: 10px; 21 | } 22 | 23 | #notice { 24 | color: green; 25 | } 26 | 27 | .field_with_errors { 28 | padding: 2px; 29 | background-color: red; 30 | display: table; 31 | } 32 | 33 | #error_explanation { 34 | width: 450px; 35 | border: 2px solid red; 36 | padding: 7px; 37 | padding-bottom: 0; 38 | margin-bottom: 20px; 39 | background-color: #f0f0f0; 40 | } 41 | 42 | #error_explanation h2 { 43 | text-align: left; 44 | font-weight: bold; 45 | padding: 5px 5px 5px 15px; 46 | font-size: 12px; 47 | margin: -7px; 48 | margin-bottom: 0px; 49 | background-color: #c00; 50 | color: #fff; 51 | } 52 | 53 | #error_explanation ul li { 54 | font-size: 12px; 55 | list-style: square; 56 | } 57 | -------------------------------------------------------------------------------- /public/stylesheets/facebox.css: -------------------------------------------------------------------------------- 1 | #facebox { 2 | position: absolute; 3 | top: 0; 4 | left: 0; 5 | z-index: 100; 6 | text-align: left; 7 | } 8 | 9 | 10 | #facebox .popup{ 11 | position:relative; 12 | border:3px solid rgba(0,0,0,0); 13 | -webkit-border-radius:5px; 14 | -moz-border-radius:5px; 15 | border-radius:5px; 16 | -webkit-box-shadow:0 0 18px rgba(0,0,0,0.4); 17 | -moz-box-shadow:0 0 18px rgba(0,0,0,0.4); 18 | box-shadow:0 0 18px rgba(0,0,0,0.4); 19 | } 20 | 21 | #facebox .content { 22 | display:table; 23 | width: 660px; 24 | padding: 30px; 25 | background: #fff; 26 | -webkit-border-radius:4px; 27 | -moz-border-radius:4px; 28 | border-radius:4px; 29 | } 30 | 31 | #facebox .content > p:first-child{ 32 | margin-top:0; 33 | } 34 | #facebox .content > p:last-child{ 35 | margin-bottom:0; 36 | } 37 | 38 | #facebox .close{ 39 | position:absolute; 40 | top:5px; 41 | right:5px; 42 | padding:2px; 43 | background:#fff; 44 | } 45 | #facebox .close img{ 46 | opacity:0.3; 47 | } 48 | #facebox .close:hover img{ 49 | opacity:1.0; 50 | } 51 | 52 | #facebox .loading { 53 | text-align: center; 54 | } 55 | 56 | #facebox .image { 57 | text-align: center; 58 | } 59 | 60 | #facebox img { 61 | border: 0; 62 | margin: 0; 63 | } 64 | 65 | #facebox_overlay { 66 | position: fixed; 67 | top: 0px; 68 | left: 0px; 69 | height:100%; 70 | width:100%; 71 | } 72 | 73 | .facebox_hide { 74 | z-index:-100; 75 | } 76 | 77 | .facebox_overlayBG { 78 | background-color: #000; 79 | z-index: 99; 80 | } 81 | .facebox-footnote{ 82 | margin-top:40px; 83 | } -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | Tryruby::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Log error messages when you accidentally call methods on nil. 11 | config.whiny_nils = true 12 | 13 | # Show full error reports and disable caching 14 | config.consider_all_requests_local = true 15 | config.action_controller.perform_caching = false 16 | 17 | # Raise exceptions instead of rendering exception templates 18 | config.action_dispatch.show_exceptions = false 19 | 20 | # Disable request forgery protection in test environment 21 | config.action_controller.allow_forgery_protection = false 22 | 23 | # Tell Action Mailer not to deliver emails to the real world. 24 | # The :test delivery method accumulates sent emails in the 25 | # ActionMailer::Base.deliveries array. 26 | config.action_mailer.delivery_method = :test 27 | 28 | # Use SQL instead of Active Record's schema dumper when creating the test database. 29 | # This is necessary if your schema can't be completely dumped by the schema dumper, 30 | # like if you have constraints or database-specific column types 31 | # config.active_record.schema_format = :sql 32 | 33 | # Print deprecation notices to the stderr 34 | config.active_support.deprecation = :stderr 35 | end 36 | -------------------------------------------------------------------------------- /config/environments/production.rb: -------------------------------------------------------------------------------- 1 | Tryruby::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb 3 | 4 | # The production environment is meant for finished, "live" apps. 5 | # Code is not reloaded between requests 6 | config.cache_classes = true 7 | 8 | # Full error reports are disabled and caching is turned on 9 | config.consider_all_requests_local = false 10 | config.action_controller.perform_caching = true 11 | 12 | # Specifies the header that your server uses for sending files 13 | config.action_dispatch.x_sendfile_header = "X-Sendfile" 14 | 15 | # For nginx: 16 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' 17 | 18 | # If you have no front-end server that supports something like X-Sendfile, 19 | # just comment this out and Rails will serve the files 20 | 21 | # See everything in the log (default is :info) 22 | # config.log_level = :debug 23 | 24 | # Use a different logger for distributed setups 25 | # config.logger = SyslogLogger.new 26 | 27 | # Use a different cache store in production 28 | # config.cache_store = :mem_cache_store 29 | 30 | # Disable Rails's static asset server 31 | # In production, Apache or nginx will already do this 32 | config.serve_static_assets = false 33 | 34 | # Enable serving of images, stylesheets, and javascripts from an asset server 35 | # config.action_controller.asset_host = "http://assets.example.com" 36 | 37 | # Disable delivery errors, bad email addresses will be ignored 38 | # config.action_mailer.raise_delivery_errors = false 39 | 40 | # Enable threaded mode 41 | # config.threadsafe! 42 | 43 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 44 | # the I18n.default_locale when a translation can not be found) 45 | config.i18n.fallbacks = true 46 | 47 | # Send deprecation notices to registered listeners 48 | config.active_support.deprecation = :notify 49 | end 50 | -------------------------------------------------------------------------------- /config/application.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../boot', __FILE__) 2 | 3 | require 'rails/all' 4 | 5 | # If you have a Gemfile, require the gems listed there, including any gems 6 | # you've limited to :test, :development, or :production. 7 | Bundler.require(:default, Rails.env) if defined?(Bundler) 8 | 9 | module Tryruby 10 | class Application < Rails::Application 11 | # Settings in config/environments/* take precedence over those specified here. 12 | # Application configuration should go into files in config/initializers 13 | # -- all .rb files in that directory are automatically loaded. 14 | 15 | # Custom directories with classes and modules you want to be autoloadable. 16 | # config.autoload_paths += %W(#{config.root}/extras) 17 | 18 | # Only load the plugins named here, in the order given (default is alphabetical). 19 | # :all can be used as a placeholder for all plugins not explicitly named. 20 | # config.plugins = [ :exception_notification, :ssl_requirement, :all ] 21 | 22 | # Activate observers that should always be running. 23 | # config.active_record.observers = :cacher, :garbage_collector, :forum_observer 24 | 25 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 26 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. 27 | # config.time_zone = 'Central Time (US & Canada)' 28 | 29 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. 30 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] 31 | # config.i18n.default_locale = :de 32 | 33 | # JavaScript files you want as :defaults (application.js is always included). 34 | # config.action_view.javascript_expansions[:defaults] = %w(jquery rails) 35 | 36 | # Configure the default encoding used in templates for Ruby 1.9. 37 | config.encoding = "utf-8" 38 | 39 | # Configure sensitive parameters which will be filtered from the log file. 40 | config.filter_parameters += [:password] 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/tasks/cucumber.rake: -------------------------------------------------------------------------------- 1 | # IMPORTANT: This file is generated by cucumber-rails - edit at your own peril. 2 | # It is recommended to regenerate this file in the future when you upgrade to a 3 | # newer version of cucumber-rails. Consider adding your own code to a new file 4 | # instead of editing this one. Cucumber will automatically load all features/**/*.rb 5 | # files. 6 | 7 | 8 | unless ARGV.any? {|a| a =~ /^gems/} # Don't load anything when running the gems:* tasks 9 | 10 | vendored_cucumber_bin = Dir["#{Rails.root}/vendor/{gems,plugins}/cucumber*/bin/cucumber"].first 11 | $LOAD_PATH.unshift(File.dirname(vendored_cucumber_bin) + '/../lib') unless vendored_cucumber_bin.nil? 12 | 13 | begin 14 | require 'cucumber/rake/task' 15 | 16 | namespace :cucumber do 17 | Cucumber::Rake::Task.new({:ok => 'db:test:prepare'}, 'Run features that should pass') do |t| 18 | t.binary = vendored_cucumber_bin # If nil, the gem's binary is used. 19 | t.fork = true # You may get faster startup if you set this to false 20 | t.profile = 'default' 21 | end 22 | 23 | Cucumber::Rake::Task.new({:wip => 'db:test:prepare'}, 'Run features that are being worked on') do |t| 24 | t.binary = vendored_cucumber_bin 25 | t.fork = true # You may get faster startup if you set this to false 26 | t.profile = 'wip' 27 | end 28 | 29 | Cucumber::Rake::Task.new({:rerun => 'db:test:prepare'}, 'Record failing features and run only them if any exist') do |t| 30 | t.binary = vendored_cucumber_bin 31 | t.fork = true # You may get faster startup if you set this to false 32 | t.profile = 'rerun' 33 | end 34 | 35 | desc 'Run all features' 36 | task :all => [:ok, :wip] 37 | end 38 | desc 'Alias for cucumber:ok' 39 | task :cucumber => 'cucumber:ok' 40 | 41 | task :default => :cucumber 42 | 43 | task :features => :cucumber do 44 | STDERR.puts "*** The 'features' task is deprecated. See rake -T cucumber ***" 45 | end 46 | rescue LoadError 47 | desc 'cucumber rake task not available (cucumber not installed)' 48 | task :cucumber do 49 | abort 'Cucumber rake task is not available. Be sure to install cucumber as a gem or plugin' 50 | end 51 | end 52 | 53 | end 54 | -------------------------------------------------------------------------------- /app/controllers/irb_controller.rb: -------------------------------------------------------------------------------- 1 | class IrbController < ApplicationController 2 | # GET /irb 3 | # GET /irb.xml 4 | def index 5 | 6 | @irb = [] 7 | 8 | respond_to do |format| 9 | format.html # index.html.erb 10 | format.to_json 11 | format.xml { render :xml => @irb } 12 | end 13 | end 14 | 15 | # GET /irb/1 16 | # GET /irb/1.xml 17 | def show 18 | @irb = Irb.find(params[:id]) 19 | 20 | respond_to do |format| 21 | format.html # show.html.erb 22 | format.xml { render :xml => @irb } 23 | end 24 | end 25 | 26 | # GET /irb/new 27 | # GET /irb/new.xml 28 | def new 29 | @irb = Irb.new 30 | 31 | respond_to do |format| 32 | format.html # new.html.erb 33 | format.xml { render :xml => @irb } 34 | end 35 | end 36 | 37 | # GET /irb/1/edit 38 | def edit 39 | @irb = Irb.find(params[:id]) 40 | end 41 | 42 | # POST /irb 43 | # POST /irb.xml 44 | def create 45 | @irb = Irb.new(params[:irb]) 46 | 47 | respond_to do |format| 48 | if @irb.save 49 | format.html { redirect_to(@irb, :notice => 'Irb was successfully created.') } 50 | format.xml { render :xml => @irb, :status => :created, :location => @irb } 51 | else 52 | format.html { render :action => "new" } 53 | format.xml { render :xml => @irb.errors, :status => :unprocessable_entity } 54 | end 55 | end 56 | end 57 | 58 | # PUT /irb/1 59 | # PUT /irb/1.xml 60 | def update 61 | @irb = Irb.find(params[:id]) 62 | 63 | respond_to do |format| 64 | if @irb.update_attributes(params[:irb]) 65 | format.html { redirect_to(@irb, :notice => 'Irb was successfully updated.') } 66 | format.xml { head :ok } 67 | else 68 | format.html { render :action => "edit" } 69 | format.xml { render :xml => @irb.errors, :status => :unprocessable_entity } 70 | end 71 | end 72 | end 73 | 74 | # DELETE /irb/1 75 | # DELETE /irb/1.xml 76 | def destroy 77 | @irb = Irb.find(params[:id]) 78 | @irb.destroy 79 | 80 | respond_to do |format| 81 | format.html { redirect_to(irb_url) } 82 | format.xml { head :ok } 83 | end 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | Tryruby::Application.routes.draw do 2 | 3 | resources :irb 4 | 5 | # The priority is based upon order of creation: 6 | # first created -> highest priority. 7 | root :to => "tryruby#index" 8 | match '/tryruby/run' => 'tryruby#run' 9 | # connect ':controller/:action/:id' 10 | # connect ':controller/:action/:id.:format' 11 | 12 | # Sample of regular route: 13 | # match 'products/:id' => 'catalog#view' 14 | # Keep in mind you can assign values other than :controller and :action 15 | 16 | # Sample of named route: 17 | # match 'products/:id/purchase' => 'catalog#purchase', :as => :purchase 18 | # This route can be invoked with purchase_url(:id => product.id) 19 | 20 | # Sample resource route (maps HTTP verbs to controller actions automatically): 21 | # resources :products 22 | 23 | # Sample resource route with options: 24 | # resources :products do 25 | # member do 26 | # get 'short' 27 | # post 'toggle' 28 | # end 29 | # 30 | # collection do 31 | # get 'sold' 32 | # end 33 | # end 34 | 35 | # Sample resource route with sub-resources: 36 | # resources :products do 37 | # resources :comments, :sales 38 | # resource :seller 39 | # end 40 | 41 | # Sample resource route with more complex sub-resources 42 | # resources :products do 43 | # resources :comments 44 | # resources :sales do 45 | # get 'recent', :on => :collection 46 | # end 47 | # end 48 | 49 | # Sample resource route within a namespace: 50 | # namespace :admin do 51 | # # Directs /admin/products/* to Admin::ProductsController 52 | # # (app/controllers/admin/products_controller.rb) 53 | # resources :products 54 | # end 55 | 56 | # You can have the root of your site routed with "root" 57 | # just remember to delete public/index.html. 58 | # root :to => "welcome#index" 59 | 60 | # See how all your routes lay out with "rake routes" 61 | 62 | # This is a legacy wild controller route that's not recommended for RESTful applications. 63 | # Note: This route will make all actions in every controller accessible via GET requests. 64 | # match ':controller(/:action(/:id(.:format)))' 65 | end 66 | 67 | 68 | -------------------------------------------------------------------------------- /lib/setup.rb: -------------------------------------------------------------------------------- 1 | module Tryrubyengine 2 | SetupCode = <#{self.text}" 20 | end 21 | 22 | end 23 | 24 | class Link 25 | attr_accessor :text, :target 26 | def initialize(text, target) 27 | self.text, self.target = text, target 28 | end 29 | def generate_html 30 | "#{text}" 31 | end 32 | 33 | 34 | end 35 | 36 | class List 37 | attr_accessor :elements 38 | def initialize(elements) 39 | self.elements = elements 40 | end 41 | 42 | def generate_html 43 | items = elements.map do |elem| 44 | text = elem.instance_of?(Paragraph) ? elem.text : elem.generate_html 45 | "
  • #{text}
  • " 46 | end.join(" ") 47 | 48 | "" 49 | end 50 | end 51 | 52 | 53 | class Paragraph 54 | attr_accessor :text 55 | def initialize(text) 56 | self.text = text 57 | end 58 | 59 | def generate_html 60 | "

    #{self.text}

    " 61 | end 62 | end 63 | 64 | 65 | class ComplexPopup 66 | attr_reader :elements 67 | def initialize 68 | @elements = [] 69 | end 70 | 71 | (1..6).each do |n| 72 | define_method "h#{n}".to_sym do |text| 73 | @elements << Header.new(n, text) 74 | end 75 | end 76 | 77 | 78 | # def h1 text 79 | # @elements << Header.new(1, text) 80 | # end 81 | 82 | def link(text, target) 83 | @elements << Link.new(text, target) 84 | end 85 | 86 | def p(text) 87 | @elements << Paragraph.new(text) 88 | end 89 | 90 | def list(&block) 91 | lst = ComplexPopup.new 92 | lst.instance_eval(&block) 93 | @elements << List.new(lst.elements) 94 | end 95 | 96 | def generate_html() 97 | @elements.map(&:generate_html).join(" ") 98 | end 99 | 100 | 101 | 102 | end 103 | 104 | 105 | def self.make(&block) 106 | result = ComplexPopup.new 107 | result.instance_eval(&block) 108 | 109 | html = result.generate_html.gsub('\\', '\\\\').gsub('"', '\"') 110 | command = "window.irb.options.popup_make(\"#{html}\")" 111 | Tryrubyengine::Output.javascript command 112 | end 113 | 114 | end 115 | end -------------------------------------------------------------------------------- /app/views/tryruby/_donate.html.erb: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/javascripts/json2.js: -------------------------------------------------------------------------------- 1 | if(!this.JSON){this.JSON={}}(function(){function f(n){return n<10?'0'+n:n}if(typeof Date.prototype.toJSON!=='function'){Date.prototype.toJSON=function(key){return isFinite(this.valueOf())?this.getUTCFullYear()+'-'+f(this.getUTCMonth()+1)+'-'+f(this.getUTCDate())+'T'+f(this.getUTCHours())+':'+f(this.getUTCMinutes())+':'+f(this.getUTCSeconds())+'Z':null};String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(key){return this.valueOf()}}var cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,gap,indent,meta={'\b':'\\b','\t':'\\t','\n':'\\n','\f':'\\f','\r':'\\r','"':'\\"','\\':'\\\\'},rep;function quote(string){escapable.lastIndex=0;return escapable.test(string)?'"'+string.replace(escapable,function(a){var c=meta[a];return typeof c==='string'?c:'\\u'+('0000'+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+string+'"'}function str(key,holder){var i,k,v,length,mind=gap,partial,value=holder[key];if(value&&typeof value==='object'&&typeof value.toJSON==='function'){value=value.toJSON(key)}if(typeof rep==='function'){value=rep.call(holder,key,value)}switch(typeof value){case'string':return quote(value);case'number':return isFinite(value)?String(value):'null';case'boolean':case'null':return String(value);case'object':if(!value){return'null'}gap+=indent;partial=[];if(Object.prototype.toString.apply(value)==='[object Array]'){length=value.length;for(i=0;i>\033[m', 47 | user: 'guest', 48 | host: 'tryruby', 49 | // original: irbUrl: '/irb', 50 | irbUrl: '/tryruby/run', 51 | init: function () { 52 | helpPages = $(".stretcher"); 53 | chapPages = new Array(); 54 | for (var i = 0; i < helpPages.length; i++ ) { 55 | var cls = helpPages[i].className.split(' '); 56 | for (var j = 0; j < cls.length; j++) { 57 | if (cls[j] == 'chapmark') { 58 | chapPages.push([i, helpPages[i]]); 59 | break; 60 | } 61 | } 62 | } 63 | }, 64 | loadTutorial: function (id, instruct) { 65 | $.ajax({ 66 | url: '/tutorials/' + id , 67 | type: 'GET', 68 | complete: function (r) { 69 | $('#helpstone').html("
    " + defaultPage + "
    " + r.responseText); 70 | window.irb.init(); 71 | window.irb.showHelp(0); 72 | } 73 | }); 74 | }, 75 | showChapter: function (n) { 76 | if (n >= chapPages.length) return; 77 | this.setHelpPage(chapPages[n][0], chapPages[n][1]); 78 | }, 79 | showHelp: function (n) { 80 | if (n >= helpPages.length) return; 81 | this.setHelpPage(n, helpPages[n]); 82 | }, 83 | popup_goto: function (u) { 84 | $('#lilBrowser').show().css({left: '40px', top: '40px'}); 85 | $('#lbIframe').attr('src', u); 86 | }, 87 | popup_make: function (s) { 88 | $('#lilBrowser').show().css({left: '40px', top: '40px'}); 89 | $('#lbIframe').get(0).onIframeLoad = function () { 90 | alert($(this).html()); 91 | alert("$(this).html()"); 92 | return s; 93 | }; 94 | //$('#lbIframe').attr({src: '/blank.html'}); 95 | src = s.replace(/\\/g, "\\\\").replace(/\"/g, "\\\""); 96 | $('#lbIframe').attr({src: "javascript:\"" + src + "\""}); 97 | // $('# 98 | }, 99 | popup_close: function () { 100 | $('#lilBrowser').hide(); 101 | } 102 | }); 103 | 104 | if ( !toot ) { 105 | toot = 'intro'; 106 | } 107 | try { 108 | window.irb.options.loadTutorial( toot, true ); 109 | } catch (e) {} 110 | } 111 | -------------------------------------------------------------------------------- /app/views/layouts/tryruby.rhtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | try ruby! (en tu navegador) 6 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 | 17 |
    18 | 19 |
    20 |
    21 |
    22 | 23 |

    A Popup Browser

    24 |

    [x]

    25 |
    26 | 27 |
    28 |
    29 | 30 |
    31 |
    32 |
    33 |
    34 |
    35 |
    36 |

    ¿Tienes 15 minutos? ¡Prueba Ruby ahora mismo!

    37 |

    Ruby es un lenguaje de programación de Japón 38 | (disponible en ruby-lang.org) 39 | que está revolucionando la web. 40 | La belleza de Ruby se encuentra en su balance entre la simplicidad y el poder.

    41 | 42 |

    Prueba código Ruby en el prompt de arriba. Además de los métodos 43 | originales de Ruby, los siguientes comandos están disponibles:

    44 |
      45 |
    • help 46 | Empieza el tutorial interactivo de 15 minutos. ¡Creeme, es muy básico!
    • 47 |
    • help 2 48 | Salta al capítulo 2.
    • 49 | 50 |
    • clear 51 | Limpia la pantalla. Útil si tu navegador empieza a alerdarce. 52 | Tu historial de comandos será recordado. 53 |
    • back 54 | Retrocede una pantalla en el tutorial.
    • 55 |
    • reset 56 | Resetea el interprete. (o Ctrl-D!)
    • 57 |
    • next 58 | Te permite saltear la siguiente lección
    • 59 | 60 |
    • time 61 | Detiene el reloj. Imprime cuanto tiempo tu sesión estuvo abierta.
    • 62 |
    63 |

    Si te pasa de dejar o refrescar la página, tu sesión seguirá aquí a menos que 64 | se deje inactiva por diez minutos.

    65 |
    66 |
    67 |
    68 | 69 |
    70 |
    ¿Atrapado en los dos puntos? Unas comillas o algo fue dejado abierto. Escribe: reset o aprieta Ctrl-D.
    71 |
    72 | 73 |

    This place was sired by why the lucky stiff. 74 | Please contact me using the email address at that link.is maintained by Andrew McElroy and David Miani. For support issues, please post a ticket or contact Sophrinix on github.
    Por asuntos de traducción, mandar un ticket o contactarse con Cristian Re (leizzer) en Github.
    75 |

    76 | 77 |
    78 | 79 | 80 | 83 | 84 | 88 | 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /public/javascripts/jquery.console.min.js: -------------------------------------------------------------------------------- 1 | (function($){$.fn.console=function(config){var keyCodes={left:37,right:39,up:38,down:40,back:8,del:46,end:35,start:36,ret:13};var cursor=' ';var wbr=$.browser.opera?'​':'';var container=$(this);var inner=$('
    ');var typer=$('');var promptBox;var prompt;var promptLabel=config&&config.promptLabel?config.promptLabel:"> ";var column=0;var promptText='';var restoreText='';var history=[];var ringn=0;var cancelKeyPress=0;var extern={};(function(){container.append(inner);inner.append(typer);typer.css({position:'absolute',top:0,left:'-999px'});if(config.welcomeMessage)message(config.welcomeMessage,'jquery-console-welcome');newPromptBox();if(config.autofocus){inner.addClass('jquery-console-focus');typer.focus();setTimeout(function(){inner.addClass('jquery-console-focus');typer.focus()},100)}})();extern.reset=function(){var welcome=true;inner.parent().fadeOut(function(){inner.find('div').each(function(){if(!welcome)$(this).remove();welcome=false});newPromptBox();inner.parent().fadeIn(function(){inner.addClass('jquery-console-focus');typer.focus()})})};function newPromptBox(){column=0;promptText='';promptBox=$('
    ');var label=$('');promptBox.append(label.text(promptLabel).show());prompt=$('');promptBox.append(prompt);inner.append(promptBox);updatePromptDisplay()};container.click(function(){inner.addClass('jquery-console-focus');inner.removeClass('jquery-console-nofocus');typer.focus();scrollToBottom();return false});typer.blur(function(){inner.removeClass('jquery-console-focus');inner.addClass('jquery-console-nofocus')});typer.keydown(function(e){cancelKeyPress=0;var keyCode=e.keyCode;if(isControlCharacter(keyCode)){cancelKeyPress=keyCode;if(!typer.consoleControl(keyCode)){return false}}});typer.keypress(function(e){var keyCode=e.keyCode||e.which;if(cancelKeyPress!=keyCode&&keyCode>=32){if(cancelKeyPress)return false;typer.consoleInsert(keyCode)}if($.browser.webkit)return false});function isControlCharacter(keyCode){return((keyCode>=keyCodes.left&&keyCode<=keyCodes.down)||keyCode==keyCodes.back||keyCode==keyCodes.del||keyCode==keyCodes.end||keyCode==keyCodes.start||keyCode==keyCodes.ret)};typer.consoleControl=function(keyCode){switch(keyCode){case keyCodes.left:{moveColumn(-1);updatePromptDisplay();return false;break}case keyCodes.right:{moveColumn(1);updatePromptDisplay();return false;break}case keyCodes.back:{if(moveColumn(-1)){deleteCharAtPos();updatePromptDisplay()}return false;break}case keyCodes.del:{if(deleteCharAtPos())updatePromptDisplay();return false;break}case keyCodes.end:{if(moveColumn(promptText.length-column))updatePromptDisplay();return false;break}case keyCodes.start:{if(moveColumn(-column))updatePromptDisplay();return false;break}case keyCodes.ret:{commandTrigger();return false}case keyCodes.up:{rotateHistory(-1);return false}case keyCodes.down:{rotateHistory(1);return false}default:}};function rotateHistory(n){if(history.length==0)return;ringn+=n;if(ringn<0)ringn=history.length;else if(ringn>history.length)ringn=0;var prevText=promptText;if(ringn==0){promptText=restoreText}else{promptText=history[ringn-1]}if(config.historyPreserveColumn){if(promptText.length');if(className)mesg.addClass(className);mesg.filledText(msg).hide();inner.append(mesg);mesg.show()};typer.consoleInsert=function(keyCode){var char=String.fromCharCode(keyCode);var before=promptText.substring(0,column);var after=promptText.substring(column);promptText=before+char+after;moveColumn(1);restoreText=promptText;updatePromptDisplay()};function moveColumn(n){if(column+n>=0&&column+n<=promptText.length){column+=n;return true}else return false};function updatePromptDisplay(){var line=promptText;var html='';if(column>0&&line==''){html=cursor}else if(column==promptText.length){html=htmlEncode(line)+cursor}else{var before=line.substring(0,column);var current=line.substring(column,column+1);if(current){current=''+htmlEncode(current)+''}var after=line.substring(column+1);html=htmlEncode(before)+current+htmlEncode(after)}prompt.html(html);scrollToBottom()};function htmlEncode(text){return(text.replace(/&/g,'&').replace(/&]{10})/g,'$1­'+wbr))};return extern};$.fn.filledText=function(txt){$(this).text(txt);$(this).html($(this).html().replace(/\n/g,'
    '));return this}})(jQuery); -------------------------------------------------------------------------------- /lib/tryruby.rb: -------------------------------------------------------------------------------- 1 | #require 'ruby_parser' 2 | require 'ruby_parser' 3 | 4 | require 'stringio' 5 | require 'popup.rb' 6 | require 'setup.rb' 7 | require 'fakefs/safe' 8 | require 'cgi' 9 | require 'cgi/session' 10 | require 'cgi/session/pstore' 11 | 12 | 13 | 14 | 15 | 16 | 17 | module Tryrubyengine 18 | extend self 19 | class TRSession 20 | #include ActionDispatch::Session 21 | # < TryRuby::Session 22 | attr_accessor :cgi, :session 23 | 24 | def initialize 25 | 26 | @session = CGI::Session.new @cgi = CGI.new, 27 | 'database_manager' => CGI::Session::PStore, # use PStore 28 | 'session_key' => 'trb_sess_id', # custom $session key 29 | 'session_expires' => Time.now + 60 * 60, # 60 minute timeout 30 | 'prefix' => 'pstore_sid_', #Pstore option 31 | 'tmpdir' => 'tmp' # Temp Directory for sessions 32 | 33 | @session['start_time'] ||= Time.now 34 | #ActionController::Base.session 35 | @session['current_statement'] ||= '' 36 | @session['past_commands'] ||= '' 37 | end 38 | 39 | def header 40 | @cgi.header 'text/plain' 41 | end 42 | 43 | [:current_statement, :past_commands, :start_time].each do |accessor| 44 | define_method(accessor) { @session[accessor.to_s] } 45 | define_method(:"#{accessor.to_s}=") { |new_val| @session[accessor.to_s] = new_val } 46 | end 47 | end 48 | 49 | 50 | 51 | class Output 52 | attr_reader :type, :result, :output, :error, :indent_level, :javascript 53 | 54 | def self.standard(params = {}) 55 | Output.new type: :standard, result: params[:result], 56 | output: params[:output] || '' 57 | end 58 | 59 | def self.illegal 60 | Output.new type: :illegal 61 | end 62 | 63 | def self.javascript(js) 64 | Output.new type: :javascript, javascript: js 65 | end 66 | 67 | def self.no_output 68 | Output.standard result: nil 69 | end 70 | 71 | def self.line_continuation(level) 72 | Output.new type: :line_continuation, indent_level: level 73 | end 74 | 75 | def self.error(params = {}) 76 | params[:error] ||= StandardError.new('TryRuby Error') 77 | params[:error].message.gsub! /\(eval\):\d*/, '(TryRuby):1' 78 | Output.new type: :error, error: params[:error], 79 | output: params[:output] || '' 80 | end 81 | 82 | def format 83 | case @type 84 | when :line_continuation 85 | ".." * @indent_level 86 | when :error 87 | @output + "\033[1;33m#{@error.class}: #{@error.message}" 88 | when :illegal 89 | "\033[1;33mYou aren't allowed to run that command!" 90 | when :javascript 91 | "\033[1;JSm#{@javascript}\033[m " 92 | else 93 | @output + "=> \033[1;20m#{@result.inspect}" 94 | end 95 | end 96 | 97 | protected 98 | def initialize(values = {}) 99 | values.each do |variable, value| 100 | instance_variable_set("@#{variable}", value) 101 | end 102 | end 103 | end 104 | 105 | 106 | class << self 107 | attr_accessor :session 108 | Tryrubyengine.session = Tryrubyengine::TRSession.new 109 | end 110 | 111 | def calculate_nesting_level(statement) 112 | begin 113 | RubyParser.new.parse(statement) 114 | 0 115 | rescue Racc::ParseError => e 116 | case e.message 117 | when /parse error on value \"\$end\" \(\$end\)/ then 118 | new_statement = statement + "\n end" 119 | begin 120 | RubyParser.new.parse(new_statement) 121 | return 1 122 | rescue Racc::ParseError => e 123 | if e.message =~ /parse error on value \"end\" \(kEND\)/ then 124 | new_statement = statement + "\n }" 125 | end 126 | end 127 | begin 128 | 1 + calculate_nesting_level(new_statement) 129 | rescue Racc::ParseError => e 130 | return 1 131 | end 132 | else 133 | raise e 134 | end 135 | end 136 | end 137 | 138 | def run_line(code,session) 139 | case code.strip 140 | when '!INIT!IRB!' 141 | return Output.no_output 142 | when 'reset' 143 | session.current_statement = '' 144 | return Output.no_output 145 | when 'time' 146 | seconds = (Time.now - session.start_time).ceil 147 | return Output.standard result: 148 | if seconds < 60; "#{seconds} seconds" 149 | elsif seconds < 120; "1 minute" 150 | else; "#{seconds / 60} minutes" 151 | end 152 | end 153 | 154 | # nesting level 155 | level = begin 156 | calculate_nesting_level(session.current_statement + "\n" + code) 157 | rescue Racc::ParseError, SyntaxError 158 | 0 159 | end 160 | if level > 0 161 | session.current_statement += "\n" + code 162 | return Output.line_continuation(level) 163 | end 164 | 165 | # run something 166 | FakeFS.activate! 167 | stdout_id = $stdout.to_i 168 | $stdout = StringIO.new 169 | cmd = <<-EOF 170 | #{SetupCode} 171 | $SAFE = 3 172 | #{session.past_commands} 173 | $stdout = StringIO.new 174 | begin 175 | #{session.current_statement} 176 | #{code} 177 | end 178 | EOF 179 | begin 180 | result = Thread.new { eval cmd, TOPLEVEL_BINDING }.value 181 | rescue SecurityError 182 | return Output.illegal 183 | rescue Exception => e 184 | return Output.error :error => e, :output => get_stdout 185 | ensure 186 | output = get_stdout 187 | $stdout = IO.new(stdout_id) 188 | FakeFS.deactivate! 189 | end 190 | 191 | session.current_statement += "\n" + code 192 | session.past_commands += "\n" + session.current_statement.strip 193 | session.current_statement = '' 194 | 195 | return result if result.is_a? Output and result.type == :javascript 196 | Output.standard result: result, output: output 197 | end 198 | 199 | private 200 | def get_stdout 201 | raise TypeError, "$stdout is a #{$stdout.class}" unless $stdout.is_a? StringIO 202 | $stdout.rewind 203 | $stdout.read 204 | end 205 | 206 | end 207 | -------------------------------------------------------------------------------- /app/views/layouts/tryruby_2.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Try Haskell! An interactive tutorial in your browser 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
    22 |
    23 |
    24 |
    25 |
    26 |

    Try Haskell! An interactive tutorial in your 27 | browser

    28 | 33 |
    34 | 35 |
    36 |
    37 |
    38 | 39 |

    A Popup Browser

    40 |

    [x]

    41 |
    42 | <%= yield %> 43 | 44 | 45 |
    46 |
    47 | 48 |
    49 |
    50 |
    51 |
    52 |
    53 |
    54 | 55 |
    56 |
    57 |
    58 |
    59 |
    60 |
    61 |
    62 |
    63 |
    64 |
    65 |
    66 |
    67 |
    68 |
    69 |
    70 |
    71 |

    72 | Welcome to your first taste of Haskell! Let's try Haskell right now! 73 |

    74 |

    Beginners

    75 |
    76 |

    Type help to start the 77 | tutorial. Type lessons to see the 78 | list of lessons.

    79 |

    Or try typing these out and see what happens 80 | (click to insert):

    81 |
      82 |
    • 83 | 23*36 84 |
    • 85 |
    • 86 | reverse "hello" 87 |
    • 88 |
    89 |
    90 |
    91 |

    Learn More

    92 |
    93 |

    94 | Real World Haskell! 97 | Get stuck into a book with 98 | Real 99 | World Haskell 100 | (readable online!), published by O'Reilly Media. 101 | Checkout 102 | Haskell.org 104 | for more information about Haskell.

    105 |
    106 |
    107 |
    108 |
    109 |
    110 |
    111 |
    112 |
    113 |
    114 | 144 |
    145 |
    146 | 147 | 148 | -------------------------------------------------------------------------------- /public/javascripts/mouseirb_2.js: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2008 why the lucky stiff 3 | // 4 | // Permission is hereby granted, free of charge, to any person 5 | // obtaining a copy of this software and associated documentation 6 | // files (the "Software"), to deal in the Software without restriction, 7 | // including without limitation the rights to use, copy, modify, merge, 8 | // publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, 10 | // subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be 13 | // included in all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | // SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT 21 | // OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | // SOFTWARE. 24 | // 25 | 26 | /* Irb running moush */ 27 | MouseApp.Irb = function(element, options) { 28 | this.element = $(element); 29 | this.setOptions(options); 30 | this.showHelp = this.options.showHelp; 31 | if ( this.options.showChapter ) { 32 | this.showChapter = this.options.showChapter; 33 | } 34 | if ( this.options.init ) { 35 | this.init = this.options.init; 36 | } 37 | this.initWindow(); 38 | this.setup(); 39 | this.helpPage = null; 40 | this.irbInit = false; 41 | }; 42 | 43 | $.extend(MouseApp.Irb.prototype, MouseApp.Terminal.prototype, { 44 | cmdToQuery: function(cmd) { 45 | return "cmd=" + escape(cmd.replace(/</g, '<').replace(/>/g, '>'). 46 | replace(/&/g, '&').replace(/\r?\n/g, "\n")).replace(/\+/g, "%2B"); 47 | }, 48 | 49 | fireOffCmd: function(cmd, func) { 50 | var irb = this; 51 | if (!this.irbInit) 52 | { 53 | $.ajax({url: this.options.irbUrl + "?" + this.cmdToQuery("!INIT!IRB!"), type: "GET", 54 | complete: (function(r) { irb.irbInit = true; irb.fireOffCmd(cmd, func); })}); 55 | } 56 | else 57 | { 58 | $.ajax({url: this.options.irbUrl + "?" + this.cmdToQuery(cmd), type: "GET", 59 | complete: func}); 60 | } 61 | }, 62 | 63 | reply: function(str) { 64 | var raw = str.replace(/\033\[(\d);(\d+)m/g, ''); 65 | this.checkAnswer(raw); 66 | if (!str.match(/^(\.\.)+$/)) { 67 | if ( str[str.length - 1] != "\n" ) { 68 | str += "\n"; 69 | } 70 | js_payload = /\033\[1;JSm(.*)\033\[m/; 71 | js_in = str.match(js_payload); 72 | if (js_in) { 73 | try { 74 | js_in = eval(js_in[1]); 75 | } catch (e) {} 76 | str = str.replace(js_payload, ''); 77 | } 78 | var pr_re = new RegExp("(^|\\n)=>"); 79 | if ( str.match( pr_re ) ) { 80 | str = str.replace(new RegExp("(^|\\n)=>"), "$1\033[1;34m=>\033[m"); 81 | } else { 82 | str = str.replace(new RegExp("(^|\\n)= (.+?) ="), "$1\033[1;33m$2\033[m"); 83 | } 84 | this.write(str); 85 | this.prompt(); 86 | } else { 87 | this.prompt("\033[1;32m" + ".." + "\033[m", true); 88 | this.puts(str.replace(/\./g, ' '), 0); 89 | } 90 | }, 91 | 92 | setHelpPage: function(n, page) { 93 | if (this.helpPage) 94 | $(this.helpPage.ele).hide('fast'); 95 | this.helpPage = {index: n, ele: page}; 96 | match = this.scanHelpPageFor('load'); 97 | if (match != -1) 98 | { 99 | this.fireOffCmd(match, (function(r) { 100 | $(page).show('fast'); 101 | })); 102 | } 103 | else 104 | { 105 | $(page).show('fast'); 106 | } 107 | }, 108 | 109 | scanHelpPageFor: function(eleClass) { 110 | match = $("div." + eleClass, this.helpPage.ele); 111 | if ( match[0] ) return match[0].innerHTML; 112 | else return -1; 113 | }, 114 | 115 | checkAnswer: function(str) { 116 | if ( this.helpPage ) { 117 | match = this.scanHelpPageFor('answer'); 118 | if ( match != -1 ) { 119 | if ( str.match( new RegExp('^\s*=> ' + match + '\s*$', 'm') ) ) { 120 | this.showHelp(this.helpPage.index + 1); 121 | } 122 | } else { 123 | match = this.scanHelpPageFor('stdout'); 124 | if ( match != -1 ) { 125 | if ( match == '' ) { 126 | if ( str == '' || str == null ) this.showHelp(this.helpPage.index + 1); 127 | } else if ( str.match( new RegExp('^\s*' + match + '$', 'm') ) ) { 128 | this.showHelp(this.helpPage.index + 1); 129 | } 130 | } 131 | } 132 | } 133 | }, 134 | 135 | onKeyCtrld: function() { 136 | this.clearCommand(); 137 | this.puts("reset"); 138 | this.onKeyEnter(); 139 | }, 140 | 141 | onKeyEnter: function() { 142 | this.typingOff(); 143 | var cmd = this.getCommand(); 144 | if (cmd) { 145 | this.history[this.historyNum] = cmd; 146 | this.backupNum = ++this.historyNum; 147 | } 148 | this.commandNum++; 149 | this.advanceLine(); 150 | if (cmd) { 151 | if ( cmd == "clear" ) { 152 | this.clear(); 153 | this.prompt(); 154 | } else if ( cmd.match(/^(back)$/) ) { 155 | if (this.helpPage && this.helpPage.index >= 1) { 156 | this.showHelp(this.helpPage.index - 1); 157 | } 158 | this.prompt(); 159 | } else if ( cmd.match(/^(next)$/) ) { 160 | if (this.helpPage) { 161 | this.showHelp(this.helpPage.index + 1); 162 | } 163 | this.prompt(); 164 | } else if ( cmd.match(/^(help|wtf\?*)$/) ) { 165 | this.showHelp(1); 166 | this.prompt(); 167 | } else if ( regs = cmd.match(/^(help|wtf\?*)\s+#?(\d+)\s*$/) ) { 168 | this.showChapter(parseInt(regs[2])); 169 | this.prompt(); 170 | } else { 171 | var term = this; 172 | this.fireOffCmd(cmd, (function(r) { term.reply(r.responseText ? r.responseText : ''); })); 173 | } 174 | } else { 175 | this.prompt(); 176 | } 177 | } 178 | }); 179 | 180 | -------------------------------------------------------------------------------- /public/javascripts/rails.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | // Technique from Juriy Zaytsev 3 | // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/ 4 | function isEventSupported(eventName) { 5 | var el = document.createElement('div'); 6 | eventName = 'on' + eventName; 7 | var isSupported = (eventName in el); 8 | if (!isSupported) { 9 | el.setAttribute(eventName, 'return;'); 10 | isSupported = typeof el[eventName] == 'function'; 11 | } 12 | el = null; 13 | return isSupported; 14 | } 15 | 16 | function isForm(element) { 17 | return Object.isElement(element) && element.nodeName.toUpperCase() == 'FORM' 18 | } 19 | 20 | function isInput(element) { 21 | if (Object.isElement(element)) { 22 | var name = element.nodeName.toUpperCase() 23 | return name == 'INPUT' || name == 'SELECT' || name == 'TEXTAREA' 24 | } 25 | else return false 26 | } 27 | 28 | var submitBubbles = isEventSupported('submit'), 29 | changeBubbles = isEventSupported('change') 30 | 31 | if (!submitBubbles || !changeBubbles) { 32 | // augment the Event.Handler class to observe custom events when needed 33 | Event.Handler.prototype.initialize = Event.Handler.prototype.initialize.wrap( 34 | function(init, element, eventName, selector, callback) { 35 | init(element, eventName, selector, callback) 36 | // is the handler being attached to an element that doesn't support this event? 37 | if ( (!submitBubbles && this.eventName == 'submit' && !isForm(this.element)) || 38 | (!changeBubbles && this.eventName == 'change' && !isInput(this.element)) ) { 39 | // "submit" => "emulated:submit" 40 | this.eventName = 'emulated:' + this.eventName 41 | } 42 | } 43 | ) 44 | } 45 | 46 | if (!submitBubbles) { 47 | // discover forms on the page by observing focus events which always bubble 48 | document.on('focusin', 'form', function(focusEvent, form) { 49 | // special handler for the real "submit" event (one-time operation) 50 | if (!form.retrieve('emulated:submit')) { 51 | form.on('submit', function(submitEvent) { 52 | var emulated = form.fire('emulated:submit', submitEvent, true) 53 | // if custom event received preventDefault, cancel the real one too 54 | if (emulated.returnValue === false) submitEvent.preventDefault() 55 | }) 56 | form.store('emulated:submit', true) 57 | } 58 | }) 59 | } 60 | 61 | if (!changeBubbles) { 62 | // discover form inputs on the page 63 | document.on('focusin', 'input, select, texarea', function(focusEvent, input) { 64 | // special handler for real "change" events 65 | if (!input.retrieve('emulated:change')) { 66 | input.on('change', function(changeEvent) { 67 | input.fire('emulated:change', changeEvent, true) 68 | }) 69 | input.store('emulated:change', true) 70 | } 71 | }) 72 | } 73 | 74 | function handleRemote(element) { 75 | var method, url, params; 76 | 77 | var event = element.fire("ajax:before"); 78 | if (event.stopped) return false; 79 | 80 | if (element.tagName.toLowerCase() === 'form') { 81 | method = element.readAttribute('method') || 'post'; 82 | url = element.readAttribute('action'); 83 | params = element.serialize(); 84 | } else { 85 | method = element.readAttribute('data-method') || 'get'; 86 | url = element.readAttribute('href'); 87 | params = {}; 88 | } 89 | 90 | new Ajax.Request(url, { 91 | method: method, 92 | parameters: params, 93 | evalScripts: true, 94 | 95 | onComplete: function(request) { element.fire("ajax:complete", request); }, 96 | onSuccess: function(request) { element.fire("ajax:success", request); }, 97 | onFailure: function(request) { element.fire("ajax:failure", request); } 98 | }); 99 | 100 | element.fire("ajax:after"); 101 | } 102 | 103 | function handleMethod(element) { 104 | var method = element.readAttribute('data-method'), 105 | url = element.readAttribute('href'), 106 | csrf_param = $$('meta[name=csrf-param]')[0], 107 | csrf_token = $$('meta[name=csrf-token]')[0]; 108 | 109 | var form = new Element('form', { method: "POST", action: url, style: "display: none;" }); 110 | element.parentNode.insert(form); 111 | 112 | if (method !== 'post') { 113 | var field = new Element('input', { type: 'hidden', name: '_method', value: method }); 114 | form.insert(field); 115 | } 116 | 117 | if (csrf_param) { 118 | var param = csrf_param.readAttribute('content'), 119 | token = csrf_token.readAttribute('content'), 120 | field = new Element('input', { type: 'hidden', name: param, value: token }); 121 | form.insert(field); 122 | } 123 | 124 | form.submit(); 125 | } 126 | 127 | 128 | document.on("click", "*[data-confirm]", function(event, element) { 129 | var message = element.readAttribute('data-confirm'); 130 | if (!confirm(message)) event.stop(); 131 | }); 132 | 133 | document.on("click", "a[data-remote]", function(event, element) { 134 | if (event.stopped) return; 135 | handleRemote(element); 136 | event.stop(); 137 | }); 138 | 139 | document.on("click", "a[data-method]", function(event, element) { 140 | if (event.stopped) return; 141 | handleMethod(element); 142 | event.stop(); 143 | }); 144 | 145 | document.on("submit", function(event) { 146 | var element = event.findElement(), 147 | message = element.readAttribute('data-confirm'); 148 | if (message && !confirm(message)) { 149 | event.stop(); 150 | return false; 151 | } 152 | 153 | var inputs = element.select("input[type=submit][data-disable-with]"); 154 | inputs.each(function(input) { 155 | input.disabled = true; 156 | input.writeAttribute('data-original-value', input.value); 157 | input.value = input.readAttribute('data-disable-with'); 158 | }); 159 | 160 | var element = event.findElement("form[data-remote]"); 161 | if (element) { 162 | handleRemote(element); 163 | event.stop(); 164 | } 165 | }); 166 | 167 | document.on("ajax:after", "form", function(event, element) { 168 | var inputs = element.select("input[type=submit][disabled=true][data-disable-with]"); 169 | inputs.each(function(input) { 170 | input.value = input.readAttribute('data-original-value'); 171 | input.removeAttribute('data-original-value'); 172 | input.disabled = false; 173 | }); 174 | }); 175 | 176 | Ajax.Responders.register({ 177 | onCreate: function(request) { 178 | var csrf_meta_tag = $$('meta[name=csrf-token]')[0]; 179 | 180 | if (csrf_meta_tag) { 181 | var header = 'X-CSRF-Token', 182 | token = csrf_meta_tag.readAttribute('content'); 183 | 184 | if (!request.options.requestHeaders) { 185 | request.options.requestHeaders = {}; 186 | } 187 | request.options.requestHeaders[header] = token; 188 | } 189 | } 190 | }); 191 | })(); 192 | -------------------------------------------------------------------------------- /public/javascripts/console.js: -------------------------------------------------------------------------------- 1 | jQuery.fn.debug = function() { 2 | var msg = jqArray.args(arguments); 3 | $("
    ").addClass("error").text(msg.join(", ")).prependTo(this); 4 | } 5 | 6 | jQConsole = function(input, output) { 7 | 8 | var args = jqArray.args 9 | 10 | var input = input; 11 | 12 | // History 13 | var command_history = []; 14 | var command_selected = 0; 15 | 16 | var local_scope = safe_scope(); 17 | 18 | function hide_fn(fn) { return function() { return fn.apply(this, arguments); } } 19 | hide_fn.desc = "A function that creates a wrapper that hides the implementation of another function"; 20 | function queue_fn(fn, time) { if(!time) time = 0; return function() { setTimeout(fn, time); } } 21 | queue_fn.desc = "Turns a function into a function that's called later"; 22 | 23 | var keys = function (o) { 24 | var r = []; 25 | if (typeof o != "object") return r; 26 | for (var k in o) r.push(k); 27 | return r; 28 | } 29 | var refocus = queue_fn(function() { input.blur().focus(); }); 30 | var reset_input = queue_fn(function() { input.val("").blur().focus(); }); 31 | function no_recurse(fn, max_depth) { 32 | var count = 0; 33 | if(!max_depth) max_depth = 1; 34 | return function() { 35 | count++; 36 | if(count > max_depth) { 37 | count--; return; 38 | } else { 39 | fn.apply(this, arguments); 40 | } 41 | } 42 | } 43 | 44 | function hook_fn(fn, listener) { 45 | fn.listener = function() { return listener; } 46 | fn.apply = function(thisArg, argArray) { 47 | if(fn == caller) return; 48 | listener(); 49 | return fn.apply(thisArg, argArray); 50 | }; 51 | } 52 | 53 | hook_fn(history, function() { print("Yo"); }); 54 | 55 | $(document).ready(page_onload); 56 | 57 | function page_onload() { 58 | input = $(input); 59 | output = $(output); 60 | input.keypress(map_keyboard()); 61 | $(document).click(refocus); 62 | refocus(); 63 | } 64 | page_onload = hide_fn(page_onload); 65 | 66 | function clear() { output.html(""); } 67 | 68 | function history() { 69 | return command_history.join("\n"); 70 | } 71 | 72 | var keyLogging = false; 73 | 74 | function map_keyboard() { 75 | var cmdKeys = keymap(); 76 | with(cmdKeys) { 77 | mapKeyCode(toggleKeyLogging, 120); 78 | map(executeCommand, {keyCode:13, ctrlKey:true}); 79 | //mapKeyCode(executeCommand, 13); 80 | mapKeyCode(refocus, 9); 81 | map(historyLast, {keyCode:38, ctrlKey:true}); 82 | map(historyNext, {keyCode:40, ctrlKey:true}); 83 | map(function() { return false; }, {keyCode:123}); 84 | } 85 | return function(e) { 86 | if(keyLogging) 87 | log("keyCode: " + e.keyCode, " shiftKey: " + e.shiftKey, " ctrlKey: " + e.ctrlKey); 88 | resize_input(); 89 | return cmdKeys.dispatch(e); 90 | } 91 | } 92 | 93 | function toggleKeyLogging() { keyLogging = !keyLogging; } 94 | 95 | function historyLast() { 96 | command_selected = Math.max(0, command_selected - 1); 97 | edit_command(command_history[command_selected]); 98 | } 99 | 100 | function historyNext() { 101 | command_selected = Math.min(command_history.length, command_selected + 1); 102 | var cmd = (command_selected == command_history.length) ? "" : command_history[command_selected]; 103 | edit_command(cmd); 104 | } 105 | 106 | function tryComplete() { 107 | 108 | refocus(); 109 | } 110 | 111 | function executeCommand(cmd) { 112 | cmd = (!cmd) ? input.val() : cmd; 113 | reset_input(); 114 | var result = evalInScope(cmd, default_scope); 115 | command_selected = command_history.length; 116 | logCommand(cmd, result); 117 | setTimeout(function() { input.attr("rows", 1); }, 2); 118 | return false; 119 | } 120 | 121 | function evalInScope(cmd, scope) { 122 | try { 123 | 124 | //if(!scope) return eval.apply(our_scope, [cmd]); 125 | with(scope) { 126 | with(jQConsole.our_scope) { 127 | return eval(cmd); 128 | } 129 | } 130 | 131 | //move_modified_scope(local_scope, global_scope); 132 | } 133 | catch(e) { 134 | return e.message; 135 | } 136 | } 137 | 138 | function move_modified_scope(l, g) { 139 | 140 | for(var k in g) { 141 | if(g[k] && typeof l[k] == 'undefined') { 142 | l[k] = g[k]; 143 | g[k] = null; 144 | } 145 | } 146 | } 147 | 148 | function safe_scope() { 149 | var s = {}, g = jQConsole.global_scope; 150 | for(var k in g) { s[k] = null; } 151 | s.global_scope = jQConsole.global_scope; 152 | return s; 153 | } 154 | 155 | var encoders = { 156 | "object": function(o, l) { 157 | if(o.constructor == Array) 158 | return encode_array(o); 159 | //return "[array]"; 160 | return "{ " + encode_obj(o, l) + " }"; 161 | }, 162 | "function": function(v) { return v.toString(); }, 163 | "string": function(v) { return "\"" + v + "\""; }, 164 | "undefined": function() { return "undefined"; }, 165 | _default: function(v) { return v.toString(); } 166 | } 167 | 168 | function encode_array(a) { 169 | var r = a.map(enc); 170 | return "[" + r.join(",") + "]"; 171 | } 172 | 173 | enc = function(v, l, root) { 174 | root = root || true; 175 | if(v == null) return (root) ? "" : "null" + l; 176 | if(encoders[typeof v]) return encoders[typeof v](v); 177 | //log("enc", v, l); 178 | return encoders._default(v, l); 179 | } 180 | 181 | function encode_obj(val, expand) { 182 | if(expand <= 0) { return val.toString(); } 183 | var r = []; 184 | for(var i in val) { 185 | r.push(i + ": " + enc(val[i], expand - 1, false)); 186 | } 187 | return r.join(",\n"); 188 | } 189 | 190 | function encode_reg(s) { 191 | return s.replace(/([\\/\t\n])/g, "\\$1"); 192 | } 193 | 194 | function reg_lookup_fn(lookup) { 195 | var re = new RegExp(encode_reg(keys(lookup).join("")), "ig"); 196 | return re; 197 | } 198 | 199 | function print(msg) { 200 | var className = (typeof msg == "function") ? "cmd" : "print"; 201 | msg = enc(msg, 3); 202 | if(!msg) return; 203 | var out = $($.PRE({"className":className}, msg)); 204 | if(className == "cmd") { out.click(select_command); } 205 | output.prepend(out); 206 | } 207 | 208 | function logCommand(cmd, result) { 209 | command_history.push(cmd); 210 | if(result != undefined) { 211 | output.prepend(jQuery.dump(result)); 212 | } 213 | $($.PRE({className:'cmd'}, cmd)).click(select_command).prependTo(output); 214 | //print(result); 215 | return cmd; 216 | } 217 | 218 | function select_command() { 219 | edit_command($(this).text()); 220 | } 221 | 222 | function edit_command(cmd) { 223 | input.val(cmd); 224 | resize_input(); 225 | input.get(0).select(); 226 | } 227 | 228 | function log() { 229 | var msg = args(arguments); 230 | $("
    ").text(msg.join(", ")).prependTo(output); 231 | } 232 | 233 | function resize_input() 234 | { 235 | setTimeout(do_resize, 0); 236 | 237 | function do_resize() { 238 | var rows = input.val().split(/\n/).length 239 | // + 1 // prevent scrollbar flickering in Mozilla 240 | + (window.opera ? 1 : 0); // leave room for scrollbar in Opera 241 | 242 | // without this check, it is impossible to select text in Opera 7.60 or Opera 8.0. 243 | if (input.attr("rows") != rows) 244 | input.attr("rows", rows); 245 | } 246 | } 247 | 248 | var default_scope = { 249 | "log": log, 250 | "history": history, 251 | alert: function(msg) { alert(msg); } 252 | } 253 | disable_functions(default_scope, "window,document,t1"); 254 | 255 | function disable_functions(obj, list) { 256 | var list = list.split(","); 257 | for(var i in list) { 258 | obj[list[i]] = {}; 259 | } 260 | } 261 | 262 | return this; 263 | }; 264 | 265 | jQuery.extend(jQuery.fn, { 266 | "autoresize": function() 267 | { 268 | var thisp = this; 269 | setTimeout(do_resize, 0); 270 | 271 | function do_resize() { 272 | var s = thisp.val() || ""; 273 | var rows = s.split(/\n/).length; 274 | // + 1 // prevent scrollbar flickering in Mozilla 275 | + (window.opera ? 1 : 0); // leave room for scrollbar in Opera 276 | 277 | // without this check, it is impossible to select text in Opera 7.60 or Opera 8.0. 278 | if (thisp.attr("rows") != rows) 279 | thisp.attr("rows", rows); 280 | } 281 | return this; 282 | } 283 | }) 284 | 285 | 286 | 287 | jQConsole.global_scope = this; 288 | jQConsole.our_scope = {}; -------------------------------------------------------------------------------- /app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | try ruby! (in your browser) 12 | 13 | 14 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 38 | 49 | 50 | 51 |
    52 | 53 |
    54 | 95 |
    96 |
    97 | 98 |

    A Popup Browser

    99 |

    [x]

    100 |
    101 | 102 |
    103 |
    104 | 105 |
    106 |
    107 | <%= yield %> 108 |
    109 |
    110 |
    111 |
    112 |
    113 |

    Got 15 minutes? Give Ruby a shot right now!

    114 |

    Ruby is a programming language from Japan 115 | (available at ruby-lang.org) 116 | which is revolutionizing the web. 117 | The beauty of Ruby is found in its balance between simplicity and power.

    118 | 119 |

    Try out Ruby code in the prompt above. In addition 120 | to Ruby's builtin methods, the following commands are available:

    121 |
      122 |
    • help 123 | Start the 15 minute interactive tutorial. Trust me, it's very basic!
    • 124 |
    • help 2 125 | Hop to chapter two.
    • 126 | 127 |
    • clear 128 | Clear screen. Useful if your browser starts slowing down. 129 | Your command history will be remembered. 130 |
    • back 131 | Go back one screen in the tutorial.
    • 132 |
    • reset 133 | Reset the interpreter if you get too deep. (or Ctrl-D!)
    • 134 |
    • next 135 | Allows you to skip to the next section of a lesson.
    • 136 | 137 |
    • time 138 | A stopwatch. Prints the time your session has been open.
    • 139 |
    140 |

    If you happen to leave or refresh the page, your session will still be here for 141 | unless it is left inactive for ten minutes.

    142 |
    143 |
    144 |
    145 | 146 |
    147 |
    Trapped in double dots? A quote or something was left open. Type: reset or hit Ctrl-D.
    148 |
    149 | 150 |

    This place was sired by why the lucky stiff. 151 | Please contact me using the email address at that link.is maintained by Andrew McElroy and David Miani. For support issues, please post a ticket or contact Sophrinix on github. 152 |

    153 | 154 |
    155 | 156 | 157 | 160 | 161 | 165 | 170 | 171 | 172 | Please Support
    Try Ruby!
    173 | 174 | 175 | 176 | -------------------------------------------------------------------------------- /public/javascripts/facebox.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Facebox (for jQuery) 3 | * version: 1.2 (05/05/2008) 4 | * @requires jQuery v1.2 or later 5 | * 6 | * Examples at http://famspam.com/facebox/ 7 | * 8 | * Licensed under the MIT: 9 | * http://www.opensource.org/licenses/mit-license.php 10 | * 11 | * Copyright 2007, 2008 Chris Wanstrath [ chris@ozmm.org ] 12 | * 13 | * Usage: 14 | * 15 | * jQuery(document).ready(function() { 16 | * jQuery('a[rel*=facebox]').facebox() 17 | * }) 18 | * 19 | * Terms 20 | * Loads the #terms div in the box 21 | * 22 | * Terms 23 | * Loads the terms.html page in the box 24 | * 25 | * Terms 26 | * Loads the terms.png image in the box 27 | * 28 | * 29 | * You can also use it programmatically: 30 | * 31 | * jQuery.facebox('some html') 32 | * jQuery.facebox('some html', 'my-groovy-style') 33 | * 34 | * The above will open a facebox with "some html" as the content. 35 | * 36 | * jQuery.facebox(function($) { 37 | * $.get('blah.html', function(data) { $.facebox(data) }) 38 | * }) 39 | * 40 | * The above will show a loading screen before the passed function is called, 41 | * allowing for a better ajaxy experience. 42 | * 43 | * The facebox function can also display an ajax page, an image, or the contents of a div: 44 | * 45 | * jQuery.facebox({ ajax: 'remote.html' }) 46 | * jQuery.facebox({ ajax: 'remote.html' }, 'my-groovy-style') 47 | * jQuery.facebox({ image: 'stairs.jpg' }) 48 | * jQuery.facebox({ image: 'stairs.jpg' }, 'my-groovy-style') 49 | * jQuery.facebox({ div: '#box' }) 50 | * jQuery.facebox({ div: '#box' }, 'my-groovy-style') 51 | * 52 | * Want to close the facebox? Trigger the 'close.facebox' document event: 53 | * 54 | * jQuery(document).trigger('close.facebox') 55 | * 56 | * Facebox also has a bunch of other hooks: 57 | * 58 | * loading.facebox 59 | * beforeReveal.facebox 60 | * reveal.facebox (aliased as 'afterReveal.facebox') 61 | * init.facebox 62 | * afterClose.facebox 63 | * 64 | * Simply bind a function to any of these hooks: 65 | * 66 | * $(document).bind('reveal.facebox', function() { ...stuff to do after the facebox and contents are revealed... }) 67 | * 68 | */ 69 | (function($) { 70 | $.facebox = function(data, klass) { 71 | $.facebox.loading() 72 | 73 | if (data.ajax) fillFaceboxFromAjax(data.ajax, klass) 74 | else if (data.image) fillFaceboxFromImage(data.image, klass) 75 | else if (data.div) fillFaceboxFromHref(data.div, klass) 76 | else if ($.isFunction(data)) data.call($) 77 | else $.facebox.reveal(data, klass) 78 | } 79 | 80 | /* 81 | * Public, $.facebox methods 82 | */ 83 | 84 | $.extend($.facebox, { 85 | settings: { 86 | opacity : 0.2, 87 | overlay : true, 88 | loadingImage : '/facebox/loading.gif', 89 | closeImage : '/facebox/closelabel.png', 90 | imageTypes : [ 'png', 'jpg', 'jpeg', 'gif' ], 91 | faceboxHtml : '\ 92 | ' 99 | }, 100 | 101 | loading: function() { 102 | init() 103 | if ($('#facebox .loading').length == 1) return true 104 | showOverlay() 105 | 106 | $('#facebox .content').empty() 107 | $('#facebox .body').children().hide().end(). 108 | append('
    ') 109 | 110 | $('#facebox').css({ 111 | top: getPageScroll()[1] + (getPageHeight() / 10), 112 | left: $(window).width() / 2 - 205 113 | }).show() 114 | 115 | $(document).bind('keydown.facebox', function(e) { 116 | if (e.keyCode == 27) $.facebox.close() 117 | return true 118 | }) 119 | $(document).trigger('loading.facebox') 120 | }, 121 | 122 | reveal: function(data, klass) { 123 | $(document).trigger('beforeReveal.facebox') 124 | if (klass) $('#facebox .content').addClass(klass) 125 | $('#facebox .content').append(data) 126 | $('#facebox .loading').remove() 127 | $('#facebox .body').children().fadeIn('normal') 128 | $('#facebox').css('left', $(window).width() / 2 - ($('#facebox .popup').width() / 2)) 129 | $(document).trigger('reveal.facebox').trigger('afterReveal.facebox') 130 | }, 131 | 132 | close: function() { 133 | $(document).trigger('close.facebox') 134 | return false 135 | } 136 | }) 137 | 138 | /* 139 | * Public, $.fn methods 140 | */ 141 | 142 | $.fn.facebox = function(settings) { 143 | if ($(this).length == 0) return 144 | 145 | init(settings) 146 | 147 | function clickHandler() { 148 | $.facebox.loading(true) 149 | 150 | // support for rel="facebox.inline_popup" syntax, to add a class 151 | // also supports deprecated "facebox[.inline_popup]" syntax 152 | var klass = this.rel.match(/facebox\[?\.(\w+)\]?/) 153 | if (klass) klass = klass[1] 154 | 155 | fillFaceboxFromHref(this.href, klass) 156 | return false 157 | } 158 | 159 | return this.bind('click.facebox', clickHandler) 160 | } 161 | 162 | /* 163 | * Private methods 164 | */ 165 | 166 | // called one time to setup facebox on this page 167 | function init(settings) { 168 | if ($.facebox.settings.inited) return true 169 | else $.facebox.settings.inited = true 170 | 171 | $(document).trigger('init.facebox') 172 | makeCompatible() 173 | 174 | var imageTypes = $.facebox.settings.imageTypes.join('|') 175 | $.facebox.settings.imageTypesRegexp = new RegExp('\.(' + imageTypes + ')$', 'i') 176 | 177 | if (settings) $.extend($.facebox.settings, settings) 178 | $('body').append($.facebox.settings.faceboxHtml) 179 | 180 | var preload = [ new Image(), new Image() ] 181 | preload[0].src = $.facebox.settings.closeImage 182 | preload[1].src = $.facebox.settings.loadingImage 183 | 184 | $('#facebox').find('.b:first, .bl').each(function() { 185 | preload.push(new Image()) 186 | preload.slice(-1).src = $(this).css('background-image').replace(/url\((.+)\)/, '$1') 187 | }) 188 | 189 | $('#facebox .close').click($.facebox.close) 190 | $('#facebox .close_image').attr('src', $.facebox.settings.closeImage) 191 | } 192 | 193 | // getPageScroll() by quirksmode.com 194 | function getPageScroll() { 195 | var xScroll, yScroll; 196 | if (self.pageYOffset) { 197 | yScroll = self.pageYOffset; 198 | xScroll = self.pageXOffset; 199 | } else if (document.documentElement && document.documentElement.scrollTop) { // Explorer 6 Strict 200 | yScroll = document.documentElement.scrollTop; 201 | xScroll = document.documentElement.scrollLeft; 202 | } else if (document.body) {// all other Explorers 203 | yScroll = document.body.scrollTop; 204 | xScroll = document.body.scrollLeft; 205 | } 206 | return new Array(xScroll,yScroll) 207 | } 208 | 209 | // Adapted from getPageSize() by quirksmode.com 210 | function getPageHeight() { 211 | var windowHeight 212 | if (self.innerHeight) { // all except Explorer 213 | windowHeight = self.innerHeight; 214 | } else if (document.documentElement && document.documentElement.clientHeight) { // Explorer 6 Strict Mode 215 | windowHeight = document.documentElement.clientHeight; 216 | } else if (document.body) { // other Explorers 217 | windowHeight = document.body.clientHeight; 218 | } 219 | return windowHeight 220 | } 221 | 222 | // Backwards compatibility 223 | function makeCompatible() { 224 | var $s = $.facebox.settings 225 | 226 | $s.loadingImage = $s.loading_image || $s.loadingImage 227 | $s.closeImage = $s.close_image || $s.closeImage 228 | $s.imageTypes = $s.image_types || $s.imageTypes 229 | $s.faceboxHtml = $s.facebox_html || $s.faceboxHtml 230 | } 231 | 232 | // Figures out what you want to display and displays it 233 | // formats are: 234 | // div: #id 235 | // image: blah.extension 236 | // ajax: anything else 237 | function fillFaceboxFromHref(href, klass) { 238 | // div 239 | if (href.match(/#/)) { 240 | var url = window.location.href.split('#')[0] 241 | var target = href.replace(url,'') 242 | if (target == '#') return 243 | $.facebox.reveal($(target).html(), klass) 244 | 245 | // image 246 | } else if (href.match($.facebox.settings.imageTypesRegexp)) { 247 | fillFaceboxFromImage(href, klass) 248 | // ajax 249 | } else { 250 | fillFaceboxFromAjax(href, klass) 251 | } 252 | } 253 | 254 | function fillFaceboxFromImage(href, klass) { 255 | var image = new Image() 256 | image.onload = function() { 257 | $.facebox.reveal('
    ', klass) 258 | } 259 | image.src = href 260 | } 261 | 262 | function fillFaceboxFromAjax(href, klass) { 263 | $.get(href, function(data) { $.facebox.reveal(data, klass) }) 264 | } 265 | 266 | function skipOverlay() { 267 | return $.facebox.settings.overlay == false || $.facebox.settings.opacity === null 268 | } 269 | 270 | function showOverlay() { 271 | if (skipOverlay()) return 272 | 273 | if ($('#facebox_overlay').length == 0) 274 | $("body").append('
    ') 275 | 276 | $('#facebox_overlay').hide().addClass("facebox_overlayBG") 277 | .css('opacity', $.facebox.settings.opacity) 278 | .click(function() { $(document).trigger('close.facebox') }) 279 | .fadeIn(200) 280 | return false 281 | } 282 | 283 | function hideOverlay() { 284 | if (skipOverlay()) return 285 | 286 | $('#facebox_overlay').fadeOut(200, function(){ 287 | $("#facebox_overlay").removeClass("facebox_overlayBG") 288 | $("#facebox_overlay").addClass("facebox_hide") 289 | $("#facebox_overlay").remove() 290 | }) 291 | 292 | return false 293 | } 294 | 295 | /* 296 | * Bindings 297 | */ 298 | 299 | $(document).bind('close.facebox', function() { 300 | $(document).unbind('keydown.facebox') 301 | $('#facebox').fadeOut(function() { 302 | $('#facebox .content').removeClass().addClass('content') 303 | $('#facebox .loading').remove() 304 | $(document).trigger('afterClose.facebox') 305 | }) 306 | hideOverlay() 307 | }) 308 | 309 | })(jQuery); 310 | -------------------------------------------------------------------------------- /public/stylesheets/site.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: verdana, arial, sans-serif; 3 | font-size: 14px; 4 | text-align: center; 5 | } 6 | h1, h2, h3, h4 { 7 | font-family: georgia, serif; 8 | margin: 10px 45px; padding: 0; 9 | } 10 | h1 { 11 | color: #333; 12 | font-size: 48px; 13 | font-weight: normal; 14 | margin: 10px 0px; 15 | } 16 | h3 { 17 | color: white; 18 | font-size: 21px; 19 | font-weight: normal; 20 | } 21 | a,a:link,a:visited { 22 | text-decoration: none; 23 | color: #57ad11; 24 | } 25 | a:hover { 26 | text-decoration: underline; 27 | color: #57ad11; 28 | } 29 | a:active { 30 | text-decoration: underline; 31 | color: #dddddd; 32 | } 33 | input.keyboard-selector-input { 34 | position: fixed; 35 | top: 0; 36 | _position: absolute; 37 | _top: expression(eval(document.body.scrollTop)); 38 | left: -300px; 39 | } 40 | .container { 41 | width: 678px; 42 | margin: 0 auto; 43 | text-align: left; 44 | } 45 | .content { 46 | width: 712px; 47 | background: url(/images/tile.png) repeat-y; 48 | } 49 | #lilBrowser { 50 | position: absolute; 51 | background-color: white; 52 | top: -540px; 53 | left: -440px; 54 | width: 510px; 55 | height: 430px; 56 | padding: 5px; 57 | border: solid 1px #444; 58 | z-index: 100; 59 | } 60 | h3#lbTitle { 61 | color: #444; 62 | font-family: verdana, arial, sans-serif; 63 | font-size: small; 64 | float:left; 65 | line-height: 90%; 66 | margin: 0; padding: 3px; 67 | } 68 | p#lbClose { 69 | font-size: small; 70 | float: right; 71 | margin: 0; padding: 3px; 72 | } 73 | p#lbClose a { 74 | display: inline; 75 | } 76 | .shellwin { 77 | width: 712px; 78 | padding-left: 12px; 79 | background: url(/images/background.png) no-repeat; 80 | } 81 | 82 | /* tutorial panes */ 83 | .stretcher { 84 | color: #f1f1ff; 85 | display: none; 86 | margin: 0; 87 | padding: 0; 88 | padding-left: 12px; 89 | background: url(/images/tile.png) repeat-y; 90 | } 91 | .stretcher a, .stretcher a:link, .stretcher a:visited, .stretcher a:active { 92 | text-decoration: none; 93 | color: #a7ed91; 94 | } 95 | .stretcher a:hover { 96 | text-decoration: underline; 97 | color: #b7fd91; 98 | } 99 | .stretcher p { 100 | margin: 10px 16px; 101 | } 102 | .stretcher dl, .stretcher ul { 103 | background-color: white; 104 | color: #333; 105 | padding: 4px 8px; 106 | font-size: 12px; 107 | margin-left: 16px; 108 | list-style: none; 109 | } 110 | .stretcher li { 111 | margin: 6px; 112 | } 113 | .stretcher p code { 114 | background-color: #874a20; 115 | color: #fedeec; 116 | padding: 1px 4px; 117 | } 118 | .stretcher p code.cmd { 119 | background-color: #eeeeec; 120 | color: #204a87; 121 | } 122 | .stretcher dt { 123 | font-weight: bold; 124 | } 125 | 126 | .chapmark { 127 | padding: 6px 0; 128 | margin-left: 12px; 129 | margin-right: 22px; 130 | color: #553; 131 | background: #efefe1; 132 | } 133 | .chapmark h3 { 134 | color: #335; 135 | } 136 | .chapmark a, .chapmark a:link, .chapmark a:visited, .chapmark a:active { 137 | text-decoration: none; 138 | color: #372d61; 139 | } 140 | .chapmark a:hover { 141 | text-decoration: underline; 142 | color: #477d51; 143 | } 144 | .note { color: #ddc; text-align: center; font-size: xx-small; } 145 | ul li strong { color: #286; border-bottom: solid 2px #cca; } 146 | ul li code { background-color: #f1f1f1; padding: 1px 3px; border-bottom: solid 2px #ddd; } 147 | ul li code.faded { color: #899; } 148 | code strong { background-color: #dcffb9; padding: 1px 3px; } 149 | ul.commands li strong { display: block; float: left; width: 60px; border: none; } 150 | 151 | /* irb terminal */ 152 | .terminal { 153 | background-color: #ffffff; 154 | border: solid 1px #204a87; 155 | width: 678px; 156 | height: 240px; 157 | overflow: auto; 158 | } 159 | .console { 160 | padding: 4px; margin-left: -50px; 161 | font-family: "Andale Mono", courier, fixed, monospace; 162 | font-size: 14px; 163 | line-height: 16px; 164 | color: #204a87; 165 | text-align: left; 166 | width: 664px; 167 | height:220px; 168 | } 169 | 170 | .console div b { 171 | background-color: #874a20; 172 | color: #fedeac; 173 | } 174 | div.answer, div.stdout, div.no_answer, div.load { 175 | display: none; 176 | } 177 | 178 | /* terminal escape colors */ 179 | span.fore_black { color: #2e3436; } 180 | span.fore_dark_gray { color: #888a85; } 181 | span.fore_gray { color: #babdb6; } 182 | span.fore_white { color: #eeeeec; } 183 | span.fore_blue { color: #204a87; } 184 | span.fore_lt_blue { color: #729fcf; } 185 | span.fore_green { color: #788600; font-weight: bold; } 186 | span.fore_lt_green { color: #cbe134; } 187 | span.fore_cyan { color: #c4a000; } /* using cyan for yellows */ 188 | span.fore_lt_cyan { color: #fc994f; } 189 | span.fore_red { color: #a40000; } 190 | span.fore_lt_red { color: #ef2929; font-weight: bold; } 191 | span.fore_purple { color: #5c3566; } 192 | span.fore_lt_purple { color: #ad7fa8; } 193 | span.fore_brown { color: #8f5972; } 194 | span.fore_lt_brown { color: #b9b9de; } 195 | span.back_black { background-color: #2e3436; } 196 | span.back_dark_gray { background-color: #888a85; } 197 | span.back_gray { background-color: #babdb6; } 198 | span.back_white { background-color: #eeeeec; } 199 | span.back_blue { background-color: #204a87; } 200 | span.back_lt_blue { background-color: #729fcf; } 201 | span.back_green { background-color: #788600; } 202 | span.back_lt_green { background-color: #cbe134; } 203 | span.back_cyan { background-color: #c4a000; } /* using cyan for yellows */ 204 | span.back_lt_cyan { background-color: #fce94f; } 205 | span.back_red { background-color: #a40000; } 206 | span.back_lt_red { background-color: #ef2929; } 207 | span.back_purple { background-color: #5c3566; } 208 | span.back_lt_purple { background-color: #ad7fa8; } 209 | span.back_brown { background-color: #8f5902; } 210 | span.back_lt_brown { background-color: #b9b96e; } 211 | 212 | /** no ways***/ 213 | 214 | div.main-wrapper-bottom { 215 | height:4px;background-position: 0px -80px; background-repeat: no-repeat; 216 | font-size:0 /* IE6, go figure */ 217 | } 218 | div.main-wrapper-borders { 219 | background-position: -1731px 0; padding:15px; 220 | background-repeat:repeat-y; 221 | } 222 | div.console-wrapper { 223 | margin:0px auto ;width:566px; 224 | cursor:text; 225 | font-family:monospace; 226 | } 227 | div.console-wrapper-top { 228 | height:3px;background-position: -50px -48px; background-repeat: no-repeat; 229 | font-size:0 /* IE6, go figure */ 230 | } 231 | div.console-wrapper-bottom { 232 | height:3px;background-position: -50px -51px; background-repeat: no-repeat; 233 | font-size:0 /* IE6, go figure */ 234 | } 235 | div.console-wrapper-borders { 236 | background-position: 0px 0px; padding:1px; 237 | background-repeat:repeat-y; 238 | } 239 | div.guide-wrapper { 240 | color:#fff; width:566px;margin-left:2px 241 | } 242 | div.guide-wrapper-top { 243 | height:4px;background-position: 0 -65px; 244 | font-size:0 /* IE6, go figure */ 245 | } 246 | div.guide-wrapper-bottom { 247 | height:4px;background-position: 0 -70px; 248 | font-size:0 /* IE6, go figure */ 249 | } 250 | div.guide-wrapper-borders { 251 | background-position: -1166px 0px;padding:15px; 252 | background-repeat:repeat-y; 253 | } 254 | 255 | div.footer-wrapper-borders { 256 | background-position: -566px 0px; padding:10px; 257 | background-repeat:repeat-y; 258 | font-size:12px 259 | } 260 | h1.main-header { 261 | text-indent:-9999px; background-position: -49px 0px; 262 | background-repeat: no-repeat; 263 | width:318px; height:48px; margin-bottom:20px; 264 | float:left 265 | } 266 | 267 | div.footer { 268 | line-height: 1.3em; 269 | } 270 | div.console div.jquery-console-inner 271 | { height:100%; overflow:auto; background:white} 272 | div.console div.jquery-console-prompt-box 273 | { color:#437375; font-family:monospace; margin-top:0.5em; } 274 | div.console div.jquery-console-prompt-box .prompt-done 275 | { cursor: pointer } 276 | div.console div.jquery-console-prompt-box .prompt-done:hover 277 | { background:#453D5B; color: white; } 278 | div.console div.jquery-console-focus span.jquery-console-cursor 279 | { background:#666; color:#fff; } 280 | div.console div.jquery-console-message-error { 281 | color:#ef0505; font-family:sans-serif; font-weight:bold; 282 | padding-top:0.25em 283 | } 284 | div.console div.jquery-console-message-value 285 | { color:#000; font-family:monospace;padding-top:0.25em; font-weight: bold; } 286 | div.console div.jquery-console-message-type 287 | { color:#382567; font-family:monospace;padding-left:0em;padding-top:0.25em; font-size:.9em } 288 | div.console span.jquery-console-prompt-label { font-weight:bold } 289 | div.console div.jquery-console-welcome { font-family:"DejaVu Sans",sans-serif; } 290 | 291 | div.share-wrapper { font-size:12px;padding:10px 0em 0em 10px } 292 | div.share-wrapper strong { font-weight: bold } 293 | 294 | .clearfix:after { content:"."; display:block; height:0; clear:both; visibility:hidden } 295 | div.menu { 296 | float:right; 297 | margin-right:2px; 298 | margin-top:40px; 299 | margin-bottom:5px 300 | } 301 | a.reset-btn { 302 | float:left; 303 | display:block; 304 | width:59px; 305 | height:24px; 306 | background-position: -427px -18px; background-repeat: no-repeat; 307 | } 308 | a.reset-btn span { display:none } 309 | 310 | div.clear { clear:both } 311 | 312 | 313 | div.console-wrapper .notice { 314 | position:absolute; 315 | bottom:0;right:0; 316 | margin:1px; 317 | background:#eee; 318 | color:black; 319 | padding:10px; 320 | font-size:12px; 321 | font-family:sans-serif; 322 | font-weight:bold; 323 | } 324 | p.ajax-loader { background:url(../images/ajax-loader.gif); width:16px; height:16px;text-indent:-9999px } 325 | 326 | .notice a { padding:3px;background:#333;color:white} 327 | .notice .action { text-align: right } 328 | 329 | /* Support Try Ruby! */ 330 | 331 | 332 | a.trigger{ 333 | position: absolute; 334 | text-decoration: none; 335 | top: 80px; right: 0; 336 | font-size: 14px; 337 | letter-spacing:-1px; 338 | font-family: verdana, helvetica, arial, sans-serif; 339 | color:#fff; 340 | padding: 10px 10px 10px 10px; 341 | font-weight: 500; 342 | background:#333333 url(images/plus.png) 15% 55% no-repeat; 343 | border:1px solid #444444; 344 | -moz-border-radius-topleft: 20px; 345 | -webkit-border-top-left-radius: 20px; 346 | -moz-border-radius-bottomleft: 20px; 347 | -webkit-border-bottom-left-radius: 20px; 348 | -moz-border-radius-bottomright: 0px; 349 | -webkit-border-bottom-right-radius: 0px; 350 | display: block; 351 | } 352 | 353 | a.trigger:hover{ 354 | position: absolute; 355 | text-decoration: none; 356 | top: 80px; right: 0; 357 | font-size: 14px; 358 | letter-spacing:-1px; 359 | font-family: verdana, helvetica, arial, sans-serif; 360 | color:#fff; 361 | padding: 10px 10px 10px 10px; 362 | font-weight: 500; 363 | background: green; 364 | border:1px solid #444444; 365 | -moz-border-radius-topleft: 20px; 366 | -webkit-border-top-left-radius: 20px; 367 | -moz-border-radius-bottomleft: 20px; 368 | -webkit-border-bottom-left-radius: 20px; 369 | -moz-border-radius-bottomright: 0px; 370 | -webkit-border-bottom-right-radius: 0px; 371 | display: block; 372 | } 373 | 374 | a.active.trigger { 375 | background:#222222 url(images/minus.png) 15% 55% no-repeat; 376 | } 377 | 378 | -------------------------------------------------------------------------------- /public/javascripts/jquery.console.js: -------------------------------------------------------------------------------- 1 | // JQuery Console 1.0 2 | // Sun Feb 21 20:28:47 GMT 2010 3 | // 4 | // Copyright 2010 Chris Done, Simon David Pratt. All rights reserved. 5 | // 6 | // Redistribution and use in source and binary forms, with or without 7 | // modification, are permitted provided that the following conditions 8 | // are met: 9 | // 10 | // 1. Redistributions of source code must retain the above 11 | // copyright notice, this list of conditions and the following 12 | // disclaimer. 13 | // 14 | // 2. Redistributions in binary form must reproduce the above 15 | // copyright notice, this list of conditions and the following 16 | // disclaimer in the documentation and/or other materials 17 | // provided with the distribution. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 22 | // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 23 | // COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 24 | // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 25 | // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 26 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 | // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 29 | // ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | // POSSIBILITY OF SUCH DAMAGE. 31 | 32 | // TESTED ON 33 | // Internet Explorer 6 34 | // Opera 10.01 35 | // Chromium 4.0.237.0 (Ubuntu build 31094) 36 | // Firefox 3.5.8, 3.6.2 (Mac) 37 | // Safari 4.0.5 (6531.22.7) (Mac) 38 | // Google Chrome 5.0.375.55 (Mac) 39 | 40 | (function($){ 41 | $.fn.console = function(config){ 42 | //////////////////////////////////////////////////////////////////////// 43 | // Constants 44 | // Some are enums, data types, others just for optimisation 45 | var keyCodes = { 46 | // left 47 | 37: moveBackward, 48 | // right 49 | 39: moveForward, 50 | // up 51 | 38: previousHistory, 52 | // down 53 | 40: nextHistory, 54 | // backspace 55 | 8: backDelete, 56 | // delete 57 | 46: forwardDelete, 58 | // end 59 | 35: moveToEnd, 60 | // start 61 | 36: moveToStart, 62 | // return 63 | 13: commandTrigger, 64 | // tab 65 | 18: doNothing 66 | }; 67 | var ctrlCodes = { 68 | // C-a 69 | 65: moveToStart, 70 | // C-e 71 | 69: moveToEnd, 72 | // C-d 73 | 68: forwardDelete, 74 | // C-n 75 | 78: nextHistory, 76 | // C-p 77 | 80: previousHistory, 78 | // C-b 79 | 66: moveBackward, 80 | // C-f 81 | 70: moveForward, 82 | // C-k 83 | 75: deleteUntilEnd 84 | }; 85 | var altCodes = { 86 | // M-f 87 | 70: moveToNextWord, 88 | // M-b 89 | 66: moveToPreviousWord, 90 | // M-d 91 | 68: deleteNextWord 92 | }; 93 | var cursor = ' '; 94 | // Opera only works with this character, not or ­, 95 | // but IE6 displays this character, which is bad, so just use 96 | // it on Opera. 97 | var wbr = $.browser.opera? '​' : ''; 98 | 99 | //////////////////////////////////////////////////////////////////////// 100 | // Globals 101 | var container = $(this); 102 | var inner = $('
    '); 103 | var typer = $(''); 104 | // Prompt 105 | var promptBox; 106 | var prompt; 107 | var promptLabel = config && config.promptLabel? config.promptLabel : "> "; 108 | var column = 0; 109 | var promptText = ''; 110 | var restoreText = ''; 111 | // Prompt history stack 112 | var history = []; 113 | var ringn = 0; 114 | // For reasons unknown to The Sword of Michael himself, Opera 115 | // triggers and sends a key character when you hit various 116 | // keys like PgUp, End, etc. So there is no way of knowing 117 | // when a user has typed '#' or End. My solution is in the 118 | // typer.keydown and typer.keypress functions; I use the 119 | // variable below to ignore the keypress event if the keydown 120 | // event succeeds. 121 | var cancelKeyPress = 0; 122 | // When this value is false, the prompt will not respond to input 123 | var acceptInput = true; 124 | // When this value is true, the command has been canceled 125 | var cancelCommand = false; 126 | 127 | // External exports object 128 | var extern = {}; 129 | 130 | //////////////////////////////////////////////////////////////////////// 131 | // Main entry point 132 | (function(){ 133 | container.append(inner); 134 | inner.append(typer); 135 | typer.css({position:'absolute',top:0,left:'-9999px'}); 136 | if (config.welcomeMessage) 137 | message(config.welcomeMessage,'jquery-console-welcome'); 138 | newPromptBox(); 139 | if (config.autofocus) { 140 | inner.addClass('jquery-console-focus'); 141 | typer.focus(); 142 | setTimeout(function(){ 143 | inner.addClass('jquery-console-focus'); 144 | typer.focus(); 145 | },100); 146 | } 147 | extern.inner = inner; 148 | extern.typer = typer; 149 | extern.scrollToBottom = scrollToBottom; 150 | })(); 151 | 152 | //////////////////////////////////////////////////////////////////////// 153 | // Reset terminal 154 | extern.reset = function(){ 155 | var welcome = true; 156 | inner.parent().fadeOut(function(){ 157 | inner.find('div').each(function(){ 158 | if (!welcome) 159 | $(this).remove(); 160 | welcome = false; 161 | }); 162 | newPromptBox(); 163 | inner.parent().fadeIn(function(){ 164 | inner.addClass('jquery-console-focus'); 165 | typer.focus(); 166 | }); 167 | }); 168 | }; 169 | 170 | //////////////////////////////////////////////////////////////////////// 171 | // Reset terminal 172 | extern.notice = function(msg,style){ 173 | var n = $('
    ').append($('
    ').text(msg)) 174 | .css({visibility:'hidden'}); 175 | container.append(n); 176 | var focused = true; 177 | if (style=='fadeout') 178 | setTimeout(function(){ 179 | n.fadeOut(function(){ 180 | n.remove(); 181 | }); 182 | },4000); 183 | else if (style=='prompt') { 184 | var a = $('
    '); 185 | n.append(a); 186 | focused = false; 187 | a.click(function(){ n.fadeOut(function(){ n.remove();inner.css({opacity:1}) }); }); 188 | } 189 | var h = n.height(); 190 | n.css({height:'0px',visibility:'visible'}) 191 | .animate({height:h+'px'},function(){ 192 | if (!focused) inner.css({opacity:0.5}); 193 | }); 194 | n.css('cursor','default'); 195 | return n; 196 | }; 197 | 198 | //////////////////////////////////////////////////////////////////////// 199 | // Make a new prompt box 200 | function newPromptBox() { 201 | column = 0; 202 | promptText = ''; 203 | ringn = 0; // Reset the position of the history ring 204 | enableInput(); 205 | promptBox = $('
    '); 206 | var label = $(''); 207 | promptBox.append(label.text(promptLabel).show()); 208 | prompt = $(''); 209 | promptBox.append(prompt); 210 | inner.append(promptBox); 211 | updatePromptDisplay(); 212 | }; 213 | 214 | //////////////////////////////////////////////////////////////////////// 215 | // Handle setting focus 216 | container.click(function(){ 217 | inner.addClass('jquery-console-focus'); 218 | inner.removeClass('jquery-console-nofocus'); 219 | typer.focus(); 220 | scrollToBottom(); 221 | return false; 222 | }); 223 | 224 | //////////////////////////////////////////////////////////////////////// 225 | // Handle losing focus 226 | typer.blur(function(){ 227 | inner.removeClass('jquery-console-focus'); 228 | inner.addClass('jquery-console-nofocus'); 229 | }); 230 | 231 | //////////////////////////////////////////////////////////////////////// 232 | // Handle key hit before translation 233 | // For picking up control characters like up/left/down/right 234 | 235 | typer.keydown(function(e){ 236 | cancelKeyPress = 0; 237 | var keyCode = e.keyCode; 238 | // C-c: cancel the execution 239 | if(e.ctrlKey && keyCode == 67) { 240 | cancelKeyPress = keyCode; 241 | cancelExecution(); 242 | return false; 243 | } 244 | if (acceptInput) { 245 | if (keyCode in keyCodes) { 246 | cancelKeyPress = keyCode; 247 | (keyCodes[keyCode])(); 248 | return false; 249 | } else if (e.ctrlKey && keyCode in ctrlCodes) { 250 | cancelKeyPress = keyCode; 251 | (ctrlCodes[keyCode])(); 252 | return false; 253 | } else if (e.altKey && keyCode in altCodes) { 254 | cancelKeyPress = keyCode; 255 | (altCodes[keyCode])(); 256 | return false; 257 | } 258 | } 259 | }); 260 | 261 | //////////////////////////////////////////////////////////////////////// 262 | // Handle key press 263 | typer.keypress(function(e){ 264 | var keyCode = e.keyCode || e.which; 265 | if (isIgnorableKey(e)) { 266 | return false; 267 | } 268 | if (acceptInput && cancelKeyPress != keyCode && keyCode >= 32){ 269 | if (cancelKeyPress) return false; 270 | if (typeof config.charInsertTrigger == 'undefined' || 271 | (typeof config.charInsertTrigger == 'function' && 272 | config.charInsertTrigger(keyCode,promptText))) 273 | typer.consoleInsert(keyCode); 274 | } 275 | if ($.browser.webkit) return false; 276 | }); 277 | 278 | function isIgnorableKey(e) { 279 | // for now just filter alt+tab that we receive on some platforms when 280 | // user switches windows (goes away from the browser) 281 | return ((e.keyCode == keyCodes.tab || e.keyCode == 192) && e.altKey); 282 | }; 283 | 284 | //////////////////////////////////////////////////////////////////////// 285 | // Rotate through the command history 286 | function rotateHistory(n){ 287 | if (history.length == 0) return; 288 | ringn += n; 289 | if (ringn < 0) ringn = history.length; 290 | else if (ringn > history.length) ringn = 0; 291 | var prevText = promptText; 292 | if (ringn == 0) { 293 | promptText = restoreText; 294 | } else { 295 | promptText = history[ringn - 1]; 296 | } 297 | if (config.historyPreserveColumn) { 298 | if (promptText.length < column + 1) { 299 | column = promptText.length; 300 | } else if (column == 0) { 301 | column = promptText.length; 302 | } 303 | } else if (config.historyColumnAtEnd) { 304 | column = promptText.length; 305 | } else { 306 | column = 0; 307 | } 308 | updatePromptDisplay(); 309 | }; 310 | 311 | function previousHistory() { 312 | rotateHistory(-1); 313 | }; 314 | 315 | function nextHistory() { 316 | rotateHistory(1); 317 | }; 318 | 319 | // Add something to the history ring 320 | function addToHistory(line){ 321 | history.push(line); 322 | restoreText = ''; 323 | }; 324 | 325 | // Delete the character at the current position 326 | function deleteCharAtPos(){ 327 | if (column < promptText.length){ 328 | promptText = 329 | promptText.substring(0,column) + 330 | promptText.substring(column+1); 331 | restoreText = promptText; 332 | return true; 333 | } else return false; 334 | }; 335 | 336 | function backDelete() { 337 | if (moveColumn(-1)){ 338 | deleteCharAtPos(); 339 | updatePromptDisplay(); 340 | } 341 | }; 342 | 343 | function forwardDelete() { 344 | if (deleteCharAtPos()) 345 | updatePromptDisplay(); 346 | }; 347 | 348 | function deleteUntilEnd() { 349 | while(deleteCharAtPos()) { 350 | updatePromptDisplay(); 351 | } 352 | }; 353 | 354 | function deleteNextWord() { 355 | // A word is defined within this context as a series of alphanumeric 356 | // characters. 357 | // Delete up to the next alphanumeric character 358 | while(column < promptText.length && 359 | !isCharAlphanumeric(promptText[column])) { 360 | deleteCharAtPos(); 361 | updatePromptDisplay(); 362 | } 363 | // Then, delete until the next non-alphanumeric character 364 | while(column < promptText.length && 365 | isCharAlphanumeric(promptText[column])) { 366 | deleteCharAtPos(); 367 | updatePromptDisplay(); 368 | } 369 | }; 370 | 371 | //////////////////////////////////////////////////////////////////////// 372 | // Validate command and trigger it if valid, or show a validation error 373 | function commandTrigger() { 374 | var line = promptText; 375 | if (typeof config.commandValidate == 'function') { 376 | var ret = config.commandValidate(line); 377 | if (ret == true || ret == false) { 378 | if (ret) { 379 | handleCommand(); 380 | } 381 | } else { 382 | commandResult(ret,"jquery-console-message-error"); 383 | } 384 | } else { 385 | handleCommand(); 386 | } 387 | }; 388 | 389 | // Scroll to the bottom of the view 390 | function scrollToBottom() { 391 | inner.attr({ scrollTop: inner.attr("scrollHeight") });; 392 | }; 393 | 394 | function cancelExecution() { 395 | if(typeof config.cancelHandle == 'function') { 396 | config.cancelHandle(); 397 | } 398 | } 399 | 400 | //////////////////////////////////////////////////////////////////////// 401 | // Handle a command 402 | function handleCommand() { 403 | if (typeof config.commandHandle == 'function') { 404 | disableInput(); 405 | addToHistory(promptText); 406 | var ret = config.commandHandle(promptText,function(msgs){ 407 | commandResult(msgs); 408 | }); 409 | if (typeof ret == 'boolean') { 410 | if (ret) { 411 | // Command succeeded without a result. 412 | commandResult(); 413 | } else { 414 | commandResult('Command failed.', 415 | "jquery-console-message-error"); 416 | } 417 | } else if (typeof ret == "string") { 418 | commandResult(ret,"jquery-console-message-success"); 419 | } else if (typeof ret == 'object' && ret.length) { 420 | commandResult(ret); 421 | } 422 | } 423 | }; 424 | 425 | //////////////////////////////////////////////////////////////////////// 426 | // Disable input 427 | function disableInput() { 428 | acceptInput = false; 429 | }; 430 | 431 | // Enable input 432 | function enableInput() { 433 | acceptInput = true; 434 | } 435 | 436 | //////////////////////////////////////////////////////////////////////// 437 | // Reset the prompt in invalid command 438 | function commandResult(msg,className) { 439 | column = -1; 440 | updatePromptDisplay(); 441 | if (typeof msg == 'string') { 442 | message(msg,className); 443 | } else { 444 | for (var x in msg) { 445 | var ret = msg[x]; 446 | message(ret.msg,ret.className); 447 | } 448 | } 449 | newPromptBox(); 450 | }; 451 | 452 | //////////////////////////////////////////////////////////////////////// 453 | // Display a message 454 | function message(msg,className) { 455 | var mesg = $('
    '); 456 | if (className) mesg.addClass(className); 457 | mesg.filledText(msg).hide(); 458 | inner.append(mesg); 459 | mesg.show(); 460 | }; 461 | 462 | //////////////////////////////////////////////////////////////////////// 463 | // Handle normal character insertion 464 | typer.consoleInsert = function(keyCode){ 465 | // TODO: remove redundant indirection 466 | var char = String.fromCharCode(keyCode); 467 | var before = promptText.substring(0,column); 468 | var after = promptText.substring(column); 469 | promptText = before + char + after; 470 | moveColumn(1); 471 | restoreText = promptText; 472 | updatePromptDisplay(); 473 | }; 474 | 475 | //////////////////////////////////////////////////////////////////////// 476 | // Move to another column relative to this one 477 | // Negative means go back, positive means go forward. 478 | function moveColumn(n){ 479 | if (column + n >= 0 && column + n <= promptText.length){ 480 | column += n; 481 | return true; 482 | } else return false; 483 | }; 484 | 485 | function moveForward() { 486 | if(moveColumn(1)) { 487 | updatePromptDisplay(); 488 | return true; 489 | } 490 | return false; 491 | }; 492 | 493 | function moveBackward() { 494 | if(moveColumn(-1)) { 495 | updatePromptDisplay(); 496 | return true; 497 | } 498 | return false; 499 | }; 500 | 501 | function moveToStart() { 502 | if (moveColumn(-column)) 503 | updatePromptDisplay(); 504 | }; 505 | 506 | function moveToEnd() { 507 | if (moveColumn(promptText.length-column)) 508 | updatePromptDisplay(); 509 | }; 510 | 511 | function moveToNextWord() { 512 | while(column < promptText.length && 513 | !isCharAlphanumeric(promptText[column]) && 514 | moveForward()) { 515 | } 516 | while(column < promptText.length && 517 | isCharAlphanumeric(promptText[column]) && 518 | moveForward()) { 519 | } 520 | }; 521 | 522 | function moveToPreviousWord() { 523 | // Move backward until we find the first alphanumeric 524 | while(column -1 >= 0 && 525 | !isCharAlphanumeric(promptText[column-1]) && 526 | moveBackward()) { 527 | } 528 | // Move until we find the first non-alphanumeric 529 | while(column -1 >= 0 && 530 | isCharAlphanumeric(promptText[column-1]) && 531 | moveBackward()) { 532 | } 533 | }; 534 | 535 | function isCharAlphanumeric(charToTest) { 536 | if(typeof charToTest == 'string') { 537 | var code = charToTest.charCodeAt(); 538 | return (code >= 'A'.charCodeAt() && code <= 'Z'.charCodeAt()) || 539 | (code >= 'a'.charCodeAt() && code <= 'z'.charCodeAt()) || 540 | (code >= '0'.charCodeAt() && code <= '9'.charCodeAt()); 541 | } 542 | return false; 543 | }; 544 | 545 | function doNothing() {}; 546 | 547 | extern.promptText = function(text){ 548 | if (text) { 549 | promptText = text; 550 | if (column > promptText.length) 551 | column = promptText.length; 552 | updatePromptDisplay(); 553 | } 554 | return promptText; 555 | }; 556 | 557 | //////////////////////////////////////////////////////////////////////// 558 | // Update the prompt display 559 | function updatePromptDisplay(){ 560 | var line = promptText; 561 | var html = ''; 562 | if (column > 0 && line == ''){ 563 | // When we have an empty line just display a cursor. 564 | html = cursor; 565 | } else if (column == promptText.length){ 566 | // We're at the end of the line, so we need to display 567 | // the text *and* cursor. 568 | html = htmlEncode(line) + cursor; 569 | } else { 570 | // Grab the current character, if there is one, and 571 | // make it the current cursor. 572 | var before = line.substring(0, column); 573 | var current = line.substring(column,column+1); 574 | if (current){ 575 | current = 576 | '' + 577 | htmlEncode(current) + 578 | ''; 579 | } 580 | var after = line.substring(column+1); 581 | html = htmlEncode(before) + current + htmlEncode(after); 582 | } 583 | prompt.html(html); 584 | scrollToBottom(); 585 | }; 586 | 587 | // Simple HTML encoding 588 | // Simply replace '<', '>' and '&' 589 | // TODO: Use jQuery's .html() trick, or grab a proper, fast 590 | // HTML encoder. 591 | function htmlEncode(text){ 592 | return ( 593 | text.replace(/&/g,'&') 594 | .replace(/&]{10})/g,'$1­' + wbr) 598 | ); 599 | }; 600 | 601 | return extern; 602 | }; 603 | // Simple utility for printing messages 604 | $.fn.filledText = function(txt){ 605 | $(this).text(txt); 606 | $(this).html($(this).html().replace(/\n/g,'
    ')); 607 | return this; 608 | }; 609 | })(jQuery); 610 | -------------------------------------------------------------------------------- /public/javascripts/mouseapp_2.js: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2008 why the lucky stiff 3 | // 4 | // Permission is hereby granted, free of charge, to any person 5 | // obtaining a copy of this software and associated documentation 6 | // files (the "Software"), to deal in the Software without restriction, 7 | // including without limitation the rights to use, copy, modify, merge, 8 | // publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, 10 | // subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be 13 | // included in all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | // SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT 21 | // OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | // SOFTWARE. 24 | // 25 | var MouseApp = { 26 | Version: '0.12', 27 | CharCodes: { 28 | 0: ' ', 1: ' ', 9: ' ', 29 | 32: ' ', 34: '"', 38: '&', 30 | 60: '<', 62: '>', 127: '◊', 31 | 0x20AC: '€' 32 | }, 33 | KeyCodes: { 34 | Backspace: 8, Tab: 9, Enter: 13, Esc: 27, PageUp: 33, PageDown: 34, 35 | End: 35, Home: 36, Left: 37, Up: 38, Right: 39, Down: 40, Insert: 45, 36 | Delete: 46, F1: 112, F2: 113, F3: 114, F4: 115, F5: 116, F6: 117, 37 | F7: 118, F8: 119, F10: 121 38 | }, 39 | CodeKeys: {}, 40 | Modes: { 1: 'b', 2: 'u', 4: 'i', 8: 'strike' }, 41 | ModeIds: { r: 1, u: 2, i: 4, s: 8 }, 42 | Colors: ['black', 'blue', 'green', 43 | 'cyan', 'red', 'purple', 'brown', 44 | 'gray', 'dark_gray', 'lt_blue', 45 | 'lt_green', 'lt_cyan', 'lt_red', 46 | 'lt_purple', 'yellow', 'white'] 47 | } 48 | //some of these are patently false, because I need to get on a real keyboard-- not a macbook. 49 | if (navigator.userAgent.toLowerCase().indexOf('chrome') > -1){ 50 | MouseApp.KeyCodes = { 51 | Backspace: 8, Tab: 9, Enter: 13, Esc: 27, PageUp: 63276, PageDown: 63277, 52 | End: 63275, Home: 63273, Left: 37, Up: 38, Right: 39, Down: 40, Insert: 632325, 53 | Delete: 46, F1: 63236, F2: 63237, F3: 63238, F4: 63239, F5: 63240, F6: 63241, 54 | F7: 63242, F8: 63243, F10: 63244 55 | } 56 | 57 | } 58 | 59 | //even though I am tempted to combine chrome and opera into the same if statment, I will refrain for now 60 | // i dont have proper access to a proper keyboard and there might be a single difference between the two 61 | if (navigator.userAgent.indexOf("Opera") > -1){ 62 | MouseApp.KeyCodes = { 63 | Backspace: 8, Tab: 9, Enter: 13, Esc: 27, PageUp: 63276, PageDown: 63277, 64 | End: 63275, Home: 63273, Left: 37, Up: 38, Right: 39, Down: 40, Insert: 632325, 65 | Delete: 46, F1: 63236, F2: 63237, F3: 63238, F4: 63239, F5: 63240, F6: 63241, 66 | F7: 63242, F8: 63243, F10: 63244 67 | } 68 | 69 | } 70 | 71 | // i am going to comment this out, since this seems un needed. if it past december 15 2009 and this 72 | // is still commented it, please del me 73 | //if ( navigator.appVersion.indexOf('AppleWebKit') > 0 ) { 74 | // MouseApp.KeyCodes = { 75 | // Backspace: 8, Tab: 9, Enter: 13, Esc: 27, PageUp: 63276, PageDown: 63277, 76 | // End: 63275, Home: 63273, Left: 63234, Up: 63232, Right: 63235, Down: 63233, Insert: 632325, 77 | // Delete: 63272, F1: 63236, F2: 63237, F3: 63238, F4: 63239, F5: 63240, F6: 63241, 78 | // F7: 63242, F8: 63243, F10: 63244 79 | // };// 80 | //} 81 | for ( var k in MouseApp.KeyCodes ) { 82 | MouseApp.CodeKeys[MouseApp.KeyCodes[k]] = k; 83 | } 84 | 85 | MouseApp.isPrintable = function(ch) { 86 | return (ch >= 32); 87 | }; 88 | 89 | MouseApp.Base = function(){}; 90 | MouseApp.Base.prototype = { 91 | setOptions: function(options) { 92 | this.options = { 93 | columns: 72, rows: 24, indent: 2, 94 | title: 'MouseApp', 95 | blinkRate: 500, 96 | ps: '>', 97 | greeting:'%+r Terminal ready. %-r' 98 | } 99 | $.extend(this.options, options || {}); 100 | } 101 | } 102 | 103 | MouseApp.Manager = new Object(); 104 | $.extend(MouseApp.Manager, { 105 | observeTerm: function(term) { 106 | this.activeTerm = term; 107 | if ( this.observingKeyboard ) return; 108 | var mgr = this; 109 | if ( term.input ) { 110 | term.input.keypress(function(e) { mgr.onKeyPress(e) }); 111 | if (!window.opera) term.input.keydown(function(e) { mgr.onKeyDown(e) }); 112 | else window.setInterval(function(){term.input.focus()},1); 113 | } else { 114 | if (!window.opera) $(document).keydown(function(e) { mgr.onKeyDown(e) }); 115 | $(document).keypress(function(e) { mgr.onKeyPress(e) }); 116 | } 117 | this.observingKeyboard = true; 118 | }, 119 | 120 | onKeyDown: function(e) { 121 | e = (e) ? e : ((event) ? event : null); 122 | if ( e && MouseApp.CodeKeys[e.keyCode] ) { 123 | if ( window.event ) { 124 | this.sendKeyPress(e); 125 | } 126 | this.blockEvent(e); 127 | return false; 128 | } 129 | return true; 130 | }, 131 | 132 | onKeyPress: function(e) { 133 | if ( !window.opera && window.event && e.keyCode != 13 && e.keyCode != 8 ) { 134 | e.charCode = e.keyCode; e.keyCode = null; 135 | } 136 | if ( e.keyCode == 191 ) { /* FF 1.0.x sends this upsy quizy -- ignore */ 137 | return; 138 | } 139 | return this.sendKeyPress(e); 140 | }, 141 | 142 | sendKeyPress: function(e) { 143 | var term = MouseApp.Manager.activeTerm; 144 | term.cursorOff(); 145 | b = term.onKeyPress(e); 146 | term.cursorOn(); 147 | return b; 148 | }, 149 | 150 | blockEvent: function (e) { 151 | e.cancelBubble=true; 152 | if (window.event && !window.opera) e.keyCode=0; 153 | if (e.stopPropagation) e.stopPropagation(); 154 | if (e.preventDefault) e.preventDefault(); 155 | } 156 | }); 157 | 158 | /* Basic text window functionality */ 159 | MouseApp.Window = function(element, options) { 160 | this.element = $(element); 161 | this.setOptions(options); 162 | this.initWindow(); 163 | }; 164 | 165 | $.extend(MouseApp.Window.prototype, (new MouseApp.Base()), { 166 | initWindow: function() { 167 | var html = ''; 168 | for ( var i = 0; i < this.options.rows; i++ ) { 169 | html += "
     
    \n"; 170 | } 171 | this.element.html(html); 172 | this.typingOn(); 173 | if (this.options.input) { 174 | this.input = $(this.options.input); 175 | this.input.focus(); 176 | } 177 | MouseApp.Manager.observeTerm(this); 178 | this.clear(); 179 | this.cursorOn(); 180 | this.painting = true; 181 | this.element.css({visibility: 'visible'}); 182 | }, 183 | 184 | text: function() { 185 | var str = ""; 186 | for (var i = 0; i < this.screen.length; i++ ) { 187 | for (var j = 0; j < this.options.columns; j++ ) { 188 | var ch = this.screen[i][j]; 189 | if ( ch[0] != 0 ) { 190 | str += String.fromCharCode(ch[0]); 191 | } 192 | } 193 | } 194 | return str; 195 | }, 196 | 197 | clear: function() { 198 | this.rpos = 0; 199 | this.cpos = 0; 200 | this.screen = []; 201 | this.element.html(''); 202 | this.screen[0] = this.fillRow(this.options.columns, 0); 203 | this.paint(0); 204 | }, 205 | 206 | typingOn: function() { this.typing = true; }, 207 | typingOff: function() { this.typing = false; }, 208 | 209 | cursorOn: function() { 210 | if ( this.blinker ) { 211 | clearInterval( this.blinker ); 212 | } 213 | this.underblink = this.screen[this.rpos][this.cpos][1]; 214 | MouseApp.Manager.activeTerm.blink(); 215 | this.blinker = setInterval(function(){MouseApp.Manager.activeTerm.blink();}, this.options.blinkRate); 216 | this.cursor = true; 217 | }, 218 | 219 | cursorOff: function() { 220 | if ( this.blinker ) { 221 | clearInterval( this.blinker ); 222 | } 223 | if ( this.cursor ) { 224 | this.screen[this.rpos][this.cpos][1] = this.underblink; 225 | this.paint(this.rpos); 226 | this.cursor = false; 227 | } 228 | }, 229 | 230 | blink: function() { 231 | if ( this == MouseApp.Manager.activeTerm ) { 232 | var mode = this.screen[this.rpos][this.cpos][1]; 233 | this.screen[this.rpos][this.cpos][1] = ( mode & 1 ) ? mode & 4094 : mode | 1; 234 | this.paint(this.rpos); 235 | } 236 | }, 237 | 238 | fillRow: function(len, ch, mode) { 239 | ary = [] 240 | for (var i = 0; i < len; i++) { 241 | ary[i] = [ch, mode]; 242 | } 243 | return ary; 244 | }, 245 | 246 | paint: function(start, end) { 247 | if (!this.painting) return; 248 | 249 | if (!end) end = start; 250 | for (var row = start; row <= end && row < this.screen.length; row++) { 251 | var html = ''; 252 | var mode = 0; 253 | var fcolor = 0; 254 | var bcolor = 0; 255 | var spans = 0; 256 | for (var i = 0; i < this.options.columns; i++ ) { 257 | var c = this.screen[row][i][0]; 258 | var m = this.screen[row][i][1] & 15; // 4 mode bits 259 | var f = (this.screen[row][i][1] & (15 << 4)) >> 4; // 4 foreground bits 260 | var b = (this.screen[row][i][1] & (15 << 8)) >> 8; // 4 background bits 261 | if ( m != mode ) { 262 | if ( MouseApp.Modes[mode] ) html += ""; 263 | if ( MouseApp.Modes[m] ) html += "<" + MouseApp.Modes[m] + ">"; 264 | mode = m; 265 | } 266 | if ( ( f != fcolor && f == 0 ) || ( b != bcolor && b == 0 ) ) { 267 | for ( var s = 0; s < spans; s++ ) html += ""; 268 | fcolor = 0; bcolor = 0; 269 | } 270 | if ( f != fcolor ) { 271 | if ( MouseApp.Colors[f] ) { 272 | html += ""; 273 | spans++; 274 | } 275 | fcolor = f; 276 | } 277 | if ( b != bcolor ) { 278 | if ( MouseApp.Colors[b] ) html += ""; 279 | spans++; bcolor = b; 280 | } 281 | html += MouseApp.CharCodes[c] ? MouseApp.CharCodes[c] : String.fromCharCode(c); 282 | } 283 | if ( MouseApp.Modes[mode] ) html += ""; 284 | for ( var s = 0; s < spans; s++ ) html += ""; 285 | var new_id = this.element.attr('id') + '_' + row; 286 | if (!$('#' + new_id).get(0)) { 287 | this.element.append("
     
    "); 288 | this.scrollAllTheWayDown(); 289 | } 290 | $('#' + new_id).html(html); 291 | } 292 | }, 293 | 294 | onAfterKey: function() { 295 | this.scrollAllTheWayDown(); 296 | }, 297 | 298 | highlightLine: function(i) { 299 | if (i >= 0 && i < this.screen.length) 300 | { 301 | $("#" + this.element.attr('id') + "_" + i); 302 | } 303 | }, 304 | 305 | scrollToLine: function(i) { 306 | var p = this.element[0].parentNode; 307 | if ( p.scrollHeight > p.clientHeight ) { 308 | p.scrollTop = (p.scrollHeight - p.clientHeight); 309 | } 310 | }, 311 | 312 | scrollAllTheWayDown: function() { 313 | var p = this.element[0].parentNode; 314 | if ( p.scrollHeight > p.clientHeight ) { 315 | p.scrollTop = (p.scrollHeight - p.clientHeight); 316 | } 317 | }, 318 | 319 | putc: function(ch, mode) { 320 | if ( ch == 13 ) { 321 | return; 322 | } else if ( ch == 10 ) { 323 | this.screen[this.rpos][this.cpos] = [ch, mode]; 324 | this.advanceLine(); 325 | } else { 326 | this.screen[this.rpos][this.cpos] = [ch, mode]; 327 | this.paint(this.rpos); 328 | this.advance(); 329 | } 330 | }, 331 | 332 | zpad: function(n) { 333 | if (n < 10) n = "0" + n; 334 | return n; 335 | }, 336 | 337 | puts: function(str, mode) { 338 | if ( !str ) return; 339 | var p = this.painting; 340 | var r = this.rpos; 341 | this.painting = false; 342 | for ( var i = 0; i < str.length; i++ ) { 343 | this.insertc(str.charCodeAt(i), mode); 344 | } 345 | this.painting = p; 346 | this.paint(r, this.rpos); 347 | }, 348 | 349 | advance: function() { 350 | this.cpos++; 351 | if ( this.cpos >= this.options.columns ) { 352 | this.advanceLine(); 353 | } 354 | }, 355 | 356 | advanceLine: function() { 357 | this.cpos = 0; 358 | this.rpos++; 359 | this.ensureRow(this.rpos); 360 | this.paint(this.rpos, this.screen.length - 1); 361 | }, 362 | 363 | fwdc: function() { 364 | var r = this.rpos; 365 | var c = this.cpos; 366 | if ( c < this.options.columns - 1 ) { 367 | c++; 368 | } else if ( r < this.screen.length - 1 ) { 369 | r++; 370 | c = 0; 371 | } 372 | var ch = (c == 0 ? this.screen[r-1][this.options.columns-1] : this.screen[r][c-1]); 373 | if ( MouseApp.isPrintable(ch[0]) ) { 374 | this.rpos = r; 375 | this.cpos = c; 376 | } 377 | }, 378 | 379 | fwdLine: function() { 380 | if ( this.rpos >= this.screen.length - 1 ) return; 381 | this.rpos++; 382 | while ( this.cpos > 0 && !MouseApp.isPrintable(this.screen[this.rpos][this.cpos - 1][0]) ) { 383 | this.cpos--; 384 | } 385 | }, 386 | 387 | backc: function() { 388 | var r = this.rpos; 389 | var c = this.cpos; 390 | if ( c > 0 ) { 391 | c--; 392 | } else if ( r > 0 ) { 393 | c = this.options.columns - 1; 394 | r--; 395 | } 396 | if ( MouseApp.isPrintable(this.screen[r][c][0]) ) { 397 | this.rpos = r; 398 | this.cpos = c; 399 | return true; 400 | } 401 | return false; 402 | }, 403 | 404 | getTypingStart: function() { 405 | var c = this.cpos; 406 | if ( !MouseApp.isPrintable(this.screen[this.rpos][c][0]) ) { 407 | c--; 408 | } 409 | var pos = null; 410 | for ( var r = this.rpos; r >= 0; r-- ) { 411 | while ( c >= 0 ) { 412 | if ( !MouseApp.isPrintable(this.screen[r][c][0]) ) { 413 | return pos; 414 | } 415 | pos = [r, c]; 416 | c--; 417 | } 418 | c = this.options.columns - 1; 419 | } 420 | }, 421 | 422 | getTypingEnd: function(mod) { 423 | var c = this.cpos; 424 | if ( !MouseApp.isPrintable(this.screen[this.rpos][c][0]) ) { 425 | c--; 426 | } 427 | var pos = null; 428 | for ( var r = this.rpos; r < this.screen.length; r++ ) { 429 | while ( c < this.options.columns ) { 430 | if ( !this.screen[r] || !this.screen[r][c] || !MouseApp.isPrintable(this.screen[r][c][0]) ) { 431 | if (!mod) return pos; 432 | mod--; 433 | } 434 | pos = [r, c]; 435 | c++; 436 | } 437 | c = 0; 438 | } 439 | }, 440 | 441 | getTypingAt: function(start, end) { 442 | var r = start[0]; 443 | var c = start[1]; 444 | var str = ''; 445 | while ( r < end[0] || c <= end[1] ) { 446 | if ( c < this.options.columns ) { 447 | str += String.fromCharCode(this.screen[r][c][0]); 448 | c++; 449 | } else { 450 | c = 0; 451 | r++; 452 | } 453 | } 454 | return str; 455 | }, 456 | 457 | ensureRow: function(r) { 458 | if (!this.screen[r]) { 459 | this.screen[r] = this.fillRow(this.options.columns, 0); 460 | } 461 | }, 462 | 463 | insertc: function(ch, mode) { 464 | var r = this.rpos; var c = this.cpos; 465 | var end = this.getTypingEnd(+1); 466 | if (end) { 467 | var thisc = null; 468 | var lastc = this.screen[this.rpos][this.cpos]; 469 | while ( r < end[0] || c <= end[1] ) { 470 | if ( c < this.options.columns ) { 471 | thisc = this.screen[r][c]; 472 | this.screen[r][c] = lastc; 473 | lastc = thisc; 474 | c++; 475 | } else { 476 | c = 0; 477 | r++; 478 | this.ensureRow(r); 479 | } 480 | } 481 | this.paint(this.rpos, end[0]); 482 | } 483 | this.putc(ch, mode); 484 | }, 485 | 486 | delc: function() { 487 | /* end of line */ 488 | if ( MouseApp.isPrintable(this.screen[this.rpos][this.cpos][0]) ) { 489 | var end = this.getTypingEnd(); 490 | var thisc = null; 491 | var lastc = [0, 0]; 492 | while ( this.rpos < end[0] || this.cpos <= end[1] ) { 493 | if ( end[1] >= 0 ) { 494 | thisc = this.screen[end[0]][end[1]]; 495 | this.screen[end[0]][end[1]] = lastc; 496 | lastc = thisc; 497 | end[1]--; 498 | } else { 499 | end[1] = this.options.columns - 1; 500 | this.paint(end[0]); 501 | end[0]--; 502 | } 503 | } 504 | } 505 | }, 506 | 507 | backspace: function() { 508 | /* end of line */ 509 | if ( !MouseApp.isPrintable(this.screen[this.rpos][this.cpos][0]) ) { 510 | this.backc(); 511 | this.screen[this.rpos][this.cpos] = [0, 0]; 512 | } else { 513 | if ( this.backc() ) this.delc(); 514 | } 515 | }, 516 | 517 | backLine: function() { 518 | if ( this.rpos < 1 ) return; 519 | this.rpos--; 520 | while ( this.cpos > 0 && !MouseApp.isPrintable(this.screen[this.rpos][this.cpos - 1][0]) ) { 521 | this.cpos--; 522 | } 523 | }, 524 | 525 | onKeyPress: function(e) { 526 | var ch = e.keyCode; 527 | var key_name = MouseApp.CodeKeys[ch]; 528 | if (window.opera && !e.altKey && e.keyCode != 13 && e.keyCode != 8) key_name = null; 529 | ch = (e.which || e.charCode || e.keyCode); 530 | if (e.which) ch = e.which; 531 | if (!key_name) { key_name = String.fromCharCode(ch); } 532 | if (e.ctrlKey) { key_name = 'Ctrl' + key_name; } 533 | 534 | // alert([e.keyCode, e.which, key_name, this['onKey' + key_name]]); 535 | if (this.typing && this.onAnyKey) this.onAnyKey(key_name); 536 | if (key_name && this['onKey' + key_name]) { 537 | if (this.typing) this['onKey' + key_name](); 538 | MouseApp.Manager.blockEvent(e); 539 | if (this.typing && this.onAfterKey) this.onAfterKey(key_name, true); 540 | return false; 541 | } 542 | if (!e.ctrlKey) { 543 | if (MouseApp.isPrintable(ch)) { 544 | if (this.typing) this.insertc(ch, 0); 545 | MouseApp.Manager.blockEvent(e); 546 | if (this.typing && this.onAfterKey) this.onAfterKey(key_name, true); 547 | return false; 548 | } 549 | } 550 | if (this.typing && this.onAfterKey) this.onAfterKey(key_name, false); 551 | return true; 552 | }, 553 | onKeyHome: function() { 554 | var s = this.getTypingStart(); 555 | this.rpos = s[0]; this.cpos = s[1]; 556 | }, 557 | onKeyEnd: function() { 558 | var e = this.getTypingEnd(+1); 559 | this.rpos = e[0]; this.cpos = e[1]; 560 | }, 561 | onKeyInsert: function() { }, 562 | onKeyDelete: function() { this.delc(); }, 563 | onKeyUp: function() { this.backLine(); }, 564 | onKeyLeft: function() { this.backc(); }, 565 | onKeyRight: function() { this.fwdc(); }, 566 | onKeyDown: function() { this.fwdLine(); }, 567 | onKeyBackspace: function() { this.backspace(); }, 568 | onKeyEnter: function() { this.insertc(10, 0); }, 569 | onKeyTab: function() { 570 | this.insertc(32, 0); 571 | while (this.cpos % this.options.indent != 0) this.insertc(32, 0); 572 | } 573 | }); 574 | 575 | /* Terminal running moush */ 576 | MouseApp.Terminal = function(element, options) { 577 | this.element = $(element); 578 | this.setOptions(options); 579 | this.initWindow(); 580 | this.setup(); 581 | }; 582 | 583 | $.extend(MouseApp.Terminal.prototype, MouseApp.Window.prototype, { 584 | setup: function() { 585 | this.history = []; 586 | this.backupNum = this.historyNum = this.commandNum = 0; 587 | if (this.onStart) { 588 | this.onStart(); 589 | } else { 590 | this.write(this.options.greeting + "\n", true); 591 | this.prompt(); 592 | } 593 | }, 594 | 595 | prompt: function(ps, pt) { 596 | if (!ps) { 597 | ps = this.options.ps; pt = true; 598 | } 599 | this.write(ps, pt); 600 | this.putc(1, 0); 601 | this.typingOn(); 602 | }, 603 | 604 | getCommand: function() { 605 | var s = this.getTypingStart(); 606 | var e = this.getTypingEnd(); 607 | if (!s || !e) return; 608 | return this.getTypingAt(s, e); 609 | }, 610 | 611 | clearCommand: function() { 612 | var s = this.getTypingStart(); 613 | var e = this.getTypingEnd(); 614 | if (!s || !e) return; 615 | var r = s[0]; 616 | var c = s[1]; 617 | this.rpos = r; this.cpos = c; 618 | while ( r < e[0] || c <= e[1] ) { 619 | if ( c < this.options.columns ) { 620 | this.screen[r][c] = [0, 0]; 621 | c++; 622 | } else { 623 | c = 0; 624 | this.paint(r); 625 | r++; 626 | } 627 | } 628 | this.paint(r); 629 | }, 630 | 631 | write: function(str, pcodes) { 632 | var p = this.painting; 633 | var r = this.rpos; 634 | this.painting = false; 635 | var mode = 0; 636 | var today = new Date(); 637 | for ( var i = 0; i < str.length; i++ ) { 638 | if ( str.substr(i,1) == "\n" ) { 639 | this.advanceLine(); 640 | continue; 641 | } else if ( str.substr(i,1) == "\033" ) { 642 | if ( str.substr(i+1,2) == "[m" ) { 643 | mode = 0; 644 | i += 2; 645 | continue; 646 | } 647 | if ( str.substr(i+1,5) == "[0;0m" ) { 648 | mode = 0; 649 | i += 5; 650 | continue; 651 | } 652 | var colors = str.substr(i+1,7).match(/^\[(\d);(\d+)m/); 653 | if ( colors ) { 654 | var colCode = parseInt( colors[2] ); 655 | var color = colCode % 10; 656 | if ( colors[1] == '1' ) { 657 | color += 8; 658 | } 659 | if ( colCode / 10 == 4 ) { 660 | color = color << 4; 661 | } 662 | mode = (mode & 15) + color << 4; 663 | i += colors[0].length; 664 | continue; 665 | } 666 | } else if ( str.substr(i,1) == '%' && pcodes ) { 667 | var s2 = str.substr(i,2); 668 | switch ( s2 ) { 669 | case '%h': 670 | this.puts(this.options.host, mode); 671 | i++; 672 | continue; 673 | case '%l': 674 | this.puts(this.options.name, mode); 675 | i++; 676 | continue; 677 | case '%n': 678 | this.advanceLine(); 679 | i++; 680 | continue; 681 | case '%s': 682 | this.puts("moush", mode); 683 | i++; 684 | continue; 685 | case '%t': 686 | this.puts(this.zpad(today.getHours()) + ":" + this.zpad(today.getMinutes()) + ":" + 687 | this.zpad(today.getSeconds()), mode); 688 | i++; 689 | continue; 690 | case '%u': 691 | this.puts(this.options.user, mode); 692 | i++; 693 | continue; 694 | case '%v': 695 | this.puts(MouseApp.Version, mode); 696 | i++; 697 | continue; 698 | case '%!': 699 | this.puts(this.historyNum.toString(), mode); 700 | i++; 701 | continue; 702 | case '%#': 703 | this.puts(this.commandNum.toString(), mode); 704 | i++; 705 | continue; 706 | case '%+': 707 | var kind = str.substr(i+2, 1); 708 | if ( MouseApp.ModeIds[kind] ) { 709 | mode = mode | MouseApp.ModeIds[kind]; 710 | i += 2; 711 | continue; 712 | } 713 | break; 714 | case '%-': 715 | var kind = str.substr(i+2, 1); 716 | if ( MouseApp.ModeIds[kind] ) { 717 | mode = mode & ( 4095 - MouseApp.ModeIds[kind] ); 718 | i += 2; 719 | continue; 720 | } 721 | break; 722 | } 723 | } 724 | this.putc(str.charCodeAt(i), mode); 725 | } 726 | this.painting = p; 727 | this.paint(r, this.rpos); 728 | }, 729 | 730 | onKeyUp: function() { 731 | if ( this.backupNum == 0 ) return; 732 | if ( this.backupNum == this.historyNum ) { 733 | this.history[this.historyNum] = this.getCommand(); 734 | } 735 | this.clearCommand(); 736 | this.backupNum--; 737 | this.puts(this.history[this.backupNum]); 738 | }, 739 | onKeyDown: function() { 740 | if ( this.backupNum >= this.historyNum ) return; 741 | this.clearCommand(); 742 | this.backupNum++; 743 | this.puts(this.history[this.backupNum]); 744 | }, 745 | onKeyEnter: function() { 746 | var cmd = this.getCommand(); 747 | if (cmd) { 748 | this.history[this.historyNum] = cmd; 749 | this.backupNum = ++this.historyNum; 750 | } 751 | this.commandNum++; 752 | this.advanceLine(); 753 | if (cmd) { 754 | var str = this.onCommand(cmd); 755 | if (str) { 756 | if ( str.substr(str.length - 1, 1) != "\n" ) { 757 | str += "\n"; 758 | } 759 | this.write(str); 760 | } 761 | } 762 | this.prompt(); 763 | }, 764 | onCommand: function(line) { 765 | // this.puts("Echoing: " + line + "\n"); 766 | if ( line == "clear" ) { 767 | this.clear(); 768 | } else { 769 | return "\033[1;37m\033[0;44mYou typed:\033[m " + line; 770 | } 771 | } 772 | }); 773 | 774 | /* Notepad sort of editor */ 775 | MouseApp.Notepad = function(element, options) { 776 | this.element = $(element); 777 | this.setOptions(options); 778 | this.initWindow(); 779 | this.history = []; 780 | this.lineno = 0; 781 | }; 782 | 783 | $.extend(MouseApp.Notepad.prototype, MouseApp.Window.prototype, { 784 | csave: function() { 785 | if ( this.cpos_save ) { 786 | this.cpos = this.cpos_save; 787 | } else { 788 | this.cpos_save = this.cpos; 789 | } 790 | }, 791 | onKeyUp: function() { if ( this.rpos < 1 ) { return; } this.csave(); this.backLine(); }, 792 | onKeyDown: function() { if ( this.rpos < this.screen.length - 1 ) { this.csave(); this.fwdLine(); } }, 793 | onAfterKey: function(key, st) { 794 | if ( st && !(key == 'Up' || key == 'Down') ) { 795 | this.cpos_save = null; 796 | } 797 | }, 798 | insertc: function(ch, mode) { 799 | if (ch == 10) { 800 | this.element.append("
     
    "); 801 | this.screen.splice(this.rpos + 1, 0, this.fillRow(this.options.columns, 0)); 802 | var c = this.cpos; var c2 = 0; 803 | while (c < this.options.columns) 804 | { 805 | if (this.screen[this.rpos][c] == 0) break; 806 | this.screen[this.rpos + 1][c2] = this.screen[this.rpos][c]; 807 | this.screen[this.rpos][c] = [0, 0]; 808 | c++; c2++; 809 | } 810 | this.paint(this.rpos); 811 | if (MouseApp.isPrintable(this.screen[this.rpos][c])) 812 | { 813 | var r = this.rpos; var c = this.cpos; 814 | this.rpos += 1; this.cpos = c2; 815 | this.delc(); 816 | this.rpos = r; this.cpos = c; 817 | } 818 | this.putc(ch, mode); 819 | if (this.rpos == this.screen.length - 1) 820 | this.scrollAllTheWayDown(); 821 | } else { 822 | var c = this.cpos + 1; 823 | var lastc = this.screen[this.rpos][this.cpos]; 824 | this.putc(ch, mode); 825 | for ( var r = this.rpos; r < this.screen.length; r++ ) { 826 | while (c < this.options.columns) 827 | { 828 | var tmpc = this.screen[r][c]; 829 | if (lastc[0] == 0) 830 | break; 831 | this.screen[r][c] = lastc; 832 | lastc = tmpc; 833 | c++; 834 | } 835 | if (c < this.options.columns) { 836 | break; 837 | } 838 | c = 0; 839 | } 840 | } 841 | }, 842 | 843 | backc: function() { 844 | var r = this.rpos; 845 | var c = this.cpos - 1; 846 | for ( var r = this.rpos; r >= 0; r-- ) { 847 | while ( c >= 0 ) { 848 | this.rpos = r; 849 | this.cpos = c; 850 | if ( this.screen[r][c][0] != 0 ) { 851 | this.paint(r); 852 | return true; 853 | } 854 | this.screen[r][c] = [0, 0]; 855 | c--; 856 | } 857 | c = this.options.columns - 1; 858 | } 859 | return false; 860 | }, 861 | delc: function() { 862 | var c = this.cpos + 1; 863 | for ( var r = this.rpos; r < this.screen.length; r++ ) { 864 | while ( c < this.options.columns ) { 865 | if ( this.screen[r][c][0] != 0 ) { 866 | break; 867 | } 868 | c++; 869 | } 870 | if ( c < this.options.columns ) break; 871 | c = 0; 872 | } 873 | 874 | if (r >= this.screen.length) return; 875 | 876 | var r2 = this.rpos; 877 | var c2 = this.cpos; 878 | for ( var r2 = this.rpos; r2 < this.screen.length; r2++ ) { 879 | while (c2 < this.options.columns) 880 | { 881 | if (this.screen[r][c][0] == 0) 882 | break; 883 | this.screen[r2][c2] = this.screen[r][c]; 884 | c2++; 885 | c++; 886 | if (c >= this.options.columns) { 887 | r++; 888 | if (r >= this.options.rows) break; 889 | c = 0; 890 | } 891 | } 892 | if (c2 < this.options.columns) { 893 | while (c2 < this.options.columns) { 894 | this.screen[r2][c2] = [0, 0]; 895 | c2++; 896 | } 897 | break; 898 | } 899 | c2 = 0; 900 | } 901 | 902 | if (r != r2 && r < this.screen.length) 903 | { 904 | this.screen.splice(r, 1); 905 | $("#" + this.element.attr('id') + "_" + this.screen.length).remove(); 906 | } 907 | this.paint(this.rpos, this.screen.length); 908 | }, 909 | onKeyBackspace: function() { 910 | if (this.backc()) this.delc(); 911 | } 912 | 913 | }); 914 | -------------------------------------------------------------------------------- /public/javascripts/dragdrop.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) 2 | // (c) 2005-2008 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz) 3 | // 4 | // script.aculo.us is freely distributable under the terms of an MIT-style license. 5 | // For details, see the script.aculo.us web site: http://script.aculo.us/ 6 | 7 | if(Object.isUndefined(Effect)) 8 | throw("dragdrop.js requires including script.aculo.us' effects.js library"); 9 | 10 | var Droppables = { 11 | drops: [], 12 | 13 | remove: function(element) { 14 | this.drops = this.drops.reject(function(d) { return d.element==$(element) }); 15 | }, 16 | 17 | add: function(element) { 18 | element = $(element); 19 | var options = Object.extend({ 20 | greedy: true, 21 | hoverclass: null, 22 | tree: false 23 | }, arguments[1] || { }); 24 | 25 | // cache containers 26 | if(options.containment) { 27 | options._containers = []; 28 | var containment = options.containment; 29 | if(Object.isArray(containment)) { 30 | containment.each( function(c) { options._containers.push($(c)) }); 31 | } else { 32 | options._containers.push($(containment)); 33 | } 34 | } 35 | 36 | if(options.accept) options.accept = [options.accept].flatten(); 37 | 38 | Element.makePositioned(element); // fix IE 39 | options.element = element; 40 | 41 | this.drops.push(options); 42 | }, 43 | 44 | findDeepestChild: function(drops) { 45 | deepest = drops[0]; 46 | 47 | for (i = 1; i < drops.length; ++i) 48 | if (Element.isParent(drops[i].element, deepest.element)) 49 | deepest = drops[i]; 50 | 51 | return deepest; 52 | }, 53 | 54 | isContained: function(element, drop) { 55 | var containmentNode; 56 | if(drop.tree) { 57 | containmentNode = element.treeNode; 58 | } else { 59 | containmentNode = element.parentNode; 60 | } 61 | return drop._containers.detect(function(c) { return containmentNode == c }); 62 | }, 63 | 64 | isAffected: function(point, element, drop) { 65 | return ( 66 | (drop.element!=element) && 67 | ((!drop._containers) || 68 | this.isContained(element, drop)) && 69 | ((!drop.accept) || 70 | (Element.classNames(element).detect( 71 | function(v) { return drop.accept.include(v) } ) )) && 72 | Position.within(drop.element, point[0], point[1]) ); 73 | }, 74 | 75 | deactivate: function(drop) { 76 | if(drop.hoverclass) 77 | Element.removeClassName(drop.element, drop.hoverclass); 78 | this.last_active = null; 79 | }, 80 | 81 | activate: function(drop) { 82 | if(drop.hoverclass) 83 | Element.addClassName(drop.element, drop.hoverclass); 84 | this.last_active = drop; 85 | }, 86 | 87 | show: function(point, element) { 88 | if(!this.drops.length) return; 89 | var drop, affected = []; 90 | 91 | this.drops.each( function(drop) { 92 | if(Droppables.isAffected(point, element, drop)) 93 | affected.push(drop); 94 | }); 95 | 96 | if(affected.length>0) 97 | drop = Droppables.findDeepestChild(affected); 98 | 99 | if(this.last_active && this.last_active != drop) this.deactivate(this.last_active); 100 | if (drop) { 101 | Position.within(drop.element, point[0], point[1]); 102 | if(drop.onHover) 103 | drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element)); 104 | 105 | if (drop != this.last_active) Droppables.activate(drop); 106 | } 107 | }, 108 | 109 | fire: function(event, element) { 110 | if(!this.last_active) return; 111 | Position.prepare(); 112 | 113 | if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active)) 114 | if (this.last_active.onDrop) { 115 | this.last_active.onDrop(element, this.last_active.element, event); 116 | return true; 117 | } 118 | }, 119 | 120 | reset: function() { 121 | if(this.last_active) 122 | this.deactivate(this.last_active); 123 | } 124 | }; 125 | 126 | var Draggables = { 127 | drags: [], 128 | observers: [], 129 | 130 | register: function(draggable) { 131 | if(this.drags.length == 0) { 132 | this.eventMouseUp = this.endDrag.bindAsEventListener(this); 133 | this.eventMouseMove = this.updateDrag.bindAsEventListener(this); 134 | this.eventKeypress = this.keyPress.bindAsEventListener(this); 135 | 136 | Event.observe(document, "mouseup", this.eventMouseUp); 137 | Event.observe(document, "mousemove", this.eventMouseMove); 138 | Event.observe(document, "keypress", this.eventKeypress); 139 | } 140 | this.drags.push(draggable); 141 | }, 142 | 143 | unregister: function(draggable) { 144 | this.drags = this.drags.reject(function(d) { return d==draggable }); 145 | if(this.drags.length == 0) { 146 | Event.stopObserving(document, "mouseup", this.eventMouseUp); 147 | Event.stopObserving(document, "mousemove", this.eventMouseMove); 148 | Event.stopObserving(document, "keypress", this.eventKeypress); 149 | } 150 | }, 151 | 152 | activate: function(draggable) { 153 | if(draggable.options.delay) { 154 | this._timeout = setTimeout(function() { 155 | Draggables._timeout = null; 156 | window.focus(); 157 | Draggables.activeDraggable = draggable; 158 | }.bind(this), draggable.options.delay); 159 | } else { 160 | window.focus(); // allows keypress events if window isn't currently focused, fails for Safari 161 | this.activeDraggable = draggable; 162 | } 163 | }, 164 | 165 | deactivate: function() { 166 | this.activeDraggable = null; 167 | }, 168 | 169 | updateDrag: function(event) { 170 | if(!this.activeDraggable) return; 171 | var pointer = [Event.pointerX(event), Event.pointerY(event)]; 172 | // Mozilla-based browsers fire successive mousemove events with 173 | // the same coordinates, prevent needless redrawing (moz bug?) 174 | if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return; 175 | this._lastPointer = pointer; 176 | 177 | this.activeDraggable.updateDrag(event, pointer); 178 | }, 179 | 180 | endDrag: function(event) { 181 | if(this._timeout) { 182 | clearTimeout(this._timeout); 183 | this._timeout = null; 184 | } 185 | if(!this.activeDraggable) return; 186 | this._lastPointer = null; 187 | this.activeDraggable.endDrag(event); 188 | this.activeDraggable = null; 189 | }, 190 | 191 | keyPress: function(event) { 192 | if(this.activeDraggable) 193 | this.activeDraggable.keyPress(event); 194 | }, 195 | 196 | addObserver: function(observer) { 197 | this.observers.push(observer); 198 | this._cacheObserverCallbacks(); 199 | }, 200 | 201 | removeObserver: function(element) { // element instead of observer fixes mem leaks 202 | this.observers = this.observers.reject( function(o) { return o.element==element }); 203 | this._cacheObserverCallbacks(); 204 | }, 205 | 206 | notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag' 207 | if(this[eventName+'Count'] > 0) 208 | this.observers.each( function(o) { 209 | if(o[eventName]) o[eventName](eventName, draggable, event); 210 | }); 211 | if(draggable.options[eventName]) draggable.options[eventName](draggable, event); 212 | }, 213 | 214 | _cacheObserverCallbacks: function() { 215 | ['onStart','onEnd','onDrag'].each( function(eventName) { 216 | Draggables[eventName+'Count'] = Draggables.observers.select( 217 | function(o) { return o[eventName]; } 218 | ).length; 219 | }); 220 | } 221 | }; 222 | 223 | /*--------------------------------------------------------------------------*/ 224 | 225 | var Draggable = Class.create({ 226 | initialize: function(element) { 227 | var defaults = { 228 | handle: false, 229 | reverteffect: function(element, top_offset, left_offset) { 230 | var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02; 231 | new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur, 232 | queue: {scope:'_draggable', position:'end'} 233 | }); 234 | }, 235 | endeffect: function(element) { 236 | var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0; 237 | new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity, 238 | queue: {scope:'_draggable', position:'end'}, 239 | afterFinish: function(){ 240 | Draggable._dragging[element] = false 241 | } 242 | }); 243 | }, 244 | zindex: 1000, 245 | revert: false, 246 | quiet: false, 247 | scroll: false, 248 | scrollSensitivity: 20, 249 | scrollSpeed: 15, 250 | snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] } 251 | delay: 0 252 | }; 253 | 254 | if(!arguments[1] || Object.isUndefined(arguments[1].endeffect)) 255 | Object.extend(defaults, { 256 | starteffect: function(element) { 257 | element._opacity = Element.getOpacity(element); 258 | Draggable._dragging[element] = true; 259 | new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7}); 260 | } 261 | }); 262 | 263 | var options = Object.extend(defaults, arguments[1] || { }); 264 | 265 | this.element = $(element); 266 | 267 | if(options.handle && Object.isString(options.handle)) 268 | this.handle = this.element.down('.'+options.handle, 0); 269 | 270 | if(!this.handle) this.handle = $(options.handle); 271 | if(!this.handle) this.handle = this.element; 272 | 273 | if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) { 274 | options.scroll = $(options.scroll); 275 | this._isScrollChild = Element.childOf(this.element, options.scroll); 276 | } 277 | 278 | Element.makePositioned(this.element); // fix IE 279 | 280 | this.options = options; 281 | this.dragging = false; 282 | 283 | this.eventMouseDown = this.initDrag.bindAsEventListener(this); 284 | Event.observe(this.handle, "mousedown", this.eventMouseDown); 285 | 286 | Draggables.register(this); 287 | }, 288 | 289 | destroy: function() { 290 | Event.stopObserving(this.handle, "mousedown", this.eventMouseDown); 291 | Draggables.unregister(this); 292 | }, 293 | 294 | currentDelta: function() { 295 | return([ 296 | parseInt(Element.getStyle(this.element,'left') || '0'), 297 | parseInt(Element.getStyle(this.element,'top') || '0')]); 298 | }, 299 | 300 | initDrag: function(event) { 301 | if(!Object.isUndefined(Draggable._dragging[this.element]) && 302 | Draggable._dragging[this.element]) return; 303 | if(Event.isLeftClick(event)) { 304 | // abort on form elements, fixes a Firefox issue 305 | var src = Event.element(event); 306 | if((tag_name = src.tagName.toUpperCase()) && ( 307 | tag_name=='INPUT' || 308 | tag_name=='SELECT' || 309 | tag_name=='OPTION' || 310 | tag_name=='BUTTON' || 311 | tag_name=='TEXTAREA')) return; 312 | 313 | var pointer = [Event.pointerX(event), Event.pointerY(event)]; 314 | var pos = Position.cumulativeOffset(this.element); 315 | this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) }); 316 | 317 | Draggables.activate(this); 318 | Event.stop(event); 319 | } 320 | }, 321 | 322 | startDrag: function(event) { 323 | this.dragging = true; 324 | if(!this.delta) 325 | this.delta = this.currentDelta(); 326 | 327 | if(this.options.zindex) { 328 | this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0); 329 | this.element.style.zIndex = this.options.zindex; 330 | } 331 | 332 | if(this.options.ghosting) { 333 | this._clone = this.element.cloneNode(true); 334 | this._originallyAbsolute = (this.element.getStyle('position') == 'absolute'); 335 | if (!this._originallyAbsolute) 336 | Position.absolutize(this.element); 337 | this.element.parentNode.insertBefore(this._clone, this.element); 338 | } 339 | 340 | if(this.options.scroll) { 341 | if (this.options.scroll == window) { 342 | var where = this._getWindowScroll(this.options.scroll); 343 | this.originalScrollLeft = where.left; 344 | this.originalScrollTop = where.top; 345 | } else { 346 | this.originalScrollLeft = this.options.scroll.scrollLeft; 347 | this.originalScrollTop = this.options.scroll.scrollTop; 348 | } 349 | } 350 | 351 | Draggables.notify('onStart', this, event); 352 | 353 | if(this.options.starteffect) this.options.starteffect(this.element); 354 | }, 355 | 356 | updateDrag: function(event, pointer) { 357 | if(!this.dragging) this.startDrag(event); 358 | 359 | if(!this.options.quiet){ 360 | Position.prepare(); 361 | Droppables.show(pointer, this.element); 362 | } 363 | 364 | Draggables.notify('onDrag', this, event); 365 | 366 | this.draw(pointer); 367 | if(this.options.change) this.options.change(this); 368 | 369 | if(this.options.scroll) { 370 | this.stopScrolling(); 371 | 372 | var p; 373 | if (this.options.scroll == window) { 374 | with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; } 375 | } else { 376 | p = Position.page(this.options.scroll); 377 | p[0] += this.options.scroll.scrollLeft + Position.deltaX; 378 | p[1] += this.options.scroll.scrollTop + Position.deltaY; 379 | p.push(p[0]+this.options.scroll.offsetWidth); 380 | p.push(p[1]+this.options.scroll.offsetHeight); 381 | } 382 | var speed = [0,0]; 383 | if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity); 384 | if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity); 385 | if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity); 386 | if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity); 387 | this.startScrolling(speed); 388 | } 389 | 390 | // fix AppleWebKit rendering 391 | if(Prototype.Browser.WebKit) window.scrollBy(0,0); 392 | 393 | Event.stop(event); 394 | }, 395 | 396 | finishDrag: function(event, success) { 397 | this.dragging = false; 398 | 399 | if(this.options.quiet){ 400 | Position.prepare(); 401 | var pointer = [Event.pointerX(event), Event.pointerY(event)]; 402 | Droppables.show(pointer, this.element); 403 | } 404 | 405 | if(this.options.ghosting) { 406 | if (!this._originallyAbsolute) 407 | Position.relativize(this.element); 408 | delete this._originallyAbsolute; 409 | Element.remove(this._clone); 410 | this._clone = null; 411 | } 412 | 413 | var dropped = false; 414 | if(success) { 415 | dropped = Droppables.fire(event, this.element); 416 | if (!dropped) dropped = false; 417 | } 418 | if(dropped && this.options.onDropped) this.options.onDropped(this.element); 419 | Draggables.notify('onEnd', this, event); 420 | 421 | var revert = this.options.revert; 422 | if(revert && Object.isFunction(revert)) revert = revert(this.element); 423 | 424 | var d = this.currentDelta(); 425 | if(revert && this.options.reverteffect) { 426 | if (dropped == 0 || revert != 'failure') 427 | this.options.reverteffect(this.element, 428 | d[1]-this.delta[1], d[0]-this.delta[0]); 429 | } else { 430 | this.delta = d; 431 | } 432 | 433 | if(this.options.zindex) 434 | this.element.style.zIndex = this.originalZ; 435 | 436 | if(this.options.endeffect) 437 | this.options.endeffect(this.element); 438 | 439 | Draggables.deactivate(this); 440 | Droppables.reset(); 441 | }, 442 | 443 | keyPress: function(event) { 444 | if(event.keyCode!=Event.KEY_ESC) return; 445 | this.finishDrag(event, false); 446 | Event.stop(event); 447 | }, 448 | 449 | endDrag: function(event) { 450 | if(!this.dragging) return; 451 | this.stopScrolling(); 452 | this.finishDrag(event, true); 453 | Event.stop(event); 454 | }, 455 | 456 | draw: function(point) { 457 | var pos = Position.cumulativeOffset(this.element); 458 | if(this.options.ghosting) { 459 | var r = Position.realOffset(this.element); 460 | pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY; 461 | } 462 | 463 | var d = this.currentDelta(); 464 | pos[0] -= d[0]; pos[1] -= d[1]; 465 | 466 | if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) { 467 | pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft; 468 | pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop; 469 | } 470 | 471 | var p = [0,1].map(function(i){ 472 | return (point[i]-pos[i]-this.offset[i]) 473 | }.bind(this)); 474 | 475 | if(this.options.snap) { 476 | if(Object.isFunction(this.options.snap)) { 477 | p = this.options.snap(p[0],p[1],this); 478 | } else { 479 | if(Object.isArray(this.options.snap)) { 480 | p = p.map( function(v, i) { 481 | return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this)); 482 | } else { 483 | p = p.map( function(v) { 484 | return (v/this.options.snap).round()*this.options.snap }.bind(this)); 485 | } 486 | }} 487 | 488 | var style = this.element.style; 489 | if((!this.options.constraint) || (this.options.constraint=='horizontal')) 490 | style.left = p[0] + "px"; 491 | if((!this.options.constraint) || (this.options.constraint=='vertical')) 492 | style.top = p[1] + "px"; 493 | 494 | if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering 495 | }, 496 | 497 | stopScrolling: function() { 498 | if(this.scrollInterval) { 499 | clearInterval(this.scrollInterval); 500 | this.scrollInterval = null; 501 | Draggables._lastScrollPointer = null; 502 | } 503 | }, 504 | 505 | startScrolling: function(speed) { 506 | if(!(speed[0] || speed[1])) return; 507 | this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed]; 508 | this.lastScrolled = new Date(); 509 | this.scrollInterval = setInterval(this.scroll.bind(this), 10); 510 | }, 511 | 512 | scroll: function() { 513 | var current = new Date(); 514 | var delta = current - this.lastScrolled; 515 | this.lastScrolled = current; 516 | if(this.options.scroll == window) { 517 | with (this._getWindowScroll(this.options.scroll)) { 518 | if (this.scrollSpeed[0] || this.scrollSpeed[1]) { 519 | var d = delta / 1000; 520 | this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] ); 521 | } 522 | } 523 | } else { 524 | this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000; 525 | this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000; 526 | } 527 | 528 | Position.prepare(); 529 | Droppables.show(Draggables._lastPointer, this.element); 530 | Draggables.notify('onDrag', this); 531 | if (this._isScrollChild) { 532 | Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer); 533 | Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000; 534 | Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000; 535 | if (Draggables._lastScrollPointer[0] < 0) 536 | Draggables._lastScrollPointer[0] = 0; 537 | if (Draggables._lastScrollPointer[1] < 0) 538 | Draggables._lastScrollPointer[1] = 0; 539 | this.draw(Draggables._lastScrollPointer); 540 | } 541 | 542 | if(this.options.change) this.options.change(this); 543 | }, 544 | 545 | _getWindowScroll: function(w) { 546 | var T, L, W, H; 547 | with (w.document) { 548 | if (w.document.documentElement && documentElement.scrollTop) { 549 | T = documentElement.scrollTop; 550 | L = documentElement.scrollLeft; 551 | } else if (w.document.body) { 552 | T = body.scrollTop; 553 | L = body.scrollLeft; 554 | } 555 | if (w.innerWidth) { 556 | W = w.innerWidth; 557 | H = w.innerHeight; 558 | } else if (w.document.documentElement && documentElement.clientWidth) { 559 | W = documentElement.clientWidth; 560 | H = documentElement.clientHeight; 561 | } else { 562 | W = body.offsetWidth; 563 | H = body.offsetHeight; 564 | } 565 | } 566 | return { top: T, left: L, width: W, height: H }; 567 | } 568 | }); 569 | 570 | Draggable._dragging = { }; 571 | 572 | /*--------------------------------------------------------------------------*/ 573 | 574 | var SortableObserver = Class.create({ 575 | initialize: function(element, observer) { 576 | this.element = $(element); 577 | this.observer = observer; 578 | this.lastValue = Sortable.serialize(this.element); 579 | }, 580 | 581 | onStart: function() { 582 | this.lastValue = Sortable.serialize(this.element); 583 | }, 584 | 585 | onEnd: function() { 586 | Sortable.unmark(); 587 | if(this.lastValue != Sortable.serialize(this.element)) 588 | this.observer(this.element) 589 | } 590 | }); 591 | 592 | var Sortable = { 593 | SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/, 594 | 595 | sortables: { }, 596 | 597 | _findRootElement: function(element) { 598 | while (element.tagName.toUpperCase() != "BODY") { 599 | if(element.id && Sortable.sortables[element.id]) return element; 600 | element = element.parentNode; 601 | } 602 | }, 603 | 604 | options: function(element) { 605 | element = Sortable._findRootElement($(element)); 606 | if(!element) return; 607 | return Sortable.sortables[element.id]; 608 | }, 609 | 610 | destroy: function(element){ 611 | element = $(element); 612 | var s = Sortable.sortables[element.id]; 613 | 614 | if(s) { 615 | Draggables.removeObserver(s.element); 616 | s.droppables.each(function(d){ Droppables.remove(d) }); 617 | s.draggables.invoke('destroy'); 618 | 619 | delete Sortable.sortables[s.element.id]; 620 | } 621 | }, 622 | 623 | create: function(element) { 624 | element = $(element); 625 | var options = Object.extend({ 626 | element: element, 627 | tag: 'li', // assumes li children, override with tag: 'tagname' 628 | dropOnEmpty: false, 629 | tree: false, 630 | treeTag: 'ul', 631 | overlap: 'vertical', // one of 'vertical', 'horizontal' 632 | constraint: 'vertical', // one of 'vertical', 'horizontal', false 633 | containment: element, // also takes array of elements (or id's); or false 634 | handle: false, // or a CSS class 635 | only: false, 636 | delay: 0, 637 | hoverclass: null, 638 | ghosting: false, 639 | quiet: false, 640 | scroll: false, 641 | scrollSensitivity: 20, 642 | scrollSpeed: 15, 643 | format: this.SERIALIZE_RULE, 644 | 645 | // these take arrays of elements or ids and can be 646 | // used for better initialization performance 647 | elements: false, 648 | handles: false, 649 | 650 | onChange: Prototype.emptyFunction, 651 | onUpdate: Prototype.emptyFunction 652 | }, arguments[1] || { }); 653 | 654 | // clear any old sortable with same element 655 | this.destroy(element); 656 | 657 | // build options for the draggables 658 | var options_for_draggable = { 659 | revert: true, 660 | quiet: options.quiet, 661 | scroll: options.scroll, 662 | scrollSpeed: options.scrollSpeed, 663 | scrollSensitivity: options.scrollSensitivity, 664 | delay: options.delay, 665 | ghosting: options.ghosting, 666 | constraint: options.constraint, 667 | handle: options.handle }; 668 | 669 | if(options.starteffect) 670 | options_for_draggable.starteffect = options.starteffect; 671 | 672 | if(options.reverteffect) 673 | options_for_draggable.reverteffect = options.reverteffect; 674 | else 675 | if(options.ghosting) options_for_draggable.reverteffect = function(element) { 676 | element.style.top = 0; 677 | element.style.left = 0; 678 | }; 679 | 680 | if(options.endeffect) 681 | options_for_draggable.endeffect = options.endeffect; 682 | 683 | if(options.zindex) 684 | options_for_draggable.zindex = options.zindex; 685 | 686 | // build options for the droppables 687 | var options_for_droppable = { 688 | overlap: options.overlap, 689 | containment: options.containment, 690 | tree: options.tree, 691 | hoverclass: options.hoverclass, 692 | onHover: Sortable.onHover 693 | }; 694 | 695 | var options_for_tree = { 696 | onHover: Sortable.onEmptyHover, 697 | overlap: options.overlap, 698 | containment: options.containment, 699 | hoverclass: options.hoverclass 700 | }; 701 | 702 | // fix for gecko engine 703 | Element.cleanWhitespace(element); 704 | 705 | options.draggables = []; 706 | options.droppables = []; 707 | 708 | // drop on empty handling 709 | if(options.dropOnEmpty || options.tree) { 710 | Droppables.add(element, options_for_tree); 711 | options.droppables.push(element); 712 | } 713 | 714 | (options.elements || this.findElements(element, options) || []).each( function(e,i) { 715 | var handle = options.handles ? $(options.handles[i]) : 716 | (options.handle ? $(e).select('.' + options.handle)[0] : e); 717 | options.draggables.push( 718 | new Draggable(e, Object.extend(options_for_draggable, { handle: handle }))); 719 | Droppables.add(e, options_for_droppable); 720 | if(options.tree) e.treeNode = element; 721 | options.droppables.push(e); 722 | }); 723 | 724 | if(options.tree) { 725 | (Sortable.findTreeElements(element, options) || []).each( function(e) { 726 | Droppables.add(e, options_for_tree); 727 | e.treeNode = element; 728 | options.droppables.push(e); 729 | }); 730 | } 731 | 732 | // keep reference 733 | this.sortables[element.id] = options; 734 | 735 | // for onupdate 736 | Draggables.addObserver(new SortableObserver(element, options.onUpdate)); 737 | 738 | }, 739 | 740 | // return all suitable-for-sortable elements in a guaranteed order 741 | findElements: function(element, options) { 742 | return Element.findChildren( 743 | element, options.only, options.tree ? true : false, options.tag); 744 | }, 745 | 746 | findTreeElements: function(element, options) { 747 | return Element.findChildren( 748 | element, options.only, options.tree ? true : false, options.treeTag); 749 | }, 750 | 751 | onHover: function(element, dropon, overlap) { 752 | if(Element.isParent(dropon, element)) return; 753 | 754 | if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) { 755 | return; 756 | } else if(overlap>0.5) { 757 | Sortable.mark(dropon, 'before'); 758 | if(dropon.previousSibling != element) { 759 | var oldParentNode = element.parentNode; 760 | element.style.visibility = "hidden"; // fix gecko rendering 761 | dropon.parentNode.insertBefore(element, dropon); 762 | if(dropon.parentNode!=oldParentNode) 763 | Sortable.options(oldParentNode).onChange(element); 764 | Sortable.options(dropon.parentNode).onChange(element); 765 | } 766 | } else { 767 | Sortable.mark(dropon, 'after'); 768 | var nextElement = dropon.nextSibling || null; 769 | if(nextElement != element) { 770 | var oldParentNode = element.parentNode; 771 | element.style.visibility = "hidden"; // fix gecko rendering 772 | dropon.parentNode.insertBefore(element, nextElement); 773 | if(dropon.parentNode!=oldParentNode) 774 | Sortable.options(oldParentNode).onChange(element); 775 | Sortable.options(dropon.parentNode).onChange(element); 776 | } 777 | } 778 | }, 779 | 780 | onEmptyHover: function(element, dropon, overlap) { 781 | var oldParentNode = element.parentNode; 782 | var droponOptions = Sortable.options(dropon); 783 | 784 | if(!Element.isParent(dropon, element)) { 785 | var index; 786 | 787 | var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only}); 788 | var child = null; 789 | 790 | if(children) { 791 | var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap); 792 | 793 | for (index = 0; index < children.length; index += 1) { 794 | if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) { 795 | offset -= Element.offsetSize (children[index], droponOptions.overlap); 796 | } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) { 797 | child = index + 1 < children.length ? children[index + 1] : null; 798 | break; 799 | } else { 800 | child = children[index]; 801 | break; 802 | } 803 | } 804 | } 805 | 806 | dropon.insertBefore(element, child); 807 | 808 | Sortable.options(oldParentNode).onChange(element); 809 | droponOptions.onChange(element); 810 | } 811 | }, 812 | 813 | unmark: function() { 814 | if(Sortable._marker) Sortable._marker.hide(); 815 | }, 816 | 817 | mark: function(dropon, position) { 818 | // mark on ghosting only 819 | var sortable = Sortable.options(dropon.parentNode); 820 | if(sortable && !sortable.ghosting) return; 821 | 822 | if(!Sortable._marker) { 823 | Sortable._marker = 824 | ($('dropmarker') || Element.extend(document.createElement('DIV'))). 825 | hide().addClassName('dropmarker').setStyle({position:'absolute'}); 826 | document.getElementsByTagName("body").item(0).appendChild(Sortable._marker); 827 | } 828 | var offsets = Position.cumulativeOffset(dropon); 829 | Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'}); 830 | 831 | if(position=='after') 832 | if(sortable.overlap == 'horizontal') 833 | Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'}); 834 | else 835 | Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'}); 836 | 837 | Sortable._marker.show(); 838 | }, 839 | 840 | _tree: function(element, options, parent) { 841 | var children = Sortable.findElements(element, options) || []; 842 | 843 | for (var i = 0; i < children.length; ++i) { 844 | var match = children[i].id.match(options.format); 845 | 846 | if (!match) continue; 847 | 848 | var child = { 849 | id: encodeURIComponent(match ? match[1] : null), 850 | element: element, 851 | parent: parent, 852 | children: [], 853 | position: parent.children.length, 854 | container: $(children[i]).down(options.treeTag) 855 | }; 856 | 857 | /* Get the element containing the children and recurse over it */ 858 | if (child.container) 859 | this._tree(child.container, options, child); 860 | 861 | parent.children.push (child); 862 | } 863 | 864 | return parent; 865 | }, 866 | 867 | tree: function(element) { 868 | element = $(element); 869 | var sortableOptions = this.options(element); 870 | var options = Object.extend({ 871 | tag: sortableOptions.tag, 872 | treeTag: sortableOptions.treeTag, 873 | only: sortableOptions.only, 874 | name: element.id, 875 | format: sortableOptions.format 876 | }, arguments[1] || { }); 877 | 878 | var root = { 879 | id: null, 880 | parent: null, 881 | children: [], 882 | container: element, 883 | position: 0 884 | }; 885 | 886 | return Sortable._tree(element, options, root); 887 | }, 888 | 889 | /* Construct a [i] index for a particular node */ 890 | _constructIndex: function(node) { 891 | var index = ''; 892 | do { 893 | if (node.id) index = '[' + node.position + ']' + index; 894 | } while ((node = node.parent) != null); 895 | return index; 896 | }, 897 | 898 | sequence: function(element) { 899 | element = $(element); 900 | var options = Object.extend(this.options(element), arguments[1] || { }); 901 | 902 | return $(this.findElements(element, options) || []).map( function(item) { 903 | return item.id.match(options.format) ? item.id.match(options.format)[1] : ''; 904 | }); 905 | }, 906 | 907 | setSequence: function(element, new_sequence) { 908 | element = $(element); 909 | var options = Object.extend(this.options(element), arguments[2] || { }); 910 | 911 | var nodeMap = { }; 912 | this.findElements(element, options).each( function(n) { 913 | if (n.id.match(options.format)) 914 | nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode]; 915 | n.parentNode.removeChild(n); 916 | }); 917 | 918 | new_sequence.each(function(ident) { 919 | var n = nodeMap[ident]; 920 | if (n) { 921 | n[1].appendChild(n[0]); 922 | delete nodeMap[ident]; 923 | } 924 | }); 925 | }, 926 | 927 | serialize: function(element) { 928 | element = $(element); 929 | var options = Object.extend(Sortable.options(element), arguments[1] || { }); 930 | var name = encodeURIComponent( 931 | (arguments[1] && arguments[1].name) ? arguments[1].name : element.id); 932 | 933 | if (options.tree) { 934 | return Sortable.tree(element, arguments[1]).children.map( function (item) { 935 | return [name + Sortable._constructIndex(item) + "[id]=" + 936 | encodeURIComponent(item.id)].concat(item.children.map(arguments.callee)); 937 | }).flatten().join('&'); 938 | } else { 939 | return Sortable.sequence(element, arguments[1]).map( function(item) { 940 | return name + "[]=" + encodeURIComponent(item); 941 | }).join('&'); 942 | } 943 | } 944 | }; 945 | 946 | // Returns true if child is contained within element 947 | Element.isParent = function(child, element) { 948 | if (!child.parentNode || child == element) return false; 949 | if (child.parentNode == element) return true; 950 | return Element.isParent(child.parentNode, element); 951 | }; 952 | 953 | Element.findChildren = function(element, only, recursive, tagName) { 954 | if(!element.hasChildNodes()) return null; 955 | tagName = tagName.toUpperCase(); 956 | if(only) only = [only].flatten(); 957 | var elements = []; 958 | $A(element.childNodes).each( function(e) { 959 | if(e.tagName && e.tagName.toUpperCase()==tagName && 960 | (!only || (Element.classNames(e).detect(function(v) { return only.include(v) })))) 961 | elements.push(e); 962 | if(recursive) { 963 | var grandchildren = Element.findChildren(e, only, recursive, tagName); 964 | if(grandchildren) elements.push(grandchildren); 965 | } 966 | }); 967 | 968 | return (elements.length>0 ? elements.flatten() : []); 969 | }; 970 | 971 | Element.offsetSize = function (element, type) { 972 | return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')]; 973 | }; --------------------------------------------------------------------------------