From 97fd546a3301f66229bc65ba5cb3c7fba3fe9327 Mon Sep 17 00:00:00 2001 From: Maxwell Swadling Date: Sat, 7 May 2011 19:34:31 +1000 Subject: [PATCH 01/55] Initial project. Signed-off-by: Maxwell Swadling --- .gitignore | 5 + Gemfile | 31 + Gemfile.lock | 73 + LICENSE | 661 +++ README | 278 + Rakefile | 7 + app/controllers/application_controller.rb | 3 + app/helpers/application_helper.rb | 2 + app/views/layouts/application.html.erb | 14 + config.ru | 4 + config/application.rb | 42 + config/boot.rb | 6 + config/database.yml | 22 + config/environment.rb | 5 + config/environments/development.rb | 26 + config/environments/production.rb | 49 + config/environments/test.rb | 35 + config/initializers/backtrace_silencers.rb | 7 + config/initializers/inflections.rb | 10 + config/initializers/mime_types.rb | 5 + config/initializers/secret_token.rb | 7 + config/initializers/session_store.rb | 8 + config/locales/en.yml | 5 + config/routes.rb | 58 + db/seeds.rb | 7 + doc/README_FOR_APP | 2 + lib/tasks/.gitkeep | 0 public/404.html | 26 + public/422.html | 26 + public/500.html | 26 + public/favicon.ico | 0 public/images/rails.png | Bin 0 -> 6646 bytes public/index.html | 239 + public/javascripts/application.js | 2 + public/javascripts/controls.js | 965 ++++ public/javascripts/dragdrop.js | 974 ++++ public/javascripts/effects.js | 1123 ++++ public/javascripts/prototype.js | 6001 ++++++++++++++++++++ public/javascripts/rails.js | 191 + public/robots.txt | 5 + public/stylesheets/.gitkeep | 0 script/rails | 6 + test/performance/browsing_test.rb | 9 + test/test_helper.rb | 13 + vendor/plugins/.gitkeep | 0 45 files changed, 10978 insertions(+) create mode 100644 .gitignore create mode 100644 Gemfile create mode 100644 Gemfile.lock create mode 100644 LICENSE create mode 100644 README create mode 100644 Rakefile create mode 100644 app/controllers/application_controller.rb create mode 100644 app/helpers/application_helper.rb create mode 100644 app/views/layouts/application.html.erb create mode 100644 config.ru create mode 100644 config/application.rb create mode 100644 config/boot.rb create mode 100644 config/database.yml create mode 100644 config/environment.rb create mode 100644 config/environments/development.rb create mode 100644 config/environments/production.rb create mode 100644 config/environments/test.rb create mode 100644 config/initializers/backtrace_silencers.rb create mode 100644 config/initializers/inflections.rb create mode 100644 config/initializers/mime_types.rb create mode 100644 config/initializers/secret_token.rb create mode 100644 config/initializers/session_store.rb create mode 100644 config/locales/en.yml create mode 100644 config/routes.rb create mode 100644 db/seeds.rb create mode 100644 doc/README_FOR_APP create mode 100644 lib/tasks/.gitkeep create mode 100644 public/404.html create mode 100644 public/422.html create mode 100644 public/500.html create mode 100644 public/favicon.ico create mode 100644 public/images/rails.png create mode 100644 public/index.html create mode 100644 public/javascripts/application.js create mode 100644 public/javascripts/controls.js create mode 100644 public/javascripts/dragdrop.js create mode 100644 public/javascripts/effects.js create mode 100644 public/javascripts/prototype.js create mode 100644 public/javascripts/rails.js create mode 100644 public/robots.txt create mode 100644 public/stylesheets/.gitkeep create mode 100755 script/rails create mode 100644 test/performance/browsing_test.rb create mode 100644 test/test_helper.rb create mode 100644 vendor/plugins/.gitkeep diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b602af9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.bundle +db/*.sqlite3 +log/*.log +tmp/ +.DS_Store diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..7105cee --- /dev/null +++ b/Gemfile @@ -0,0 +1,31 @@ +source 'http://rubygems.org' + +gem 'rails', '3.0.7' + +# Bundle edge Rails instead: +# gem 'rails', :git => 'git://github.com/rails/rails.git' + +gem 'sqlite3' + +# Use unicorn as the web server +# gem 'unicorn' + +# Deploy with Capistrano +# gem 'capistrano' + +# To use debugger (ruby-debug for Ruby 1.8.7+, ruby-debug19 for Ruby 1.9.2+) +# gem 'ruby-debug' +# gem 'ruby-debug19', :require => 'ruby-debug' + +# Bundle the extra gems: +# gem 'bj' +# gem 'nokogiri' +# gem 'sqlite3-ruby', :require => 'sqlite3' +# gem 'aws-s3', :require => 'aws/s3' + +# Bundle gems for the local environment. Make sure to +# put test-only gems in this group so their generators +# and rake tasks are available in development mode: +# group :development, :test do +# gem 'webrat' +# end diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..89394e1 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,73 @@ +GEM + remote: http://rubygems.org/ + specs: + abstract (1.0.0) + actionmailer (3.0.7) + actionpack (= 3.0.7) + mail (~> 2.2.15) + actionpack (3.0.7) + activemodel (= 3.0.7) + activesupport (= 3.0.7) + builder (~> 2.1.2) + erubis (~> 2.6.6) + i18n (~> 0.5.0) + rack (~> 1.2.1) + rack-mount (~> 0.6.14) + rack-test (~> 0.5.7) + tzinfo (~> 0.3.23) + activemodel (3.0.7) + activesupport (= 3.0.7) + builder (~> 2.1.2) + i18n (~> 0.5.0) + activerecord (3.0.7) + activemodel (= 3.0.7) + activesupport (= 3.0.7) + arel (~> 2.0.2) + tzinfo (~> 0.3.23) + activeresource (3.0.7) + activemodel (= 3.0.7) + activesupport (= 3.0.7) + activesupport (3.0.7) + arel (2.0.9) + builder (2.1.2) + erubis (2.6.6) + abstract (>= 1.0.0) + i18n (0.5.0) + mail (2.2.19) + activesupport (>= 2.3.6) + i18n (>= 0.4.0) + mime-types (~> 1.16) + treetop (~> 1.4.8) + mime-types (1.16) + polyglot (0.3.1) + rack (1.2.2) + rack-mount (0.6.14) + rack (>= 1.0.0) + rack-test (0.5.7) + rack (>= 1.0) + rails (3.0.7) + actionmailer (= 3.0.7) + actionpack (= 3.0.7) + activerecord (= 3.0.7) + activeresource (= 3.0.7) + activesupport (= 3.0.7) + bundler (~> 1.0) + railties (= 3.0.7) + railties (3.0.7) + actionpack (= 3.0.7) + activesupport (= 3.0.7) + rake (>= 0.8.7) + thor (~> 0.14.4) + rake (0.8.7) + sqlite3 (1.3.3) + thor (0.14.6) + treetop (1.4.9) + polyglot (>= 0.3.1) + tzinfo (0.3.27) + +PLATFORMS + ruby + +DEPENDENCIES + rails (= 3.0.7) + sqlite3 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b710ecd --- /dev/null +++ b/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + UNSW CSESoc Website + Copyright (C) 2011 UNSW CSESoc + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/README b/README new file mode 100644 index 0000000..4ef1926 --- /dev/null +++ b/README @@ -0,0 +1,278 @@ +== CSESoc Website + +The UNSW CSESoc Website. + +== License + + UNSW CSESoc Website + Copyright (C) 2011 UNSW CSESoc + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +== Welcome to Rails + +Rails is a web-application framework that includes everything needed to create +database-backed web applications according to the Model-View-Control pattern. + +This pattern splits the view (also called the presentation) into "dumb" +templates that are primarily responsible for inserting pre-built data in between +HTML tags. The model contains the "smart" domain objects (such as Account, +Product, Person, Post) that holds all the business logic and knows how to +persist themselves to a database. The controller handles the incoming requests +(such as Save New Account, Update Product, Show Post) by manipulating the model +and directing data to the view. + +In Rails, the model is handled by what's called an object-relational mapping +layer entitled Active Record. This layer allows you to present the data from +database rows as objects and embellish these data objects with business logic +methods. You can read more about Active Record in +link:files/vendor/rails/activerecord/README.html. + +The controller and view are handled by the Action Pack, which handles both +layers by its two parts: Action View and Action Controller. These two layers +are bundled in a single package due to their heavy interdependence. This is +unlike the relationship between the Active Record and Action Pack that is much +more separate. Each of these packages can be used independently outside of +Rails. You can read more about Action Pack in +link:files/vendor/rails/actionpack/README.html. + + +== Getting Started + +1. At the command prompt, create a new Rails application: + rails new myapp (where myapp is the application name) + +2. Change directory to myapp and start the web server: + cd myapp; rails server (run with --help for options) + +3. Go to http://localhost:3000/ and you'll see: + "Welcome aboard: You're riding Ruby on Rails!" + +4. Follow the guidelines to start developing your application. You can find +the following resources handy: + +* The Getting Started Guide: http://guides.rubyonrails.org/getting_started.html +* Ruby on Rails Tutorial Book: http://www.railstutorial.org/ + + +== Debugging Rails + +Sometimes your application goes wrong. Fortunately there are a lot of tools that +will help you debug it and get it back on the rails. + +First area to check is the application log files. Have "tail -f" commands +running on the server.log and development.log. Rails will automatically display +debugging and runtime information to these files. Debugging info will also be +shown in the browser on requests from 127.0.0.1. + +You can also log your own messages directly into the log file from your code +using the Ruby logger class from inside your controllers. Example: + + class WeblogController < ActionController::Base + def destroy + @weblog = Weblog.find(params[:id]) + @weblog.destroy + logger.info("#{Time.now} Destroyed Weblog ID ##{@weblog.id}!") + end + end + +The result will be a message in your log file along the lines of: + + Mon Oct 08 14:22:29 +1000 2007 Destroyed Weblog ID #1! + +More information on how to use the logger is at http://www.ruby-doc.org/core/ + +Also, Ruby documentation can be found at http://www.ruby-lang.org/. There are +several books available online as well: + +* Programming Ruby: http://www.ruby-doc.org/docs/ProgrammingRuby/ (Pickaxe) +* Learn to Program: http://pine.fm/LearnToProgram/ (a beginners guide) + +These two books will bring you up to speed on the Ruby language and also on +programming in general. + + +== Debugger + +Debugger support is available through the debugger command when you start your +Mongrel or WEBrick server with --debugger. This means that you can break out of +execution at any point in the code, investigate and change the model, and then, +resume execution! You need to install ruby-debug to run the server in debugging +mode. With gems, use sudo gem install ruby-debug. Example: + + class WeblogController < ActionController::Base + def index + @posts = Post.find(:all) + debugger + end + end + +So the controller will accept the action, run the first line, then present you +with a IRB prompt in the server window. Here you can do things like: + + >> @posts.inspect + => "[#nil, "body"=>nil, "id"=>"1"}>, + #"Rails", "body"=>"Only ten..", "id"=>"2"}>]" + >> @posts.first.title = "hello from a debugger" + => "hello from a debugger" + +...and even better, you can examine how your runtime objects actually work: + + >> f = @posts.first + => #nil, "body"=>nil, "id"=>"1"}> + >> f. + Display all 152 possibilities? (y or n) + +Finally, when you're ready to resume execution, you can enter "cont". + + +== Console + +The console is a Ruby shell, which allows you to interact with your +application's domain model. Here you'll have all parts of the application +configured, just like it is when the application is running. You can inspect +domain models, change values, and save to the database. Starting the script +without arguments will launch it in the development environment. + +To start the console, run rails console from the application +directory. + +Options: + +* Passing the -s, --sandbox argument will rollback any modifications + made to the database. +* Passing an environment name as an argument will load the corresponding + environment. Example: rails console production. + +To reload your controllers and models after launching the console run +reload! + +More information about irb can be found at: +link:http://www.rubycentral.com/pickaxe/irb.html + + +== dbconsole + +You can go to the command line of your database directly through rails +dbconsole. You would be connected to the database with the credentials +defined in database.yml. Starting the script without arguments will connect you +to the development database. Passing an argument will connect you to a different +database, like rails dbconsole production. Currently works for MySQL, +PostgreSQL and SQLite 3. + +== Description of Contents + +The default directory structure of a generated Ruby on Rails application: + + |-- app + | |-- controllers + | |-- helpers + | |-- mailers + | |-- models + | `-- views + | `-- layouts + |-- config + | |-- environments + | |-- initializers + | `-- locales + |-- db + |-- doc + |-- lib + | `-- tasks + |-- log + |-- public + | |-- images + | |-- javascripts + | `-- stylesheets + |-- script + |-- test + | |-- fixtures + | |-- functional + | |-- integration + | |-- performance + | `-- unit + |-- tmp + | |-- cache + | |-- pids + | |-- sessions + | `-- sockets + `-- vendor + `-- plugins + +app + Holds all the code that's specific to this particular application. + +app/controllers + Holds controllers that should be named like weblogs_controller.rb for + automated URL mapping. All controllers should descend from + ApplicationController which itself descends from ActionController::Base. + +app/models + Holds models that should be named like post.rb. Models descend from + ActiveRecord::Base by default. + +app/views + Holds the template files for the view that should be named like + weblogs/index.html.erb for the WeblogsController#index action. All views use + eRuby syntax by default. + +app/views/layouts + Holds the template files for layouts to be used with views. This models the + common header/footer method of wrapping views. In your views, define a layout + using the layout :default and create a file named default.html.erb. + Inside default.html.erb, call <% yield %> to render the view using this + layout. + +app/helpers + Holds view helpers that should be named like weblogs_helper.rb. These are + generated for you automatically when using generators for controllers. + Helpers can be used to wrap functionality for your views into methods. + +config + Configuration files for the Rails environment, the routing map, the database, + and other dependencies. + +db + Contains the database schema in schema.rb. db/migrate contains all the + sequence of Migrations for your schema. + +doc + This directory is where your application documentation will be stored when + generated using rake doc:app + +lib + Application specific libraries. Basically, any kind of custom code that + doesn't belong under controllers, models, or helpers. This directory is in + the load path. + +public + The directory available for the web server. Contains subdirectories for + images, stylesheets, and javascripts. Also contains the dispatchers and the + default HTML files. This should be set as the DOCUMENT_ROOT of your web + server. + +script + Helper scripts for automation and generation. + +test + Unit and functional tests along with fixtures. When using the rails generate + command, template test files will be generated for you and placed in this + directory. + +vendor + External libraries that the application depends on. Also includes the plugins + subdirectory. If the app has frozen rails, those gems also go here, under + vendor/rails/. This directory is in the load path. diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..087ab7d --- /dev/null +++ b/Rakefile @@ -0,0 +1,7 @@ +# Add your own tasks in files placed in lib/tasks ending in .rake, +# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. + +require File.expand_path('../config/application', __FILE__) +require 'rake' + +CSESocWebsite::Application.load_tasks diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb new file mode 100644 index 0000000..e8065d9 --- /dev/null +++ b/app/controllers/application_controller.rb @@ -0,0 +1,3 @@ +class ApplicationController < ActionController::Base + protect_from_forgery +end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb new file mode 100644 index 0000000..de6be79 --- /dev/null +++ b/app/helpers/application_helper.rb @@ -0,0 +1,2 @@ +module ApplicationHelper +end diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb new file mode 100644 index 0000000..c0e688b --- /dev/null +++ b/app/views/layouts/application.html.erb @@ -0,0 +1,14 @@ + + + + CSESocWebsite + <%= stylesheet_link_tag :all %> + <%= javascript_include_tag :defaults %> + <%= csrf_meta_tag %> + + + +<%= yield %> + + + diff --git a/config.ru b/config.ru new file mode 100644 index 0000000..7da2ced --- /dev/null +++ b/config.ru @@ -0,0 +1,4 @@ +# This file is used by Rack-based servers to start the application. + +require ::File.expand_path('../config/environment', __FILE__) +run CSESocWebsite::Application diff --git a/config/application.rb b/config/application.rb new file mode 100644 index 0000000..d9382ae --- /dev/null +++ b/config/application.rb @@ -0,0 +1,42 @@ +require File.expand_path('../boot', __FILE__) + +require 'rails/all' + +# If you have a Gemfile, require the gems listed there, including any gems +# you've limited to :test, :development, or :production. +Bundler.require(:default, Rails.env) if defined?(Bundler) + +module CSESocWebsite + class Application < Rails::Application + # Settings in config/environments/* take precedence over those specified here. + # Application configuration should go into files in config/initializers + # -- all .rb files in that directory are automatically loaded. + + # Custom directories with classes and modules you want to be autoloadable. + # config.autoload_paths += %W(#{config.root}/extras) + + # Only load the plugins named here, in the order given (default is alphabetical). + # :all can be used as a placeholder for all plugins not explicitly named. + # config.plugins = [ :exception_notification, :ssl_requirement, :all ] + + # Activate observers that should always be running. + # config.active_record.observers = :cacher, :garbage_collector, :forum_observer + + # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. + # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. + # config.time_zone = 'Central Time (US & Canada)' + + # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. + # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] + # config.i18n.default_locale = :de + + # JavaScript files you want as :defaults (application.js is always included). + # config.action_view.javascript_expansions[:defaults] = %w(jquery rails) + + # Configure the default encoding used in templates for Ruby 1.9. + config.encoding = "utf-8" + + # Configure sensitive parameters which will be filtered from the log file. + config.filter_parameters += [:password] + end +end diff --git a/config/boot.rb b/config/boot.rb new file mode 100644 index 0000000..4489e58 --- /dev/null +++ b/config/boot.rb @@ -0,0 +1,6 @@ +require 'rubygems' + +# Set up gems listed in the Gemfile. +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) + +require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE']) diff --git a/config/database.yml b/config/database.yml new file mode 100644 index 0000000..90d87cc --- /dev/null +++ b/config/database.yml @@ -0,0 +1,22 @@ +# SQLite version 3.x +# gem install sqlite3 +development: + adapter: sqlite3 + database: db/development.sqlite3 + pool: 5 + timeout: 5000 + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + adapter: sqlite3 + database: db/test.sqlite3 + pool: 5 + timeout: 5000 + +production: + adapter: sqlite3 + database: db/production.sqlite3 + pool: 5 + timeout: 5000 diff --git a/config/environment.rb b/config/environment.rb new file mode 100644 index 0000000..71f1350 --- /dev/null +++ b/config/environment.rb @@ -0,0 +1,5 @@ +# Load the rails application +require File.expand_path('../application', __FILE__) + +# Initialize the rails application +CSESocWebsite::Application.initialize! diff --git a/config/environments/development.rb b/config/environments/development.rb new file mode 100644 index 0000000..c1528f5 --- /dev/null +++ b/config/environments/development.rb @@ -0,0 +1,26 @@ +CSESocWebsite::Application.configure do + # Settings specified here will take precedence over those in config/application.rb + + # In the development environment your application's code is reloaded on + # every request. This slows down response time but is perfect for development + # since you don't have to restart the webserver when you make code changes. + config.cache_classes = false + + # Log error messages when you accidentally call methods on nil. + config.whiny_nils = true + + # Show full error reports and disable caching + config.consider_all_requests_local = true + config.action_view.debug_rjs = true + config.action_controller.perform_caching = false + + # Don't care if the mailer can't send + config.action_mailer.raise_delivery_errors = false + + # Print deprecation notices to the Rails logger + config.active_support.deprecation = :log + + # Only use best-standards-support built into browsers + config.action_dispatch.best_standards_support = :builtin +end + diff --git a/config/environments/production.rb b/config/environments/production.rb new file mode 100644 index 0000000..831e1cf --- /dev/null +++ b/config/environments/production.rb @@ -0,0 +1,49 @@ +CSESocWebsite::Application.configure do + # Settings specified here will take precedence over those in config/application.rb + + # The production environment is meant for finished, "live" apps. + # Code is not reloaded between requests + config.cache_classes = true + + # Full error reports are disabled and caching is turned on + config.consider_all_requests_local = false + config.action_controller.perform_caching = true + + # Specifies the header that your server uses for sending files + config.action_dispatch.x_sendfile_header = "X-Sendfile" + + # For nginx: + # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' + + # If you have no front-end server that supports something like X-Sendfile, + # just comment this out and Rails will serve the files + + # See everything in the log (default is :info) + # config.log_level = :debug + + # Use a different logger for distributed setups + # config.logger = SyslogLogger.new + + # Use a different cache store in production + # config.cache_store = :mem_cache_store + + # Disable Rails's static asset server + # In production, Apache or nginx will already do this + config.serve_static_assets = false + + # Enable serving of images, stylesheets, and javascripts from an asset server + # config.action_controller.asset_host = "http://assets.example.com" + + # Disable delivery errors, bad email addresses will be ignored + # config.action_mailer.raise_delivery_errors = false + + # Enable threaded mode + # config.threadsafe! + + # Enable locale fallbacks for I18n (makes lookups for any locale fall back to + # the I18n.default_locale when a translation can not be found) + config.i18n.fallbacks = true + + # Send deprecation notices to registered listeners + config.active_support.deprecation = :notify +end diff --git a/config/environments/test.rb b/config/environments/test.rb new file mode 100644 index 0000000..459bd33 --- /dev/null +++ b/config/environments/test.rb @@ -0,0 +1,35 @@ +CSESocWebsite::Application.configure do + # Settings specified here will take precedence over those in config/application.rb + + # The test environment is used exclusively to run your application's + # test suite. You never need to work with it otherwise. Remember that + # your test database is "scratch space" for the test suite and is wiped + # and recreated between test runs. Don't rely on the data there! + config.cache_classes = true + + # Log error messages when you accidentally call methods on nil. + config.whiny_nils = true + + # Show full error reports and disable caching + config.consider_all_requests_local = true + config.action_controller.perform_caching = false + + # Raise exceptions instead of rendering exception templates + config.action_dispatch.show_exceptions = false + + # Disable request forgery protection in test environment + config.action_controller.allow_forgery_protection = false + + # Tell Action Mailer not to deliver emails to the real world. + # The :test delivery method accumulates sent emails in the + # ActionMailer::Base.deliveries array. + config.action_mailer.delivery_method = :test + + # Use SQL instead of Active Record's schema dumper when creating the test database. + # This is necessary if your schema can't be completely dumped by the schema dumper, + # like if you have constraints or database-specific column types + # config.active_record.schema_format = :sql + + # Print deprecation notices to the stderr + config.active_support.deprecation = :stderr +end diff --git a/config/initializers/backtrace_silencers.rb b/config/initializers/backtrace_silencers.rb new file mode 100644 index 0000000..59385cd --- /dev/null +++ b/config/initializers/backtrace_silencers.rb @@ -0,0 +1,7 @@ +# Be sure to restart your server when you modify this file. + +# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. +# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } + +# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. +# Rails.backtrace_cleaner.remove_silencers! diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb new file mode 100644 index 0000000..9e8b013 --- /dev/null +++ b/config/initializers/inflections.rb @@ -0,0 +1,10 @@ +# Be sure to restart your server when you modify this file. + +# Add new inflection rules using the following format +# (all these examples are active by default): +# ActiveSupport::Inflector.inflections do |inflect| +# inflect.plural /^(ox)$/i, '\1en' +# inflect.singular /^(ox)en/i, '\1' +# inflect.irregular 'person', 'people' +# inflect.uncountable %w( fish sheep ) +# end diff --git a/config/initializers/mime_types.rb b/config/initializers/mime_types.rb new file mode 100644 index 0000000..72aca7e --- /dev/null +++ b/config/initializers/mime_types.rb @@ -0,0 +1,5 @@ +# Be sure to restart your server when you modify this file. + +# Add new mime types for use in respond_to blocks: +# Mime::Type.register "text/richtext", :rtf +# Mime::Type.register_alias "text/html", :iphone diff --git a/config/initializers/secret_token.rb b/config/initializers/secret_token.rb new file mode 100644 index 0000000..1de3eab --- /dev/null +++ b/config/initializers/secret_token.rb @@ -0,0 +1,7 @@ +# Be sure to restart your server when you modify this file. + +# Your secret key for verifying the integrity of signed cookies. +# If you change this key, all old signed cookies will become invalid! +# Make sure the secret is at least 30 characters and all random, +# no regular words or you'll be exposed to dictionary attacks. +CSESocWebsite::Application.config.secret_token = '191a310a7defe174dbb15da9c7bc363621612f2da66b87e2b58da90e84d907c792dcea51c11d12a41121d40c51c5ea1cbb84e77c0af23be46453cdd953060ec1' diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb new file mode 100644 index 0000000..450bffa --- /dev/null +++ b/config/initializers/session_store.rb @@ -0,0 +1,8 @@ +# Be sure to restart your server when you modify this file. + +CSESocWebsite::Application.config.session_store :cookie_store, :key => '_CSESoc-Website_session' + +# Use the database for sessions instead of the cookie-based default, +# which shouldn't be used to store highly confidential information +# (create the session table with "rails generate session_migration") +# CSESocWebsite::Application.config.session_store :active_record_store diff --git a/config/locales/en.yml b/config/locales/en.yml new file mode 100644 index 0000000..a747bfa --- /dev/null +++ b/config/locales/en.yml @@ -0,0 +1,5 @@ +# Sample localization file for English. Add more files in this directory for other locales. +# See http://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. + +en: + hello: "Hello world" diff --git a/config/routes.rb b/config/routes.rb new file mode 100644 index 0000000..e8fa8bc --- /dev/null +++ b/config/routes.rb @@ -0,0 +1,58 @@ +CSESocWebsite::Application.routes.draw do + # The priority is based upon order of creation: + # first created -> highest priority. + + # Sample of regular route: + # match 'products/:id' => 'catalog#view' + # Keep in mind you can assign values other than :controller and :action + + # Sample of named route: + # match 'products/:id/purchase' => 'catalog#purchase', :as => :purchase + # This route can be invoked with purchase_url(:id => product.id) + + # Sample resource route (maps HTTP verbs to controller actions automatically): + # resources :products + + # Sample resource route with options: + # resources :products do + # member do + # get 'short' + # post 'toggle' + # end + # + # collection do + # get 'sold' + # end + # end + + # Sample resource route with sub-resources: + # resources :products do + # resources :comments, :sales + # resource :seller + # end + + # Sample resource route with more complex sub-resources + # resources :products do + # resources :comments + # resources :sales do + # get 'recent', :on => :collection + # end + # end + + # Sample resource route within a namespace: + # namespace :admin do + # # Directs /admin/products/* to Admin::ProductsController + # # (app/controllers/admin/products_controller.rb) + # resources :products + # end + + # You can have the root of your site routed with "root" + # just remember to delete public/index.html. + # root :to => "welcome#index" + + # See how all your routes lay out with "rake routes" + + # This is a legacy wild controller route that's not recommended for RESTful applications. + # Note: This route will make all actions in every controller accessible via GET requests. + # match ':controller(/:action(/:id(.:format)))' +end diff --git a/db/seeds.rb b/db/seeds.rb new file mode 100644 index 0000000..664d8c7 --- /dev/null +++ b/db/seeds.rb @@ -0,0 +1,7 @@ +# This file should contain all the record creation needed to seed the database with its default values. +# The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). +# +# Examples: +# +# cities = City.create([{ :name => 'Chicago' }, { :name => 'Copenhagen' }]) +# Mayor.create(:name => 'Daley', :city => cities.first) diff --git a/doc/README_FOR_APP b/doc/README_FOR_APP new file mode 100644 index 0000000..fe41f5c --- /dev/null +++ b/doc/README_FOR_APP @@ -0,0 +1,2 @@ +Use this README file to introduce your application and point to useful places in the API for learning more. +Run "rake doc:app" to generate API documentation for your models, controllers, helpers, and libraries. diff --git a/lib/tasks/.gitkeep b/lib/tasks/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/public/404.html b/public/404.html new file mode 100644 index 0000000..9a48320 --- /dev/null +++ b/public/404.html @@ -0,0 +1,26 @@ + + + + The page you were looking for doesn't exist (404) + + + + + +
+

The page you were looking for doesn't exist.

+

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

+
+ + diff --git a/public/422.html b/public/422.html new file mode 100644 index 0000000..83660ab --- /dev/null +++ b/public/422.html @@ -0,0 +1,26 @@ + + + + The change you wanted was rejected (422) + + + + + +
+

The change you wanted was rejected.

+

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

+
+ + diff --git a/public/500.html b/public/500.html new file mode 100644 index 0000000..b80307f --- /dev/null +++ b/public/500.html @@ -0,0 +1,26 @@ + + + + We're sorry, but something went wrong (500) + + + + + +
+

We're sorry, but something went wrong.

+

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

+
+ + diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..e69de29 diff --git a/public/images/rails.png b/public/images/rails.png new file mode 100644 index 0000000000000000000000000000000000000000..d5edc04e65f555e3ba4dcdaad39dc352e75b575e GIT binary patch literal 6646 zcmVpVcQya!6@Dsmj@#jv7C*qh zIhOJ6_K0n?*d`*T7TDuW-}m`9Kz3~>+7`DUkbAraU%yi+R{N~~XA2B%zt-4=tLimUer9!2M~N{G5bftFij_O&)a zsHnOppFIzebQ`RA0$!yUM-lg#*o@_O2wf422iLnM6cU(ktYU8#;*G!QGhIy9+ZfzKjLuZo%@a z-i@9A`X%J{^;2q&ZHY3C(B%gqCPW!8{9C0PMcNZccefK){s|V5-xxtHQc@uf>XqhD z7#N^siWqetgq29aX>G^olMf=bbRF6@Y(}zYxw6o!9WBdG1unP}<(V;zKlcR2p86fq zYjaqB^;Ycq>Wy@5T1xOzG3tucG3e%nPvajaN{CrFbnzv^9&K3$NrDm*eQe4`BGQ2bI;dFEwyt>hK%X!L6)82aOZp zsrGcJ#7PoX7)s|~t6is?FfX*7vWdREi58tiY4S)t6u*|kv?J)d_$r+CH#eZ?Ef+I_ z(eVlX8dh~4QP?o*E`_MgaNFIKj*rtN(0Raj3ECjSXcWfd#27NYs&~?t`QZFT}!Zaf=ldZIhi}LhQlqLo+o5(Pvui&{7PD__^53f9j>HW`Q z_V8X5j~$|GP9qXu0C#!@RX2}lXD35@3N5{BkUi%jtaPQ*H6OX2zIz4QPuqmTv3`vG{zc>l3t0B9E75h< z8&twGh%dp7WPNI+tRl%#gf2}Epg8st+~O4GjtwJsXfN;EjAmyr6z5dnaFU(;IV~QK zW62fogF~zA``(Q>_SmD!izc6Y4zq*97|NAPHp1j5X7Op2%;GLYm>^HEMyObo6s7l) zE3n|aOHi5~B84!}b^b*-aL2E)>OEJX_tJ~t<#VJ?bT?lDwyDB&5SZ$_1aUhmAY}#* zs@V1I+c5md9%R-o#_DUfqVtRk>59{+Opd5Yu%dAU#VQW}^m}x-30ftBx#527{^pI4 z6l2C6C7QBG$~NLYb3rVdLD#Z{+SleOp`(Lg5J}`kxdTHe(nV5BdpLrD=l|)e$gEqA zwI6vuX-PFCtcDIH>bGY2dwq&^tf+&R?)nY-@7_j%4CMRAF}C9w%p86W<2!aSY$p+k zrkFtG=cGo38RnrG28;?PNk%7a@faaXq&MS*&?1Z`7Ojw7(#>}ZG4nMAs3VXxfdW>i zY4VX02c5;f7jDPY_7@Oa)CHH}cH<3y#}_!nng^W+h1e-RL*YFYOteC@h?BtJZ+?sE zy)P5^8Mregx{nQaw1NY-|3>{Z)|0`?zc?G2-acYiSU`tj#sSGfm7k86ZQ0SQgPevcklHxM9<~4yW zR796sisf1|!#{Z=e^)0;_8iUhL8g(;j$l=02FTPZ(dZV@s#aQ`DHkLM6=YsbE4iQ!b#*374l0Jw5;jD%J;vQayq=nD8-kHI~f9Ux|32SJUM`> zGp2UGK*4t?cRKi!2he`zI#j0f${I#f-jeT?u_C7S4WsA0)ryi-1L0(@%pa^&g5x=e z=KW9+Nn(=)1T&S8g_ug%dgk*~l2O-$r9#zEGBdQsweO%t*6F4c8JC36JtTizCyy+E4h%G(+ z5>y$%0txMuQ$e~wjFgN(xrAndHQo`Za+K*?gUVDTBV&Ap^}|{w#CIq{DRe}+l@(Ec zCCV6f_?dY_{+f{}6XGn!pL_up?}@>KijT^$w#Lb6iHW&^8RP~g6y=vZBXx~B9nI^i zGexaPjcd(%)zGw!DG_dDwh-7x6+ST#R^${iz_M$uM!da8SxgB_;Z0G%Y*HpvLjKw; zX=ir7i1O$-T|*TBoH$dlW+TLf5j5sep^DlDtkox;Kg{Q%EXWedJq@J@%VAcK)j3y1 zShM!CS#qax;D@RND%2t3W6kv+#Ky0F9<3YKDbV^XJ=^$s(Vtza8V72YY)577nnldI zHMA0PUo!F3j(ubV*CM@PiK<^|RM2(DuCbG7`W}Rg(xdYC>C~ z;1KJGLN&$cRxSZunjXcntykmpFJ7;dk>shY(DdK&3K_JDJ6R%D`e~6Qv67@Rwu+q9 z*|NG{r}4F8f{Dfzt0+cZMd$fvlX3Q`dzM46@r?ISxr;9gBTG2rmfiGOD*#c*3f)cc zF+PFZobY$-^}J8 z%n=h4;x2}cP!@SiVd!v;^Wwo0(N??-ygDr7gG^NKxDjSo{5T{?$|Qo5;8V!~D6O;F*I zuY!gd@+2j_8Rn=UWDa#*4E2auWoGYDddMW7t0=yuC(xLWky?vLimM~!$3fgu!dR>p z?L?!8z>6v$|MsLb&dU?ob)Zd!B)!a*Z2eTE7 zKCzP&e}XO>CT%=o(v+WUY`Az*`9inbTG& z_9_*oQKw;sc8{ipoBC`S4Tb7a%tUE)1fE+~ib$;|(`|4QbXc2>VzFi%1nX%ti;^s3~NIL0R}!!a{0A zyCRp0F7Y&vcP&3`&Dzv5!&#h}F2R-h&QhIfq*ts&qO13{_CP}1*sLz!hI9VoTSzTu zok5pV0+~jrGymE~{TgbS#nN5+*rF7ij)cnSLQw0Ltc70zmk|O!O(kM<3zw-sUvkx~ z2`y+{xAwKSa-0}n7{$I@Zop7CWy%_xIeN1e-7&OjQ6vZZPbZ^3_ z(~=;ZSP98S2oB#35b1~_x`2gWiPdIVddEf`AD9<@c_s)TM;3J$T_l?pr{<7PTgdiy zBc5IGx)g~n=s+Z$RzYCmv8PlJu%gkh^;%mTGMc)UwRINVD~K;`Rl!5@hhGg;y>5qj zq|u-Yf0q_~Y+Mbivkkfa0nAOzB1acnytogsj_m7FB(-FjihMek#GAU4M!iXCgdK8a zjoKm?*|iz7;dHm4$^hh(`Ufl>yb>$hjIA-;>{>C}G0Di%bGvUsJkfLAV|xq32c>RqJqTBJ3Dx zYC;*Dt|S$b6)aCJFnK(Eey$M1DpVV~_MIhwK> zygo(jWC|_IRw|456`roEyXtkNLWNAt-4N1qyN$I@DvBzt;e|?g<*HK1%~cq|^u*}C zmMrwh>{QAq?Ar~4l^DqT%SQ)w)FA(#7#u+N;>E975rYML>)LgE`2<7nN=C1pC{IkV zVw}_&v6j&S?QVh*)wF3#XmE@0($^BVl1969csLKUBNer{suVd!a~B!0MxWY?=(GD6 zy$G&ERFR#i6G4=2F?R4}Mz3B?3tnpoX3)qFF2sh9-Jn*e%9F>i{WG7$_~XyOO2!+@ z6k+38KyD@-0=uee54D0!Z1@B^ilj~StchdOn(*qvg~s5QJpWGc!6U^Aj!xt-HZn_V zS%|fyQ5YS@EP2lBIodXCLjG_+a)%En+7jzngk@J>6D~^xbxKkvf-R0-c%mX+o{?&j zZZ%RxFeav8Y0gkwtdtrwUb-i0Egd2C=ADu%w5VV-hNJvl)GZ?M;y$!?b=S+wKRK7Q zcOjPT!p<*#8m;TsBih=@Xc&c)?Vy`Ys>IvK@|1%N+M6J-^RCRaZcPP2eQh9DEGZr+ z?8B~wF14mk4Xkuen{wY^CWwS1PI<8gikY*)3?RSo5l8es4*J z43k_BIwc}of=6Pfs%xIxlMDGOJN zvl!a>G)52XMqA%fbgkZi%)%bN*ZzZw2!rn4@+J)2eK#kWuEW{)W~-`y1vhA5-7p%R z&f5N!a9f8cK1Xa=O}=9{wg%}Ur^+8Y(!UCeqw>%wj@|bYHD-bZO~mk3L$9_^MmF3G zvCiK^e@q6G?tHkM8%GqsBMZaB20W$UEt_5r~jc#WlR>Bv{6W>A=!#InoY zLOd04@Rz?*7PpW8u|+}bt`?+Z(GsX{Br4A2$ZZ(26Degmr9`O=t2KgHTL*==R3xcP z&Y(J7hC@6_x8zVz!CX3l4Xtss6i7r#E6kXMNN1~>9KTRzewfp))ij%)SBBl0fZdYP zd!zzQD5u8yk-u|41|Rqz7_tCFUMThZJVj)yQf6^Cwtn|Ew6cm5J|u1Bq>MWX-AfB&NE;C z62@=-0le`E6-CurMKjoIy)BuUmhMGJb}pPx!@GLWMT+wH2R?wA=MEy)o57~feFp8P zY@YXAyt4<1FD<|iw{FGQu~GEI<4C64)V*QiVk+VzOV^9GWf4ir#oYgHJz!wq>iZV#_6@_{)&lum)4x z_Of*CLVQ7wdT#XT-(h0qH%mcIF7yzMIvvTN3bPceK>PpJi(=3Nny zbSn}p$dGKQUlX&-t~RR)#F7I<8NCD^yke(vdf#4^aAh}M-{tS9-&^tC4`KU_pToXy z+|K8sx}a)Kh{h{;*V1#hs1xB%(?j>)g~`Wv(9F)f=Qn)(daVB7hZtcp^#LrEr1T1J zZSJ*lVyVVjhy)mkex9Whn=EinKDHe@KlfQI-Fl7M?-c~HnW0;C;+MbUY8?FToy;A+ zs&Nc7VZ=Of+e!G6s#+S5WBU)kgQq_I1@!uH74GJ-+O|%0HXm9Mqlvp|j%0`T>fr9^ zK;qo>XdwZW<>%tTA+<(1^6(>=-2N;hRgBnjvEjN;VbKMbFg--WrGy|XESoH1p|M4` z86(gC^vB4qScASZ&cdpT{~QDN-jC|GJ(RYoW1VW4!SSn- zhQds9&RBKn6M&GVK_Aayt(Hekbnw=tr>f z^o@v9_*iQO1*zeOrts9Q-$pc@!StS&kz$cF`s@pM`rmJXTP&h5G)A74!0e%ZJbl}( zssI|_!%~_hZFypv*S^JE5N&Kvmx7KiG<|fGMO=WrH+@Yhuj+KwiS#l4>@%2nl zS)mDikfmokO4q2A)hRVZBq2-5q&XC>%HOLkOYxZ66(s86?=0s4z5xbiOV)}L-&6b)h6(~CIaR#JNw~46+WBiU7IhB zq!NuR4!TsYnyBg>@G=Ib*cMq^k<}AMpCeYEf&dzfiGI-wOQ7hb+nA zkN7_){y&c3xC0 AQ~&?~ literal 0 HcmV?d00001 diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..75d5edd --- /dev/null +++ b/public/index.html @@ -0,0 +1,239 @@ + + + + Ruby on Rails: Welcome aboard + + + + +
+ + +
+ + + + +
+

Getting started

+

Here’s how to get rolling:

+ +
    +
  1. +

    Use rails generate to create your models and controllers

    +

    To see all available options, run it without parameters.

    +
  2. + +
  3. +

    Set up a default route and remove or rename this file

    +

    Routes are set up in config/routes.rb.

    +
  4. + +
  5. +

    Create your database

    +

    Run rake db:migrate to create your database. If you're not using SQLite (the default), edit config/database.yml with your username and password.

    +
  6. +
+
+
+ + +
+ + diff --git a/public/javascripts/application.js b/public/javascripts/application.js new file mode 100644 index 0000000..fe45776 --- /dev/null +++ b/public/javascripts/application.js @@ -0,0 +1,2 @@ +// Place your application-specific JavaScript functions and classes here +// This file is automatically included by javascript_include_tag :defaults diff --git a/public/javascripts/controls.js b/public/javascripts/controls.js new file mode 100644 index 0000000..7392fb6 --- /dev/null +++ b/public/javascripts/controls.js @@ -0,0 +1,965 @@ +// script.aculo.us controls.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009 + +// Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// (c) 2005-2009 Ivan Krstic (http://blogs.law.harvard.edu/ivan) +// (c) 2005-2009 Jon Tirsen (http://www.tirsen.com) +// Contributors: +// Richard Livsey +// Rahul Bhargava +// Rob Wills +// +// script.aculo.us is freely distributable under the terms of an MIT-style license. +// For details, see the script.aculo.us web site: http://script.aculo.us/ + +// Autocompleter.Base handles all the autocompletion functionality +// that's independent of the data source for autocompletion. This +// includes drawing the autocompletion menu, observing keyboard +// and mouse events, and similar. +// +// Specific autocompleters need to provide, at the very least, +// a getUpdatedChoices function that will be invoked every time +// the text inside the monitored textbox changes. This method +// should get the text for which to provide autocompletion by +// invoking this.getToken(), NOT by directly accessing +// this.element.value. This is to allow incremental tokenized +// autocompletion. Specific auto-completion logic (AJAX, etc) +// belongs in getUpdatedChoices. +// +// Tokenized incremental autocompletion is enabled automatically +// when an autocompleter is instantiated with the 'tokens' option +// in the options parameter, e.g.: +// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' }); +// will incrementally autocomplete with a comma as the token. +// Additionally, ',' in the above example can be replaced with +// a token array, e.g. { tokens: [',', '\n'] } which +// enables autocompletion on multiple tokens. This is most +// useful when one of the tokens is \n (a newline), as it +// allows smart autocompletion after linebreaks. + +if(typeof Effect == 'undefined') + throw("controls.js requires including script.aculo.us' effects.js library"); + +var Autocompleter = { }; +Autocompleter.Base = Class.create({ + baseInitialize: function(element, update, options) { + element = $(element); + this.element = element; + this.update = $(update); + this.hasFocus = false; + this.changed = false; + this.active = false; + this.index = 0; + this.entryCount = 0; + this.oldElementValue = this.element.value; + + if(this.setOptions) + this.setOptions(options); + else + this.options = options || { }; + + this.options.paramName = this.options.paramName || this.element.name; + this.options.tokens = this.options.tokens || []; + this.options.frequency = this.options.frequency || 0.4; + this.options.minChars = this.options.minChars || 1; + this.options.onShow = this.options.onShow || + function(element, update){ + if(!update.style.position || update.style.position=='absolute') { + update.style.position = 'absolute'; + Position.clone(element, update, { + setHeight: false, + offsetTop: element.offsetHeight + }); + } + Effect.Appear(update,{duration:0.15}); + }; + this.options.onHide = this.options.onHide || + function(element, update){ new Effect.Fade(update,{duration:0.15}) }; + + if(typeof(this.options.tokens) == 'string') + this.options.tokens = new Array(this.options.tokens); + // Force carriage returns as token delimiters anyway + if (!this.options.tokens.include('\n')) + this.options.tokens.push('\n'); + + this.observer = null; + + this.element.setAttribute('autocomplete','off'); + + Element.hide(this.update); + + Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this)); + Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this)); + }, + + show: function() { + if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update); + if(!this.iefix && + (Prototype.Browser.IE) && + (Element.getStyle(this.update, 'position')=='absolute')) { + new Insertion.After(this.update, + ''); + this.iefix = $(this.update.id+'_iefix'); + } + if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50); + }, + + fixIEOverlapping: function() { + Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)}); + this.iefix.style.zIndex = 1; + this.update.style.zIndex = 2; + Element.show(this.iefix); + }, + + hide: function() { + this.stopIndicator(); + if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update); + if(this.iefix) Element.hide(this.iefix); + }, + + startIndicator: function() { + if(this.options.indicator) Element.show(this.options.indicator); + }, + + stopIndicator: function() { + if(this.options.indicator) Element.hide(this.options.indicator); + }, + + onKeyPress: function(event) { + if(this.active) + switch(event.keyCode) { + case Event.KEY_TAB: + case Event.KEY_RETURN: + this.selectEntry(); + Event.stop(event); + case Event.KEY_ESC: + this.hide(); + this.active = false; + Event.stop(event); + return; + case Event.KEY_LEFT: + case Event.KEY_RIGHT: + return; + case Event.KEY_UP: + this.markPrevious(); + this.render(); + Event.stop(event); + return; + case Event.KEY_DOWN: + this.markNext(); + this.render(); + Event.stop(event); + return; + } + else + if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || + (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return; + + this.changed = true; + this.hasFocus = true; + + if(this.observer) clearTimeout(this.observer); + this.observer = + setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000); + }, + + activate: function() { + this.changed = false; + this.hasFocus = true; + this.getUpdatedChoices(); + }, + + onHover: function(event) { + var element = Event.findElement(event, 'LI'); + if(this.index != element.autocompleteIndex) + { + this.index = element.autocompleteIndex; + this.render(); + } + Event.stop(event); + }, + + onClick: function(event) { + var element = Event.findElement(event, 'LI'); + this.index = element.autocompleteIndex; + this.selectEntry(); + this.hide(); + }, + + onBlur: function(event) { + // needed to make click events working + setTimeout(this.hide.bind(this), 250); + this.hasFocus = false; + this.active = false; + }, + + render: function() { + if(this.entryCount > 0) { + for (var i = 0; i < this.entryCount; i++) + this.index==i ? + Element.addClassName(this.getEntry(i),"selected") : + Element.removeClassName(this.getEntry(i),"selected"); + if(this.hasFocus) { + this.show(); + this.active = true; + } + } else { + this.active = false; + this.hide(); + } + }, + + markPrevious: function() { + if(this.index > 0) this.index--; + else this.index = this.entryCount-1; + this.getEntry(this.index).scrollIntoView(true); + }, + + markNext: function() { + if(this.index < this.entryCount-1) this.index++; + else this.index = 0; + this.getEntry(this.index).scrollIntoView(false); + }, + + getEntry: function(index) { + return this.update.firstChild.childNodes[index]; + }, + + getCurrentEntry: function() { + return this.getEntry(this.index); + }, + + selectEntry: function() { + this.active = false; + this.updateElement(this.getCurrentEntry()); + }, + + updateElement: function(selectedElement) { + if (this.options.updateElement) { + this.options.updateElement(selectedElement); + return; + } + var value = ''; + if (this.options.select) { + var nodes = $(selectedElement).select('.' + this.options.select) || []; + if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select); + } else + value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal'); + + var bounds = this.getTokenBounds(); + if (bounds[0] != -1) { + var newValue = this.element.value.substr(0, bounds[0]); + var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/); + if (whitespace) + newValue += whitespace[0]; + this.element.value = newValue + value + this.element.value.substr(bounds[1]); + } else { + this.element.value = value; + } + this.oldElementValue = this.element.value; + this.element.focus(); + + if (this.options.afterUpdateElement) + this.options.afterUpdateElement(this.element, selectedElement); + }, + + updateChoices: function(choices) { + if(!this.changed && this.hasFocus) { + this.update.innerHTML = choices; + Element.cleanWhitespace(this.update); + Element.cleanWhitespace(this.update.down()); + + if(this.update.firstChild && this.update.down().childNodes) { + this.entryCount = + this.update.down().childNodes.length; + for (var i = 0; i < this.entryCount; i++) { + var entry = this.getEntry(i); + entry.autocompleteIndex = i; + this.addObservers(entry); + } + } else { + this.entryCount = 0; + } + + this.stopIndicator(); + this.index = 0; + + if(this.entryCount==1 && this.options.autoSelect) { + this.selectEntry(); + this.hide(); + } else { + this.render(); + } + } + }, + + addObservers: function(element) { + Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this)); + Event.observe(element, "click", this.onClick.bindAsEventListener(this)); + }, + + onObserverEvent: function() { + this.changed = false; + this.tokenBounds = null; + if(this.getToken().length>=this.options.minChars) { + this.getUpdatedChoices(); + } else { + this.active = false; + this.hide(); + } + this.oldElementValue = this.element.value; + }, + + getToken: function() { + var bounds = this.getTokenBounds(); + return this.element.value.substring(bounds[0], bounds[1]).strip(); + }, + + getTokenBounds: function() { + if (null != this.tokenBounds) return this.tokenBounds; + var value = this.element.value; + if (value.strip().empty()) return [-1, 0]; + var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue); + var offset = (diff == this.oldElementValue.length ? 1 : 0); + var prevTokenPos = -1, nextTokenPos = value.length; + var tp; + for (var index = 0, l = this.options.tokens.length; index < l; ++index) { + tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1); + if (tp > prevTokenPos) prevTokenPos = tp; + tp = value.indexOf(this.options.tokens[index], diff + offset); + if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp; + } + return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]); + } +}); + +Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) { + var boundary = Math.min(newS.length, oldS.length); + for (var index = 0; index < boundary; ++index) + if (newS[index] != oldS[index]) + return index; + return boundary; +}; + +Ajax.Autocompleter = Class.create(Autocompleter.Base, { + initialize: function(element, update, url, options) { + this.baseInitialize(element, update, options); + this.options.asynchronous = true; + this.options.onComplete = this.onComplete.bind(this); + this.options.defaultParams = this.options.parameters || null; + this.url = url; + }, + + getUpdatedChoices: function() { + this.startIndicator(); + + var entry = encodeURIComponent(this.options.paramName) + '=' + + encodeURIComponent(this.getToken()); + + this.options.parameters = this.options.callback ? + this.options.callback(this.element, entry) : entry; + + if(this.options.defaultParams) + this.options.parameters += '&' + this.options.defaultParams; + + new Ajax.Request(this.url, this.options); + }, + + onComplete: function(request) { + this.updateChoices(request.responseText); + } +}); + +// The local array autocompleter. Used when you'd prefer to +// inject an array of autocompletion options into the page, rather +// than sending out Ajax queries, which can be quite slow sometimes. +// +// The constructor takes four parameters. The first two are, as usual, +// the id of the monitored textbox, and id of the autocompletion menu. +// The third is the array you want to autocomplete from, and the fourth +// is the options block. +// +// Extra local autocompletion options: +// - choices - How many autocompletion choices to offer +// +// - partialSearch - If false, the autocompleter will match entered +// text only at the beginning of strings in the +// autocomplete array. Defaults to true, which will +// match text at the beginning of any *word* in the +// strings in the autocomplete array. If you want to +// search anywhere in the string, additionally set +// the option fullSearch to true (default: off). +// +// - fullSsearch - Search anywhere in autocomplete array strings. +// +// - partialChars - How many characters to enter before triggering +// a partial match (unlike minChars, which defines +// how many characters are required to do any match +// at all). Defaults to 2. +// +// - ignoreCase - Whether to ignore case when autocompleting. +// Defaults to true. +// +// It's possible to pass in a custom function as the 'selector' +// option, if you prefer to write your own autocompletion logic. +// In that case, the other options above will not apply unless +// you support them. + +Autocompleter.Local = Class.create(Autocompleter.Base, { + initialize: function(element, update, array, options) { + this.baseInitialize(element, update, options); + this.options.array = array; + }, + + getUpdatedChoices: function() { + this.updateChoices(this.options.selector(this)); + }, + + setOptions: function(options) { + this.options = Object.extend({ + choices: 10, + partialSearch: true, + partialChars: 2, + ignoreCase: true, + fullSearch: false, + selector: function(instance) { + var ret = []; // Beginning matches + var partial = []; // Inside matches + var entry = instance.getToken(); + var count = 0; + + for (var i = 0; i < instance.options.array.length && + ret.length < instance.options.choices ; i++) { + + var elem = instance.options.array[i]; + var foundPos = instance.options.ignoreCase ? + elem.toLowerCase().indexOf(entry.toLowerCase()) : + elem.indexOf(entry); + + while (foundPos != -1) { + if (foundPos == 0 && elem.length != entry.length) { + ret.push("
  • " + elem.substr(0, entry.length) + "" + + elem.substr(entry.length) + "
  • "); + break; + } else if (entry.length >= instance.options.partialChars && + instance.options.partialSearch && foundPos != -1) { + if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) { + partial.push("
  • " + elem.substr(0, foundPos) + "" + + elem.substr(foundPos, entry.length) + "" + elem.substr( + foundPos + entry.length) + "
  • "); + break; + } + } + + foundPos = instance.options.ignoreCase ? + elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : + elem.indexOf(entry, foundPos + 1); + + } + } + if (partial.length) + ret = ret.concat(partial.slice(0, instance.options.choices - ret.length)); + return "
      " + ret.join('') + "
    "; + } + }, options || { }); + } +}); + +// AJAX in-place editor and collection editor +// Full rewrite by Christophe Porteneuve (April 2007). + +// Use this if you notice weird scrolling problems on some browsers, +// the DOM might be a bit confused when this gets called so do this +// waits 1 ms (with setTimeout) until it does the activation +Field.scrollFreeActivate = function(field) { + setTimeout(function() { + Field.activate(field); + }, 1); +}; + +Ajax.InPlaceEditor = Class.create({ + initialize: function(element, url, options) { + this.url = url; + this.element = element = $(element); + this.prepareOptions(); + this._controls = { }; + arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!! + Object.extend(this.options, options || { }); + if (!this.options.formId && this.element.id) { + this.options.formId = this.element.id + '-inplaceeditor'; + if ($(this.options.formId)) + this.options.formId = ''; + } + if (this.options.externalControl) + this.options.externalControl = $(this.options.externalControl); + if (!this.options.externalControl) + this.options.externalControlOnly = false; + this._originalBackground = this.element.getStyle('background-color') || 'transparent'; + this.element.title = this.options.clickToEditText; + this._boundCancelHandler = this.handleFormCancellation.bind(this); + this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this); + this._boundFailureHandler = this.handleAJAXFailure.bind(this); + this._boundSubmitHandler = this.handleFormSubmission.bind(this); + this._boundWrapperHandler = this.wrapUp.bind(this); + this.registerListeners(); + }, + checkForEscapeOrReturn: function(e) { + if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return; + if (Event.KEY_ESC == e.keyCode) + this.handleFormCancellation(e); + else if (Event.KEY_RETURN == e.keyCode) + this.handleFormSubmission(e); + }, + createControl: function(mode, handler, extraClasses) { + var control = this.options[mode + 'Control']; + var text = this.options[mode + 'Text']; + if ('button' == control) { + var btn = document.createElement('input'); + btn.type = 'submit'; + btn.value = text; + btn.className = 'editor_' + mode + '_button'; + if ('cancel' == mode) + btn.onclick = this._boundCancelHandler; + this._form.appendChild(btn); + this._controls[mode] = btn; + } else if ('link' == control) { + var link = document.createElement('a'); + link.href = '#'; + link.appendChild(document.createTextNode(text)); + link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler; + link.className = 'editor_' + mode + '_link'; + if (extraClasses) + link.className += ' ' + extraClasses; + this._form.appendChild(link); + this._controls[mode] = link; + } + }, + createEditField: function() { + var text = (this.options.loadTextURL ? this.options.loadingText : this.getText()); + var fld; + if (1 >= this.options.rows && !/\r|\n/.test(this.getText())) { + fld = document.createElement('input'); + fld.type = 'text'; + var size = this.options.size || this.options.cols || 0; + if (0 < size) fld.size = size; + } else { + fld = document.createElement('textarea'); + fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.options.rows); + fld.cols = this.options.cols || 40; + } + fld.name = this.options.paramName; + fld.value = text; // No HTML breaks conversion anymore + fld.className = 'editor_field'; + if (this.options.submitOnBlur) + fld.onblur = this._boundSubmitHandler; + this._controls.editor = fld; + if (this.options.loadTextURL) + this.loadExternalText(); + this._form.appendChild(this._controls.editor); + }, + createForm: function() { + var ipe = this; + function addText(mode, condition) { + var text = ipe.options['text' + mode + 'Controls']; + if (!text || condition === false) return; + ipe._form.appendChild(document.createTextNode(text)); + }; + this._form = $(document.createElement('form')); + this._form.id = this.options.formId; + this._form.addClassName(this.options.formClassName); + this._form.onsubmit = this._boundSubmitHandler; + this.createEditField(); + if ('textarea' == this._controls.editor.tagName.toLowerCase()) + this._form.appendChild(document.createElement('br')); + if (this.options.onFormCustomization) + this.options.onFormCustomization(this, this._form); + addText('Before', this.options.okControl || this.options.cancelControl); + this.createControl('ok', this._boundSubmitHandler); + addText('Between', this.options.okControl && this.options.cancelControl); + this.createControl('cancel', this._boundCancelHandler, 'editor_cancel'); + addText('After', this.options.okControl || this.options.cancelControl); + }, + destroy: function() { + if (this._oldInnerHTML) + this.element.innerHTML = this._oldInnerHTML; + this.leaveEditMode(); + this.unregisterListeners(); + }, + enterEditMode: function(e) { + if (this._saving || this._editing) return; + this._editing = true; + this.triggerCallback('onEnterEditMode'); + if (this.options.externalControl) + this.options.externalControl.hide(); + this.element.hide(); + this.createForm(); + this.element.parentNode.insertBefore(this._form, this.element); + if (!this.options.loadTextURL) + this.postProcessEditField(); + if (e) Event.stop(e); + }, + enterHover: function(e) { + if (this.options.hoverClassName) + this.element.addClassName(this.options.hoverClassName); + if (this._saving) return; + this.triggerCallback('onEnterHover'); + }, + getText: function() { + return this.element.innerHTML.unescapeHTML(); + }, + handleAJAXFailure: function(transport) { + this.triggerCallback('onFailure', transport); + if (this._oldInnerHTML) { + this.element.innerHTML = this._oldInnerHTML; + this._oldInnerHTML = null; + } + }, + handleFormCancellation: function(e) { + this.wrapUp(); + if (e) Event.stop(e); + }, + handleFormSubmission: function(e) { + var form = this._form; + var value = $F(this._controls.editor); + this.prepareSubmission(); + var params = this.options.callback(form, value) || ''; + if (Object.isString(params)) + params = params.toQueryParams(); + params.editorId = this.element.id; + if (this.options.htmlResponse) { + var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions); + Object.extend(options, { + parameters: params, + onComplete: this._boundWrapperHandler, + onFailure: this._boundFailureHandler + }); + new Ajax.Updater({ success: this.element }, this.url, options); + } else { + var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); + Object.extend(options, { + parameters: params, + onComplete: this._boundWrapperHandler, + onFailure: this._boundFailureHandler + }); + new Ajax.Request(this.url, options); + } + if (e) Event.stop(e); + }, + leaveEditMode: function() { + this.element.removeClassName(this.options.savingClassName); + this.removeForm(); + this.leaveHover(); + this.element.style.backgroundColor = this._originalBackground; + this.element.show(); + if (this.options.externalControl) + this.options.externalControl.show(); + this._saving = false; + this._editing = false; + this._oldInnerHTML = null; + this.triggerCallback('onLeaveEditMode'); + }, + leaveHover: function(e) { + if (this.options.hoverClassName) + this.element.removeClassName(this.options.hoverClassName); + if (this._saving) return; + this.triggerCallback('onLeaveHover'); + }, + loadExternalText: function() { + this._form.addClassName(this.options.loadingClassName); + this._controls.editor.disabled = true; + var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); + Object.extend(options, { + parameters: 'editorId=' + encodeURIComponent(this.element.id), + onComplete: Prototype.emptyFunction, + onSuccess: function(transport) { + this._form.removeClassName(this.options.loadingClassName); + var text = transport.responseText; + if (this.options.stripLoadedTextTags) + text = text.stripTags(); + this._controls.editor.value = text; + this._controls.editor.disabled = false; + this.postProcessEditField(); + }.bind(this), + onFailure: this._boundFailureHandler + }); + new Ajax.Request(this.options.loadTextURL, options); + }, + postProcessEditField: function() { + var fpc = this.options.fieldPostCreation; + if (fpc) + $(this._controls.editor)['focus' == fpc ? 'focus' : 'activate'](); + }, + prepareOptions: function() { + this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions); + Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks); + [this._extraDefaultOptions].flatten().compact().each(function(defs) { + Object.extend(this.options, defs); + }.bind(this)); + }, + prepareSubmission: function() { + this._saving = true; + this.removeForm(); + this.leaveHover(); + this.showSaving(); + }, + registerListeners: function() { + this._listeners = { }; + var listener; + $H(Ajax.InPlaceEditor.Listeners).each(function(pair) { + listener = this[pair.value].bind(this); + this._listeners[pair.key] = listener; + if (!this.options.externalControlOnly) + this.element.observe(pair.key, listener); + if (this.options.externalControl) + this.options.externalControl.observe(pair.key, listener); + }.bind(this)); + }, + removeForm: function() { + if (!this._form) return; + this._form.remove(); + this._form = null; + this._controls = { }; + }, + showSaving: function() { + this._oldInnerHTML = this.element.innerHTML; + this.element.innerHTML = this.options.savingText; + this.element.addClassName(this.options.savingClassName); + this.element.style.backgroundColor = this._originalBackground; + this.element.show(); + }, + triggerCallback: function(cbName, arg) { + if ('function' == typeof this.options[cbName]) { + this.options[cbName](this, arg); + } + }, + unregisterListeners: function() { + $H(this._listeners).each(function(pair) { + if (!this.options.externalControlOnly) + this.element.stopObserving(pair.key, pair.value); + if (this.options.externalControl) + this.options.externalControl.stopObserving(pair.key, pair.value); + }.bind(this)); + }, + wrapUp: function(transport) { + this.leaveEditMode(); + // Can't use triggerCallback due to backward compatibility: requires + // binding + direct element + this._boundComplete(transport, this.element); + } +}); + +Object.extend(Ajax.InPlaceEditor.prototype, { + dispose: Ajax.InPlaceEditor.prototype.destroy +}); + +Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, { + initialize: function($super, element, url, options) { + this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions; + $super(element, url, options); + }, + + createEditField: function() { + var list = document.createElement('select'); + list.name = this.options.paramName; + list.size = 1; + this._controls.editor = list; + this._collection = this.options.collection || []; + if (this.options.loadCollectionURL) + this.loadCollection(); + else + this.checkForExternalText(); + this._form.appendChild(this._controls.editor); + }, + + loadCollection: function() { + this._form.addClassName(this.options.loadingClassName); + this.showLoadingText(this.options.loadingCollectionText); + var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); + Object.extend(options, { + parameters: 'editorId=' + encodeURIComponent(this.element.id), + onComplete: Prototype.emptyFunction, + onSuccess: function(transport) { + var js = transport.responseText.strip(); + if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check + throw('Server returned an invalid collection representation.'); + this._collection = eval(js); + this.checkForExternalText(); + }.bind(this), + onFailure: this.onFailure + }); + new Ajax.Request(this.options.loadCollectionURL, options); + }, + + showLoadingText: function(text) { + this._controls.editor.disabled = true; + var tempOption = this._controls.editor.firstChild; + if (!tempOption) { + tempOption = document.createElement('option'); + tempOption.value = ''; + this._controls.editor.appendChild(tempOption); + tempOption.selected = true; + } + tempOption.update((text || '').stripScripts().stripTags()); + }, + + checkForExternalText: function() { + this._text = this.getText(); + if (this.options.loadTextURL) + this.loadExternalText(); + else + this.buildOptionList(); + }, + + loadExternalText: function() { + this.showLoadingText(this.options.loadingText); + var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); + Object.extend(options, { + parameters: 'editorId=' + encodeURIComponent(this.element.id), + onComplete: Prototype.emptyFunction, + onSuccess: function(transport) { + this._text = transport.responseText.strip(); + this.buildOptionList(); + }.bind(this), + onFailure: this.onFailure + }); + new Ajax.Request(this.options.loadTextURL, options); + }, + + buildOptionList: function() { + this._form.removeClassName(this.options.loadingClassName); + this._collection = this._collection.map(function(entry) { + return 2 === entry.length ? entry : [entry, entry].flatten(); + }); + var marker = ('value' in this.options) ? this.options.value : this._text; + var textFound = this._collection.any(function(entry) { + return entry[0] == marker; + }.bind(this)); + this._controls.editor.update(''); + var option; + this._collection.each(function(entry, index) { + option = document.createElement('option'); + option.value = entry[0]; + option.selected = textFound ? entry[0] == marker : 0 == index; + option.appendChild(document.createTextNode(entry[1])); + this._controls.editor.appendChild(option); + }.bind(this)); + this._controls.editor.disabled = false; + Field.scrollFreeActivate(this._controls.editor); + } +}); + +//**** DEPRECATION LAYER FOR InPlace[Collection]Editor! **** +//**** This only exists for a while, in order to let **** +//**** users adapt to the new API. Read up on the new **** +//**** API and convert your code to it ASAP! **** + +Ajax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) { + if (!options) return; + function fallback(name, expr) { + if (name in options || expr === undefined) return; + options[name] = expr; + }; + fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' : + options.cancelLink == options.cancelButton == false ? false : undefined))); + fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' : + options.okLink == options.okButton == false ? false : undefined))); + fallback('highlightColor', options.highlightcolor); + fallback('highlightEndColor', options.highlightendcolor); +}; + +Object.extend(Ajax.InPlaceEditor, { + DefaultOptions: { + ajaxOptions: { }, + autoRows: 3, // Use when multi-line w/ rows == 1 + cancelControl: 'link', // 'link'|'button'|false + cancelText: 'cancel', + clickToEditText: 'Click to edit', + externalControl: null, // id|elt + externalControlOnly: false, + fieldPostCreation: 'activate', // 'activate'|'focus'|false + formClassName: 'inplaceeditor-form', + formId: null, // id|elt + highlightColor: '#ffff99', + highlightEndColor: '#ffffff', + hoverClassName: '', + htmlResponse: true, + loadingClassName: 'inplaceeditor-loading', + loadingText: 'Loading...', + okControl: 'button', // 'link'|'button'|false + okText: 'ok', + paramName: 'value', + rows: 1, // If 1 and multi-line, uses autoRows + savingClassName: 'inplaceeditor-saving', + savingText: 'Saving...', + size: 0, + stripLoadedTextTags: false, + submitOnBlur: false, + textAfterControls: '', + textBeforeControls: '', + textBetweenControls: '' + }, + DefaultCallbacks: { + callback: function(form) { + return Form.serialize(form); + }, + onComplete: function(transport, element) { + // For backward compatibility, this one is bound to the IPE, and passes + // the element directly. It was too often customized, so we don't break it. + new Effect.Highlight(element, { + startcolor: this.options.highlightColor, keepBackgroundImage: true }); + }, + onEnterEditMode: null, + onEnterHover: function(ipe) { + ipe.element.style.backgroundColor = ipe.options.highlightColor; + if (ipe._effect) + ipe._effect.cancel(); + }, + onFailure: function(transport, ipe) { + alert('Error communication with the server: ' + transport.responseText.stripTags()); + }, + onFormCustomization: null, // Takes the IPE and its generated form, after editor, before controls. + onLeaveEditMode: null, + onLeaveHover: function(ipe) { + ipe._effect = new Effect.Highlight(ipe.element, { + startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor, + restorecolor: ipe._originalBackground, keepBackgroundImage: true + }); + } + }, + Listeners: { + click: 'enterEditMode', + keydown: 'checkForEscapeOrReturn', + mouseover: 'enterHover', + mouseout: 'leaveHover' + } +}); + +Ajax.InPlaceCollectionEditor.DefaultOptions = { + loadingCollectionText: 'Loading options...' +}; + +// Delayed observer, like Form.Element.Observer, +// but waits for delay after last key input +// Ideal for live-search fields + +Form.Element.DelayedObserver = Class.create({ + initialize: function(element, delay, callback) { + this.delay = delay || 0.5; + this.element = $(element); + this.callback = callback; + this.timer = null; + this.lastValue = $F(this.element); + Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this)); + }, + delayedListener: function(event) { + if(this.lastValue == $F(this.element)) return; + if(this.timer) clearTimeout(this.timer); + this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000); + this.lastValue = $F(this.element); + }, + onTimerEvent: function() { + this.timer = null; + this.callback(this.element, $F(this.element)); + } +}); \ No newline at end of file diff --git a/public/javascripts/dragdrop.js b/public/javascripts/dragdrop.js new file mode 100644 index 0000000..15c6dbc --- /dev/null +++ b/public/javascripts/dragdrop.js @@ -0,0 +1,974 @@ +// script.aculo.us dragdrop.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009 + +// Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// +// script.aculo.us is freely distributable under the terms of an MIT-style license. +// For details, see the script.aculo.us web site: http://script.aculo.us/ + +if(Object.isUndefined(Effect)) + throw("dragdrop.js requires including script.aculo.us' effects.js library"); + +var Droppables = { + drops: [], + + remove: function(element) { + this.drops = this.drops.reject(function(d) { return d.element==$(element) }); + }, + + add: function(element) { + element = $(element); + var options = Object.extend({ + greedy: true, + hoverclass: null, + tree: false + }, arguments[1] || { }); + + // cache containers + if(options.containment) { + options._containers = []; + var containment = options.containment; + if(Object.isArray(containment)) { + containment.each( function(c) { options._containers.push($(c)) }); + } else { + options._containers.push($(containment)); + } + } + + if(options.accept) options.accept = [options.accept].flatten(); + + Element.makePositioned(element); // fix IE + options.element = element; + + this.drops.push(options); + }, + + findDeepestChild: function(drops) { + deepest = drops[0]; + + for (i = 1; i < drops.length; ++i) + if (Element.isParent(drops[i].element, deepest.element)) + deepest = drops[i]; + + return deepest; + }, + + isContained: function(element, drop) { + var containmentNode; + if(drop.tree) { + containmentNode = element.treeNode; + } else { + containmentNode = element.parentNode; + } + return drop._containers.detect(function(c) { return containmentNode == c }); + }, + + isAffected: function(point, element, drop) { + return ( + (drop.element!=element) && + ((!drop._containers) || + this.isContained(element, drop)) && + ((!drop.accept) || + (Element.classNames(element).detect( + function(v) { return drop.accept.include(v) } ) )) && + Position.within(drop.element, point[0], point[1]) ); + }, + + deactivate: function(drop) { + if(drop.hoverclass) + Element.removeClassName(drop.element, drop.hoverclass); + this.last_active = null; + }, + + activate: function(drop) { + if(drop.hoverclass) + Element.addClassName(drop.element, drop.hoverclass); + this.last_active = drop; + }, + + show: function(point, element) { + if(!this.drops.length) return; + var drop, affected = []; + + this.drops.each( function(drop) { + if(Droppables.isAffected(point, element, drop)) + affected.push(drop); + }); + + if(affected.length>0) + drop = Droppables.findDeepestChild(affected); + + if(this.last_active && this.last_active != drop) this.deactivate(this.last_active); + if (drop) { + Position.within(drop.element, point[0], point[1]); + if(drop.onHover) + drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element)); + + if (drop != this.last_active) Droppables.activate(drop); + } + }, + + fire: function(event, element) { + if(!this.last_active) return; + Position.prepare(); + + if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active)) + if (this.last_active.onDrop) { + this.last_active.onDrop(element, this.last_active.element, event); + return true; + } + }, + + reset: function() { + if(this.last_active) + this.deactivate(this.last_active); + } +}; + +var Draggables = { + drags: [], + observers: [], + + register: function(draggable) { + if(this.drags.length == 0) { + this.eventMouseUp = this.endDrag.bindAsEventListener(this); + this.eventMouseMove = this.updateDrag.bindAsEventListener(this); + this.eventKeypress = this.keyPress.bindAsEventListener(this); + + Event.observe(document, "mouseup", this.eventMouseUp); + Event.observe(document, "mousemove", this.eventMouseMove); + Event.observe(document, "keypress", this.eventKeypress); + } + this.drags.push(draggable); + }, + + unregister: function(draggable) { + this.drags = this.drags.reject(function(d) { return d==draggable }); + if(this.drags.length == 0) { + Event.stopObserving(document, "mouseup", this.eventMouseUp); + Event.stopObserving(document, "mousemove", this.eventMouseMove); + Event.stopObserving(document, "keypress", this.eventKeypress); + } + }, + + activate: function(draggable) { + if(draggable.options.delay) { + this._timeout = setTimeout(function() { + Draggables._timeout = null; + window.focus(); + Draggables.activeDraggable = draggable; + }.bind(this), draggable.options.delay); + } else { + window.focus(); // allows keypress events if window isn't currently focused, fails for Safari + this.activeDraggable = draggable; + } + }, + + deactivate: function() { + this.activeDraggable = null; + }, + + updateDrag: function(event) { + if(!this.activeDraggable) return; + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + // Mozilla-based browsers fire successive mousemove events with + // the same coordinates, prevent needless redrawing (moz bug?) + if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return; + this._lastPointer = pointer; + + this.activeDraggable.updateDrag(event, pointer); + }, + + endDrag: function(event) { + if(this._timeout) { + clearTimeout(this._timeout); + this._timeout = null; + } + if(!this.activeDraggable) return; + this._lastPointer = null; + this.activeDraggable.endDrag(event); + this.activeDraggable = null; + }, + + keyPress: function(event) { + if(this.activeDraggable) + this.activeDraggable.keyPress(event); + }, + + addObserver: function(observer) { + this.observers.push(observer); + this._cacheObserverCallbacks(); + }, + + removeObserver: function(element) { // element instead of observer fixes mem leaks + this.observers = this.observers.reject( function(o) { return o.element==element }); + this._cacheObserverCallbacks(); + }, + + notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag' + if(this[eventName+'Count'] > 0) + this.observers.each( function(o) { + if(o[eventName]) o[eventName](eventName, draggable, event); + }); + if(draggable.options[eventName]) draggable.options[eventName](draggable, event); + }, + + _cacheObserverCallbacks: function() { + ['onStart','onEnd','onDrag'].each( function(eventName) { + Draggables[eventName+'Count'] = Draggables.observers.select( + function(o) { return o[eventName]; } + ).length; + }); + } +}; + +/*--------------------------------------------------------------------------*/ + +var Draggable = Class.create({ + initialize: function(element) { + var defaults = { + handle: false, + reverteffect: function(element, top_offset, left_offset) { + var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02; + new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur, + queue: {scope:'_draggable', position:'end'} + }); + }, + endeffect: function(element) { + var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0; + new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity, + queue: {scope:'_draggable', position:'end'}, + afterFinish: function(){ + Draggable._dragging[element] = false + } + }); + }, + zindex: 1000, + revert: false, + quiet: false, + scroll: false, + scrollSensitivity: 20, + scrollSpeed: 15, + snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] } + delay: 0 + }; + + if(!arguments[1] || Object.isUndefined(arguments[1].endeffect)) + Object.extend(defaults, { + starteffect: function(element) { + element._opacity = Element.getOpacity(element); + Draggable._dragging[element] = true; + new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7}); + } + }); + + var options = Object.extend(defaults, arguments[1] || { }); + + this.element = $(element); + + if(options.handle && Object.isString(options.handle)) + this.handle = this.element.down('.'+options.handle, 0); + + if(!this.handle) this.handle = $(options.handle); + if(!this.handle) this.handle = this.element; + + if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) { + options.scroll = $(options.scroll); + this._isScrollChild = Element.childOf(this.element, options.scroll); + } + + Element.makePositioned(this.element); // fix IE + + this.options = options; + this.dragging = false; + + this.eventMouseDown = this.initDrag.bindAsEventListener(this); + Event.observe(this.handle, "mousedown", this.eventMouseDown); + + Draggables.register(this); + }, + + destroy: function() { + Event.stopObserving(this.handle, "mousedown", this.eventMouseDown); + Draggables.unregister(this); + }, + + currentDelta: function() { + return([ + parseInt(Element.getStyle(this.element,'left') || '0'), + parseInt(Element.getStyle(this.element,'top') || '0')]); + }, + + initDrag: function(event) { + if(!Object.isUndefined(Draggable._dragging[this.element]) && + Draggable._dragging[this.element]) return; + if(Event.isLeftClick(event)) { + // abort on form elements, fixes a Firefox issue + var src = Event.element(event); + if((tag_name = src.tagName.toUpperCase()) && ( + tag_name=='INPUT' || + tag_name=='SELECT' || + tag_name=='OPTION' || + tag_name=='BUTTON' || + tag_name=='TEXTAREA')) return; + + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + var pos = this.element.cumulativeOffset(); + this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) }); + + Draggables.activate(this); + Event.stop(event); + } + }, + + startDrag: function(event) { + this.dragging = true; + if(!this.delta) + this.delta = this.currentDelta(); + + if(this.options.zindex) { + this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0); + this.element.style.zIndex = this.options.zindex; + } + + if(this.options.ghosting) { + this._clone = this.element.cloneNode(true); + this._originallyAbsolute = (this.element.getStyle('position') == 'absolute'); + if (!this._originallyAbsolute) + Position.absolutize(this.element); + this.element.parentNode.insertBefore(this._clone, this.element); + } + + if(this.options.scroll) { + if (this.options.scroll == window) { + var where = this._getWindowScroll(this.options.scroll); + this.originalScrollLeft = where.left; + this.originalScrollTop = where.top; + } else { + this.originalScrollLeft = this.options.scroll.scrollLeft; + this.originalScrollTop = this.options.scroll.scrollTop; + } + } + + Draggables.notify('onStart', this, event); + + if(this.options.starteffect) this.options.starteffect(this.element); + }, + + updateDrag: function(event, pointer) { + if(!this.dragging) this.startDrag(event); + + if(!this.options.quiet){ + Position.prepare(); + Droppables.show(pointer, this.element); + } + + Draggables.notify('onDrag', this, event); + + this.draw(pointer); + if(this.options.change) this.options.change(this); + + if(this.options.scroll) { + this.stopScrolling(); + + var p; + if (this.options.scroll == window) { + with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; } + } else { + p = Position.page(this.options.scroll); + p[0] += this.options.scroll.scrollLeft + Position.deltaX; + p[1] += this.options.scroll.scrollTop + Position.deltaY; + p.push(p[0]+this.options.scroll.offsetWidth); + p.push(p[1]+this.options.scroll.offsetHeight); + } + var speed = [0,0]; + if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity); + if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity); + if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity); + if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity); + this.startScrolling(speed); + } + + // fix AppleWebKit rendering + if(Prototype.Browser.WebKit) window.scrollBy(0,0); + + Event.stop(event); + }, + + finishDrag: function(event, success) { + this.dragging = false; + + if(this.options.quiet){ + Position.prepare(); + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + Droppables.show(pointer, this.element); + } + + if(this.options.ghosting) { + if (!this._originallyAbsolute) + Position.relativize(this.element); + delete this._originallyAbsolute; + Element.remove(this._clone); + this._clone = null; + } + + var dropped = false; + if(success) { + dropped = Droppables.fire(event, this.element); + if (!dropped) dropped = false; + } + if(dropped && this.options.onDropped) this.options.onDropped(this.element); + Draggables.notify('onEnd', this, event); + + var revert = this.options.revert; + if(revert && Object.isFunction(revert)) revert = revert(this.element); + + var d = this.currentDelta(); + if(revert && this.options.reverteffect) { + if (dropped == 0 || revert != 'failure') + this.options.reverteffect(this.element, + d[1]-this.delta[1], d[0]-this.delta[0]); + } else { + this.delta = d; + } + + if(this.options.zindex) + this.element.style.zIndex = this.originalZ; + + if(this.options.endeffect) + this.options.endeffect(this.element); + + Draggables.deactivate(this); + Droppables.reset(); + }, + + keyPress: function(event) { + if(event.keyCode!=Event.KEY_ESC) return; + this.finishDrag(event, false); + Event.stop(event); + }, + + endDrag: function(event) { + if(!this.dragging) return; + this.stopScrolling(); + this.finishDrag(event, true); + Event.stop(event); + }, + + draw: function(point) { + var pos = this.element.cumulativeOffset(); + if(this.options.ghosting) { + var r = Position.realOffset(this.element); + pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY; + } + + var d = this.currentDelta(); + pos[0] -= d[0]; pos[1] -= d[1]; + + if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) { + pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft; + pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop; + } + + var p = [0,1].map(function(i){ + return (point[i]-pos[i]-this.offset[i]) + }.bind(this)); + + if(this.options.snap) { + if(Object.isFunction(this.options.snap)) { + p = this.options.snap(p[0],p[1],this); + } else { + if(Object.isArray(this.options.snap)) { + p = p.map( function(v, i) { + return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this)); + } else { + p = p.map( function(v) { + return (v/this.options.snap).round()*this.options.snap }.bind(this)); + } + }} + + var style = this.element.style; + if((!this.options.constraint) || (this.options.constraint=='horizontal')) + style.left = p[0] + "px"; + if((!this.options.constraint) || (this.options.constraint=='vertical')) + style.top = p[1] + "px"; + + if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering + }, + + stopScrolling: function() { + if(this.scrollInterval) { + clearInterval(this.scrollInterval); + this.scrollInterval = null; + Draggables._lastScrollPointer = null; + } + }, + + startScrolling: function(speed) { + if(!(speed[0] || speed[1])) return; + this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed]; + this.lastScrolled = new Date(); + this.scrollInterval = setInterval(this.scroll.bind(this), 10); + }, + + scroll: function() { + var current = new Date(); + var delta = current - this.lastScrolled; + this.lastScrolled = current; + if(this.options.scroll == window) { + with (this._getWindowScroll(this.options.scroll)) { + if (this.scrollSpeed[0] || this.scrollSpeed[1]) { + var d = delta / 1000; + this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] ); + } + } + } else { + this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000; + this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000; + } + + Position.prepare(); + Droppables.show(Draggables._lastPointer, this.element); + Draggables.notify('onDrag', this); + if (this._isScrollChild) { + Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer); + Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000; + Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000; + if (Draggables._lastScrollPointer[0] < 0) + Draggables._lastScrollPointer[0] = 0; + if (Draggables._lastScrollPointer[1] < 0) + Draggables._lastScrollPointer[1] = 0; + this.draw(Draggables._lastScrollPointer); + } + + if(this.options.change) this.options.change(this); + }, + + _getWindowScroll: function(w) { + var T, L, W, H; + with (w.document) { + if (w.document.documentElement && documentElement.scrollTop) { + T = documentElement.scrollTop; + L = documentElement.scrollLeft; + } else if (w.document.body) { + T = body.scrollTop; + L = body.scrollLeft; + } + if (w.innerWidth) { + W = w.innerWidth; + H = w.innerHeight; + } else if (w.document.documentElement && documentElement.clientWidth) { + W = documentElement.clientWidth; + H = documentElement.clientHeight; + } else { + W = body.offsetWidth; + H = body.offsetHeight; + } + } + return { top: T, left: L, width: W, height: H }; + } +}); + +Draggable._dragging = { }; + +/*--------------------------------------------------------------------------*/ + +var SortableObserver = Class.create({ + initialize: function(element, observer) { + this.element = $(element); + this.observer = observer; + this.lastValue = Sortable.serialize(this.element); + }, + + onStart: function() { + this.lastValue = Sortable.serialize(this.element); + }, + + onEnd: function() { + Sortable.unmark(); + if(this.lastValue != Sortable.serialize(this.element)) + this.observer(this.element) + } +}); + +var Sortable = { + SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/, + + sortables: { }, + + _findRootElement: function(element) { + while (element.tagName.toUpperCase() != "BODY") { + if(element.id && Sortable.sortables[element.id]) return element; + element = element.parentNode; + } + }, + + options: function(element) { + element = Sortable._findRootElement($(element)); + if(!element) return; + return Sortable.sortables[element.id]; + }, + + destroy: function(element){ + element = $(element); + var s = Sortable.sortables[element.id]; + + if(s) { + Draggables.removeObserver(s.element); + s.droppables.each(function(d){ Droppables.remove(d) }); + s.draggables.invoke('destroy'); + + delete Sortable.sortables[s.element.id]; + } + }, + + create: function(element) { + element = $(element); + var options = Object.extend({ + element: element, + tag: 'li', // assumes li children, override with tag: 'tagname' + dropOnEmpty: false, + tree: false, + treeTag: 'ul', + overlap: 'vertical', // one of 'vertical', 'horizontal' + constraint: 'vertical', // one of 'vertical', 'horizontal', false + containment: element, // also takes array of elements (or id's); or false + handle: false, // or a CSS class + only: false, + delay: 0, + hoverclass: null, + ghosting: false, + quiet: false, + scroll: false, + scrollSensitivity: 20, + scrollSpeed: 15, + format: this.SERIALIZE_RULE, + + // these take arrays of elements or ids and can be + // used for better initialization performance + elements: false, + handles: false, + + onChange: Prototype.emptyFunction, + onUpdate: Prototype.emptyFunction + }, arguments[1] || { }); + + // clear any old sortable with same element + this.destroy(element); + + // build options for the draggables + var options_for_draggable = { + revert: true, + quiet: options.quiet, + scroll: options.scroll, + scrollSpeed: options.scrollSpeed, + scrollSensitivity: options.scrollSensitivity, + delay: options.delay, + ghosting: options.ghosting, + constraint: options.constraint, + handle: options.handle }; + + if(options.starteffect) + options_for_draggable.starteffect = options.starteffect; + + if(options.reverteffect) + options_for_draggable.reverteffect = options.reverteffect; + else + if(options.ghosting) options_for_draggable.reverteffect = function(element) { + element.style.top = 0; + element.style.left = 0; + }; + + if(options.endeffect) + options_for_draggable.endeffect = options.endeffect; + + if(options.zindex) + options_for_draggable.zindex = options.zindex; + + // build options for the droppables + var options_for_droppable = { + overlap: options.overlap, + containment: options.containment, + tree: options.tree, + hoverclass: options.hoverclass, + onHover: Sortable.onHover + }; + + var options_for_tree = { + onHover: Sortable.onEmptyHover, + overlap: options.overlap, + containment: options.containment, + hoverclass: options.hoverclass + }; + + // fix for gecko engine + Element.cleanWhitespace(element); + + options.draggables = []; + options.droppables = []; + + // drop on empty handling + if(options.dropOnEmpty || options.tree) { + Droppables.add(element, options_for_tree); + options.droppables.push(element); + } + + (options.elements || this.findElements(element, options) || []).each( function(e,i) { + var handle = options.handles ? $(options.handles[i]) : + (options.handle ? $(e).select('.' + options.handle)[0] : e); + options.draggables.push( + new Draggable(e, Object.extend(options_for_draggable, { handle: handle }))); + Droppables.add(e, options_for_droppable); + if(options.tree) e.treeNode = element; + options.droppables.push(e); + }); + + if(options.tree) { + (Sortable.findTreeElements(element, options) || []).each( function(e) { + Droppables.add(e, options_for_tree); + e.treeNode = element; + options.droppables.push(e); + }); + } + + // keep reference + this.sortables[element.identify()] = options; + + // for onupdate + Draggables.addObserver(new SortableObserver(element, options.onUpdate)); + + }, + + // return all suitable-for-sortable elements in a guaranteed order + findElements: function(element, options) { + return Element.findChildren( + element, options.only, options.tree ? true : false, options.tag); + }, + + findTreeElements: function(element, options) { + return Element.findChildren( + element, options.only, options.tree ? true : false, options.treeTag); + }, + + onHover: function(element, dropon, overlap) { + if(Element.isParent(dropon, element)) return; + + if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) { + return; + } else if(overlap>0.5) { + Sortable.mark(dropon, 'before'); + if(dropon.previousSibling != element) { + var oldParentNode = element.parentNode; + element.style.visibility = "hidden"; // fix gecko rendering + dropon.parentNode.insertBefore(element, dropon); + if(dropon.parentNode!=oldParentNode) + Sortable.options(oldParentNode).onChange(element); + Sortable.options(dropon.parentNode).onChange(element); + } + } else { + Sortable.mark(dropon, 'after'); + var nextElement = dropon.nextSibling || null; + if(nextElement != element) { + var oldParentNode = element.parentNode; + element.style.visibility = "hidden"; // fix gecko rendering + dropon.parentNode.insertBefore(element, nextElement); + if(dropon.parentNode!=oldParentNode) + Sortable.options(oldParentNode).onChange(element); + Sortable.options(dropon.parentNode).onChange(element); + } + } + }, + + onEmptyHover: function(element, dropon, overlap) { + var oldParentNode = element.parentNode; + var droponOptions = Sortable.options(dropon); + + if(!Element.isParent(dropon, element)) { + var index; + + var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only}); + var child = null; + + if(children) { + var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap); + + for (index = 0; index < children.length; index += 1) { + if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) { + offset -= Element.offsetSize (children[index], droponOptions.overlap); + } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) { + child = index + 1 < children.length ? children[index + 1] : null; + break; + } else { + child = children[index]; + break; + } + } + } + + dropon.insertBefore(element, child); + + Sortable.options(oldParentNode).onChange(element); + droponOptions.onChange(element); + } + }, + + unmark: function() { + if(Sortable._marker) Sortable._marker.hide(); + }, + + mark: function(dropon, position) { + // mark on ghosting only + var sortable = Sortable.options(dropon.parentNode); + if(sortable && !sortable.ghosting) return; + + if(!Sortable._marker) { + Sortable._marker = + ($('dropmarker') || Element.extend(document.createElement('DIV'))). + hide().addClassName('dropmarker').setStyle({position:'absolute'}); + document.getElementsByTagName("body").item(0).appendChild(Sortable._marker); + } + var offsets = dropon.cumulativeOffset(); + Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'}); + + if(position=='after') + if(sortable.overlap == 'horizontal') + Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'}); + else + Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'}); + + Sortable._marker.show(); + }, + + _tree: function(element, options, parent) { + var children = Sortable.findElements(element, options) || []; + + for (var i = 0; i < children.length; ++i) { + var match = children[i].id.match(options.format); + + if (!match) continue; + + var child = { + id: encodeURIComponent(match ? match[1] : null), + element: element, + parent: parent, + children: [], + position: parent.children.length, + container: $(children[i]).down(options.treeTag) + }; + + /* Get the element containing the children and recurse over it */ + if (child.container) + this._tree(child.container, options, child); + + parent.children.push (child); + } + + return parent; + }, + + tree: function(element) { + element = $(element); + var sortableOptions = this.options(element); + var options = Object.extend({ + tag: sortableOptions.tag, + treeTag: sortableOptions.treeTag, + only: sortableOptions.only, + name: element.id, + format: sortableOptions.format + }, arguments[1] || { }); + + var root = { + id: null, + parent: null, + children: [], + container: element, + position: 0 + }; + + return Sortable._tree(element, options, root); + }, + + /* Construct a [i] index for a particular node */ + _constructIndex: function(node) { + var index = ''; + do { + if (node.id) index = '[' + node.position + ']' + index; + } while ((node = node.parent) != null); + return index; + }, + + sequence: function(element) { + element = $(element); + var options = Object.extend(this.options(element), arguments[1] || { }); + + return $(this.findElements(element, options) || []).map( function(item) { + return item.id.match(options.format) ? item.id.match(options.format)[1] : ''; + }); + }, + + setSequence: function(element, new_sequence) { + element = $(element); + var options = Object.extend(this.options(element), arguments[2] || { }); + + var nodeMap = { }; + this.findElements(element, options).each( function(n) { + if (n.id.match(options.format)) + nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode]; + n.parentNode.removeChild(n); + }); + + new_sequence.each(function(ident) { + var n = nodeMap[ident]; + if (n) { + n[1].appendChild(n[0]); + delete nodeMap[ident]; + } + }); + }, + + serialize: function(element) { + element = $(element); + var options = Object.extend(Sortable.options(element), arguments[1] || { }); + var name = encodeURIComponent( + (arguments[1] && arguments[1].name) ? arguments[1].name : element.id); + + if (options.tree) { + return Sortable.tree(element, arguments[1]).children.map( function (item) { + return [name + Sortable._constructIndex(item) + "[id]=" + + encodeURIComponent(item.id)].concat(item.children.map(arguments.callee)); + }).flatten().join('&'); + } else { + return Sortable.sequence(element, arguments[1]).map( function(item) { + return name + "[]=" + encodeURIComponent(item); + }).join('&'); + } + } +}; + +// Returns true if child is contained within element +Element.isParent = function(child, element) { + if (!child.parentNode || child == element) return false; + if (child.parentNode == element) return true; + return Element.isParent(child.parentNode, element); +}; + +Element.findChildren = function(element, only, recursive, tagName) { + if(!element.hasChildNodes()) return null; + tagName = tagName.toUpperCase(); + if(only) only = [only].flatten(); + var elements = []; + $A(element.childNodes).each( function(e) { + if(e.tagName && e.tagName.toUpperCase()==tagName && + (!only || (Element.classNames(e).detect(function(v) { return only.include(v) })))) + elements.push(e); + if(recursive) { + var grandchildren = Element.findChildren(e, only, recursive, tagName); + if(grandchildren) elements.push(grandchildren); + } + }); + + return (elements.length>0 ? elements.flatten() : []); +}; + +Element.offsetSize = function (element, type) { + return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')]; +}; \ No newline at end of file diff --git a/public/javascripts/effects.js b/public/javascripts/effects.js new file mode 100644 index 0000000..c81e6c7 --- /dev/null +++ b/public/javascripts/effects.js @@ -0,0 +1,1123 @@ +// script.aculo.us effects.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009 + +// Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// Contributors: +// Justin Palmer (http://encytemedia.com/) +// Mark Pilgrim (http://diveintomark.org/) +// Martin Bialasinki +// +// script.aculo.us is freely distributable under the terms of an MIT-style license. +// For details, see the script.aculo.us web site: http://script.aculo.us/ + +// converts rgb() and #xxx to #xxxxxx format, +// returns self (or first argument) if not convertable +String.prototype.parseColor = function() { + var color = '#'; + if (this.slice(0,4) == 'rgb(') { + var cols = this.slice(4,this.length-1).split(','); + var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3); + } else { + if (this.slice(0,1) == '#') { + if (this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase(); + if (this.length==7) color = this.toLowerCase(); + } + } + return (color.length==7 ? color : (arguments[0] || this)); +}; + +/*--------------------------------------------------------------------------*/ + +Element.collectTextNodes = function(element) { + return $A($(element).childNodes).collect( function(node) { + return (node.nodeType==3 ? node.nodeValue : + (node.hasChildNodes() ? Element.collectTextNodes(node) : '')); + }).flatten().join(''); +}; + +Element.collectTextNodesIgnoreClass = function(element, className) { + return $A($(element).childNodes).collect( function(node) { + return (node.nodeType==3 ? node.nodeValue : + ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? + Element.collectTextNodesIgnoreClass(node, className) : '')); + }).flatten().join(''); +}; + +Element.setContentZoom = function(element, percent) { + element = $(element); + element.setStyle({fontSize: (percent/100) + 'em'}); + if (Prototype.Browser.WebKit) window.scrollBy(0,0); + return element; +}; + +Element.getInlineOpacity = function(element){ + return $(element).style.opacity || ''; +}; + +Element.forceRerendering = function(element) { + try { + element = $(element); + var n = document.createTextNode(' '); + element.appendChild(n); + element.removeChild(n); + } catch(e) { } +}; + +/*--------------------------------------------------------------------------*/ + +var Effect = { + _elementDoesNotExistError: { + name: 'ElementDoesNotExistError', + message: 'The specified DOM element does not exist, but is required for this effect to operate' + }, + Transitions: { + linear: Prototype.K, + sinoidal: function(pos) { + return (-Math.cos(pos*Math.PI)/2) + .5; + }, + reverse: function(pos) { + return 1-pos; + }, + flicker: function(pos) { + var pos = ((-Math.cos(pos*Math.PI)/4) + .75) + Math.random()/4; + return pos > 1 ? 1 : pos; + }, + wobble: function(pos) { + return (-Math.cos(pos*Math.PI*(9*pos))/2) + .5; + }, + pulse: function(pos, pulses) { + return (-Math.cos((pos*((pulses||5)-.5)*2)*Math.PI)/2) + .5; + }, + spring: function(pos) { + return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6)); + }, + none: function(pos) { + return 0; + }, + full: function(pos) { + return 1; + } + }, + DefaultOptions: { + duration: 1.0, // seconds + fps: 100, // 100= assume 66fps max. + sync: false, // true for combining + from: 0.0, + to: 1.0, + delay: 0.0, + queue: 'parallel' + }, + tagifyText: function(element) { + var tagifyStyle = 'position:relative'; + if (Prototype.Browser.IE) tagifyStyle += ';zoom:1'; + + element = $(element); + $A(element.childNodes).each( function(child) { + if (child.nodeType==3) { + child.nodeValue.toArray().each( function(character) { + element.insertBefore( + new Element('span', {style: tagifyStyle}).update( + character == ' ' ? String.fromCharCode(160) : character), + child); + }); + Element.remove(child); + } + }); + }, + multiple: function(element, effect) { + var elements; + if (((typeof element == 'object') || + Object.isFunction(element)) && + (element.length)) + elements = element; + else + elements = $(element).childNodes; + + var options = Object.extend({ + speed: 0.1, + delay: 0.0 + }, arguments[2] || { }); + var masterDelay = options.delay; + + $A(elements).each( function(element, index) { + new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay })); + }); + }, + PAIRS: { + 'slide': ['SlideDown','SlideUp'], + 'blind': ['BlindDown','BlindUp'], + 'appear': ['Appear','Fade'] + }, + toggle: function(element, effect, options) { + element = $(element); + effect = (effect || 'appear').toLowerCase(); + + return Effect[ Effect.PAIRS[ effect ][ element.visible() ? 1 : 0 ] ](element, Object.extend({ + queue: { position:'end', scope:(element.id || 'global'), limit: 1 } + }, options || {})); + } +}; + +Effect.DefaultOptions.transition = Effect.Transitions.sinoidal; + +/* ------------- core effects ------------- */ + +Effect.ScopedQueue = Class.create(Enumerable, { + initialize: function() { + this.effects = []; + this.interval = null; + }, + _each: function(iterator) { + this.effects._each(iterator); + }, + add: function(effect) { + var timestamp = new Date().getTime(); + + var position = Object.isString(effect.options.queue) ? + effect.options.queue : effect.options.queue.position; + + switch(position) { + case 'front': + // move unstarted effects after this effect + this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) { + e.startOn += effect.finishOn; + e.finishOn += effect.finishOn; + }); + break; + case 'with-last': + timestamp = this.effects.pluck('startOn').max() || timestamp; + break; + case 'end': + // start effect after last queued effect has finished + timestamp = this.effects.pluck('finishOn').max() || timestamp; + break; + } + + effect.startOn += timestamp; + effect.finishOn += timestamp; + + if (!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit)) + this.effects.push(effect); + + if (!this.interval) + this.interval = setInterval(this.loop.bind(this), 15); + }, + remove: function(effect) { + this.effects = this.effects.reject(function(e) { return e==effect }); + if (this.effects.length == 0) { + clearInterval(this.interval); + this.interval = null; + } + }, + loop: function() { + var timePos = new Date().getTime(); + for(var i=0, len=this.effects.length;i= this.startOn) { + if (timePos >= this.finishOn) { + this.render(1.0); + this.cancel(); + this.event('beforeFinish'); + if (this.finish) this.finish(); + this.event('afterFinish'); + return; + } + var pos = (timePos - this.startOn) / this.totalTime, + frame = (pos * this.totalFrames).round(); + if (frame > this.currentFrame) { + this.render(pos); + this.currentFrame = frame; + } + } + }, + cancel: function() { + if (!this.options.sync) + Effect.Queues.get(Object.isString(this.options.queue) ? + 'global' : this.options.queue.scope).remove(this); + this.state = 'finished'; + }, + event: function(eventName) { + if (this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this); + if (this.options[eventName]) this.options[eventName](this); + }, + inspect: function() { + var data = $H(); + for(property in this) + if (!Object.isFunction(this[property])) data.set(property, this[property]); + return '#'; + } +}); + +Effect.Parallel = Class.create(Effect.Base, { + initialize: function(effects) { + this.effects = effects || []; + this.start(arguments[1]); + }, + update: function(position) { + this.effects.invoke('render', position); + }, + finish: function(position) { + this.effects.each( function(effect) { + effect.render(1.0); + effect.cancel(); + effect.event('beforeFinish'); + if (effect.finish) effect.finish(position); + effect.event('afterFinish'); + }); + } +}); + +Effect.Tween = Class.create(Effect.Base, { + initialize: function(object, from, to) { + object = Object.isString(object) ? $(object) : object; + var args = $A(arguments), method = args.last(), + options = args.length == 5 ? args[3] : null; + this.method = Object.isFunction(method) ? method.bind(object) : + Object.isFunction(object[method]) ? object[method].bind(object) : + function(value) { object[method] = value }; + this.start(Object.extend({ from: from, to: to }, options || { })); + }, + update: function(position) { + this.method(position); + } +}); + +Effect.Event = Class.create(Effect.Base, { + initialize: function() { + this.start(Object.extend({ duration: 0 }, arguments[0] || { })); + }, + update: Prototype.emptyFunction +}); + +Effect.Opacity = Class.create(Effect.Base, { + initialize: function(element) { + this.element = $(element); + if (!this.element) throw(Effect._elementDoesNotExistError); + // make this work on IE on elements without 'layout' + if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout)) + this.element.setStyle({zoom: 1}); + var options = Object.extend({ + from: this.element.getOpacity() || 0.0, + to: 1.0 + }, arguments[1] || { }); + this.start(options); + }, + update: function(position) { + this.element.setOpacity(position); + } +}); + +Effect.Move = Class.create(Effect.Base, { + initialize: function(element) { + this.element = $(element); + if (!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ + x: 0, + y: 0, + mode: 'relative' + }, arguments[1] || { }); + this.start(options); + }, + setup: function() { + this.element.makePositioned(); + this.originalLeft = parseFloat(this.element.getStyle('left') || '0'); + this.originalTop = parseFloat(this.element.getStyle('top') || '0'); + if (this.options.mode == 'absolute') { + this.options.x = this.options.x - this.originalLeft; + this.options.y = this.options.y - this.originalTop; + } + }, + update: function(position) { + this.element.setStyle({ + left: (this.options.x * position + this.originalLeft).round() + 'px', + top: (this.options.y * position + this.originalTop).round() + 'px' + }); + } +}); + +// for backwards compatibility +Effect.MoveBy = function(element, toTop, toLeft) { + return new Effect.Move(element, + Object.extend({ x: toLeft, y: toTop }, arguments[3] || { })); +}; + +Effect.Scale = Class.create(Effect.Base, { + initialize: function(element, percent) { + this.element = $(element); + if (!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ + scaleX: true, + scaleY: true, + scaleContent: true, + scaleFromCenter: false, + scaleMode: 'box', // 'box' or 'contents' or { } with provided values + scaleFrom: 100.0, + scaleTo: percent + }, arguments[2] || { }); + this.start(options); + }, + setup: function() { + this.restoreAfterFinish = this.options.restoreAfterFinish || false; + this.elementPositioning = this.element.getStyle('position'); + + this.originalStyle = { }; + ['top','left','width','height','fontSize'].each( function(k) { + this.originalStyle[k] = this.element.style[k]; + }.bind(this)); + + this.originalTop = this.element.offsetTop; + this.originalLeft = this.element.offsetLeft; + + var fontSize = this.element.getStyle('font-size') || '100%'; + ['em','px','%','pt'].each( function(fontSizeType) { + if (fontSize.indexOf(fontSizeType)>0) { + this.fontSize = parseFloat(fontSize); + this.fontSizeType = fontSizeType; + } + }.bind(this)); + + this.factor = (this.options.scaleTo - this.options.scaleFrom)/100; + + this.dims = null; + if (this.options.scaleMode=='box') + this.dims = [this.element.offsetHeight, this.element.offsetWidth]; + if (/^content/.test(this.options.scaleMode)) + this.dims = [this.element.scrollHeight, this.element.scrollWidth]; + if (!this.dims) + this.dims = [this.options.scaleMode.originalHeight, + this.options.scaleMode.originalWidth]; + }, + update: function(position) { + var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position); + if (this.options.scaleContent && this.fontSize) + this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType }); + this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale); + }, + finish: function(position) { + if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle); + }, + setDimensions: function(height, width) { + var d = { }; + if (this.options.scaleX) d.width = width.round() + 'px'; + if (this.options.scaleY) d.height = height.round() + 'px'; + if (this.options.scaleFromCenter) { + var topd = (height - this.dims[0])/2; + var leftd = (width - this.dims[1])/2; + if (this.elementPositioning == 'absolute') { + if (this.options.scaleY) d.top = this.originalTop-topd + 'px'; + if (this.options.scaleX) d.left = this.originalLeft-leftd + 'px'; + } else { + if (this.options.scaleY) d.top = -topd + 'px'; + if (this.options.scaleX) d.left = -leftd + 'px'; + } + } + this.element.setStyle(d); + } +}); + +Effect.Highlight = Class.create(Effect.Base, { + initialize: function(element) { + this.element = $(element); + if (!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || { }); + this.start(options); + }, + setup: function() { + // Prevent executing on elements not in the layout flow + if (this.element.getStyle('display')=='none') { this.cancel(); return; } + // Disable background image during the effect + this.oldStyle = { }; + if (!this.options.keepBackgroundImage) { + this.oldStyle.backgroundImage = this.element.getStyle('background-image'); + this.element.setStyle({backgroundImage: 'none'}); + } + if (!this.options.endcolor) + this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff'); + if (!this.options.restorecolor) + this.options.restorecolor = this.element.getStyle('background-color'); + // init color calculations + this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this)); + this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this)); + }, + update: function(position) { + this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){ + return m+((this._base[i]+(this._delta[i]*position)).round().toColorPart()); }.bind(this)) }); + }, + finish: function() { + this.element.setStyle(Object.extend(this.oldStyle, { + backgroundColor: this.options.restorecolor + })); + } +}); + +Effect.ScrollTo = function(element) { + var options = arguments[1] || { }, + scrollOffsets = document.viewport.getScrollOffsets(), + elementOffsets = $(element).cumulativeOffset(); + + if (options.offset) elementOffsets[1] += options.offset; + + return new Effect.Tween(null, + scrollOffsets.top, + elementOffsets[1], + options, + function(p){ scrollTo(scrollOffsets.left, p.round()); } + ); +}; + +/* ------------- combination effects ------------- */ + +Effect.Fade = function(element) { + element = $(element); + var oldOpacity = element.getInlineOpacity(); + var options = Object.extend({ + from: element.getOpacity() || 1.0, + to: 0.0, + afterFinishInternal: function(effect) { + if (effect.options.to!=0) return; + effect.element.hide().setStyle({opacity: oldOpacity}); + } + }, arguments[1] || { }); + return new Effect.Opacity(element,options); +}; + +Effect.Appear = function(element) { + element = $(element); + var options = Object.extend({ + from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0), + to: 1.0, + // force Safari to render floated elements properly + afterFinishInternal: function(effect) { + effect.element.forceRerendering(); + }, + beforeSetup: function(effect) { + effect.element.setOpacity(effect.options.from).show(); + }}, arguments[1] || { }); + return new Effect.Opacity(element,options); +}; + +Effect.Puff = function(element) { + element = $(element); + var oldStyle = { + opacity: element.getInlineOpacity(), + position: element.getStyle('position'), + top: element.style.top, + left: element.style.left, + width: element.style.width, + height: element.style.height + }; + return new Effect.Parallel( + [ new Effect.Scale(element, 200, + { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }), + new Effect.Opacity(element, { sync: true, to: 0.0 } ) ], + Object.extend({ duration: 1.0, + beforeSetupInternal: function(effect) { + Position.absolutize(effect.effects[0].element); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide().setStyle(oldStyle); } + }, arguments[1] || { }) + ); +}; + +Effect.BlindUp = function(element) { + element = $(element); + element.makeClipping(); + return new Effect.Scale(element, 0, + Object.extend({ scaleContent: false, + scaleX: false, + restoreAfterFinish: true, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping(); + } + }, arguments[1] || { }) + ); +}; + +Effect.BlindDown = function(element) { + element = $(element); + var elementDimensions = element.getDimensions(); + return new Effect.Scale(element, 100, Object.extend({ + scaleContent: false, + scaleX: false, + scaleFrom: 0, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + effect.element.makeClipping().setStyle({height: '0px'}).show(); + }, + afterFinishInternal: function(effect) { + effect.element.undoClipping(); + } + }, arguments[1] || { })); +}; + +Effect.SwitchOff = function(element) { + element = $(element); + var oldOpacity = element.getInlineOpacity(); + return new Effect.Appear(element, Object.extend({ + duration: 0.4, + from: 0, + transition: Effect.Transitions.flicker, + afterFinishInternal: function(effect) { + new Effect.Scale(effect.element, 1, { + duration: 0.3, scaleFromCenter: true, + scaleX: false, scaleContent: false, restoreAfterFinish: true, + beforeSetup: function(effect) { + effect.element.makePositioned().makeClipping(); + }, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity}); + } + }); + } + }, arguments[1] || { })); +}; + +Effect.DropOut = function(element) { + element = $(element); + var oldStyle = { + top: element.getStyle('top'), + left: element.getStyle('left'), + opacity: element.getInlineOpacity() }; + return new Effect.Parallel( + [ new Effect.Move(element, {x: 0, y: 100, sync: true }), + new Effect.Opacity(element, { sync: true, to: 0.0 }) ], + Object.extend( + { duration: 0.5, + beforeSetup: function(effect) { + effect.effects[0].element.makePositioned(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle); + } + }, arguments[1] || { })); +}; + +Effect.Shake = function(element) { + element = $(element); + var options = Object.extend({ + distance: 20, + duration: 0.5 + }, arguments[1] || {}); + var distance = parseFloat(options.distance); + var split = parseFloat(options.duration) / 10.0; + var oldStyle = { + top: element.getStyle('top'), + left: element.getStyle('left') }; + return new Effect.Move(element, + { x: distance, y: 0, duration: split, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -distance, y: 0, duration: split, afterFinishInternal: function(effect) { + effect.element.undoPositioned().setStyle(oldStyle); + }}); }}); }}); }}); }}); }}); +}; + +Effect.SlideDown = function(element) { + element = $(element).cleanWhitespace(); + // SlideDown need to have the content of the element wrapped in a container element with fixed height! + var oldInnerBottom = element.down().getStyle('bottom'); + var elementDimensions = element.getDimensions(); + return new Effect.Scale(element, 100, Object.extend({ + scaleContent: false, + scaleX: false, + scaleFrom: window.opera ? 0 : 1, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + effect.element.makePositioned(); + effect.element.down().makePositioned(); + if (window.opera) effect.element.setStyle({top: ''}); + effect.element.makeClipping().setStyle({height: '0px'}).show(); + }, + afterUpdateInternal: function(effect) { + effect.element.down().setStyle({bottom: + (effect.dims[0] - effect.element.clientHeight) + 'px' }); + }, + afterFinishInternal: function(effect) { + effect.element.undoClipping().undoPositioned(); + effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); } + }, arguments[1] || { }) + ); +}; + +Effect.SlideUp = function(element) { + element = $(element).cleanWhitespace(); + var oldInnerBottom = element.down().getStyle('bottom'); + var elementDimensions = element.getDimensions(); + return new Effect.Scale(element, window.opera ? 0 : 1, + Object.extend({ scaleContent: false, + scaleX: false, + scaleMode: 'box', + scaleFrom: 100, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + effect.element.makePositioned(); + effect.element.down().makePositioned(); + if (window.opera) effect.element.setStyle({top: ''}); + effect.element.makeClipping().show(); + }, + afterUpdateInternal: function(effect) { + effect.element.down().setStyle({bottom: + (effect.dims[0] - effect.element.clientHeight) + 'px' }); + }, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping().undoPositioned(); + effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); + } + }, arguments[1] || { }) + ); +}; + +// Bug in opera makes the TD containing this element expand for a instance after finish +Effect.Squish = function(element) { + return new Effect.Scale(element, window.opera ? 1 : 0, { + restoreAfterFinish: true, + beforeSetup: function(effect) { + effect.element.makeClipping(); + }, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping(); + } + }); +}; + +Effect.Grow = function(element) { + element = $(element); + var options = Object.extend({ + direction: 'center', + moveTransition: Effect.Transitions.sinoidal, + scaleTransition: Effect.Transitions.sinoidal, + opacityTransition: Effect.Transitions.full + }, arguments[1] || { }); + var oldStyle = { + top: element.style.top, + left: element.style.left, + height: element.style.height, + width: element.style.width, + opacity: element.getInlineOpacity() }; + + var dims = element.getDimensions(); + var initialMoveX, initialMoveY; + var moveX, moveY; + + switch (options.direction) { + case 'top-left': + initialMoveX = initialMoveY = moveX = moveY = 0; + break; + case 'top-right': + initialMoveX = dims.width; + initialMoveY = moveY = 0; + moveX = -dims.width; + break; + case 'bottom-left': + initialMoveX = moveX = 0; + initialMoveY = dims.height; + moveY = -dims.height; + break; + case 'bottom-right': + initialMoveX = dims.width; + initialMoveY = dims.height; + moveX = -dims.width; + moveY = -dims.height; + break; + case 'center': + initialMoveX = dims.width / 2; + initialMoveY = dims.height / 2; + moveX = -dims.width / 2; + moveY = -dims.height / 2; + break; + } + + return new Effect.Move(element, { + x: initialMoveX, + y: initialMoveY, + duration: 0.01, + beforeSetup: function(effect) { + effect.element.hide().makeClipping().makePositioned(); + }, + afterFinishInternal: function(effect) { + new Effect.Parallel( + [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }), + new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }), + new Effect.Scale(effect.element, 100, { + scaleMode: { originalHeight: dims.height, originalWidth: dims.width }, + sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true}) + ], Object.extend({ + beforeSetup: function(effect) { + effect.effects[0].element.setStyle({height: '0px'}).show(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle); + } + }, options) + ); + } + }); +}; + +Effect.Shrink = function(element) { + element = $(element); + var options = Object.extend({ + direction: 'center', + moveTransition: Effect.Transitions.sinoidal, + scaleTransition: Effect.Transitions.sinoidal, + opacityTransition: Effect.Transitions.none + }, arguments[1] || { }); + var oldStyle = { + top: element.style.top, + left: element.style.left, + height: element.style.height, + width: element.style.width, + opacity: element.getInlineOpacity() }; + + var dims = element.getDimensions(); + var moveX, moveY; + + switch (options.direction) { + case 'top-left': + moveX = moveY = 0; + break; + case 'top-right': + moveX = dims.width; + moveY = 0; + break; + case 'bottom-left': + moveX = 0; + moveY = dims.height; + break; + case 'bottom-right': + moveX = dims.width; + moveY = dims.height; + break; + case 'center': + moveX = dims.width / 2; + moveY = dims.height / 2; + break; + } + + return new Effect.Parallel( + [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }), + new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}), + new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }) + ], Object.extend({ + beforeStartInternal: function(effect) { + effect.effects[0].element.makePositioned().makeClipping(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); } + }, options) + ); +}; + +Effect.Pulsate = function(element) { + element = $(element); + var options = arguments[1] || { }, + oldOpacity = element.getInlineOpacity(), + transition = options.transition || Effect.Transitions.linear, + reverser = function(pos){ + return 1 - transition((-Math.cos((pos*(options.pulses||5)*2)*Math.PI)/2) + .5); + }; + + return new Effect.Opacity(element, + Object.extend(Object.extend({ duration: 2.0, from: 0, + afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); } + }, options), {transition: reverser})); +}; + +Effect.Fold = function(element) { + element = $(element); + var oldStyle = { + top: element.style.top, + left: element.style.left, + width: element.style.width, + height: element.style.height }; + element.makeClipping(); + return new Effect.Scale(element, 5, Object.extend({ + scaleContent: false, + scaleX: false, + afterFinishInternal: function(effect) { + new Effect.Scale(element, 1, { + scaleContent: false, + scaleY: false, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping().setStyle(oldStyle); + } }); + }}, arguments[1] || { })); +}; + +Effect.Morph = Class.create(Effect.Base, { + initialize: function(element) { + this.element = $(element); + if (!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ + style: { } + }, arguments[1] || { }); + + if (!Object.isString(options.style)) this.style = $H(options.style); + else { + if (options.style.include(':')) + this.style = options.style.parseStyle(); + else { + this.element.addClassName(options.style); + this.style = $H(this.element.getStyles()); + this.element.removeClassName(options.style); + var css = this.element.getStyles(); + this.style = this.style.reject(function(style) { + return style.value == css[style.key]; + }); + options.afterFinishInternal = function(effect) { + effect.element.addClassName(effect.options.style); + effect.transforms.each(function(transform) { + effect.element.style[transform.style] = ''; + }); + }; + } + } + this.start(options); + }, + + setup: function(){ + function parseColor(color){ + if (!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff'; + color = color.parseColor(); + return $R(0,2).map(function(i){ + return parseInt( color.slice(i*2+1,i*2+3), 16 ); + }); + } + this.transforms = this.style.map(function(pair){ + var property = pair[0], value = pair[1], unit = null; + + if (value.parseColor('#zzzzzz') != '#zzzzzz') { + value = value.parseColor(); + unit = 'color'; + } else if (property == 'opacity') { + value = parseFloat(value); + if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout)) + this.element.setStyle({zoom: 1}); + } else if (Element.CSS_LENGTH.test(value)) { + var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/); + value = parseFloat(components[1]); + unit = (components.length == 3) ? components[2] : null; + } + + var originalValue = this.element.getStyle(property); + return { + style: property.camelize(), + originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0), + targetValue: unit=='color' ? parseColor(value) : value, + unit: unit + }; + }.bind(this)).reject(function(transform){ + return ( + (transform.originalValue == transform.targetValue) || + ( + transform.unit != 'color' && + (isNaN(transform.originalValue) || isNaN(transform.targetValue)) + ) + ); + }); + }, + update: function(position) { + var style = { }, transform, i = this.transforms.length; + while(i--) + style[(transform = this.transforms[i]).style] = + transform.unit=='color' ? '#'+ + (Math.round(transform.originalValue[0]+ + (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() + + (Math.round(transform.originalValue[1]+ + (transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() + + (Math.round(transform.originalValue[2]+ + (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() : + (transform.originalValue + + (transform.targetValue - transform.originalValue) * position).toFixed(3) + + (transform.unit === null ? '' : transform.unit); + this.element.setStyle(style, true); + } +}); + +Effect.Transform = Class.create({ + initialize: function(tracks){ + this.tracks = []; + this.options = arguments[1] || { }; + this.addTracks(tracks); + }, + addTracks: function(tracks){ + tracks.each(function(track){ + track = $H(track); + var data = track.values().first(); + this.tracks.push($H({ + ids: track.keys().first(), + effect: Effect.Morph, + options: { style: data } + })); + }.bind(this)); + return this; + }, + play: function(){ + return new Effect.Parallel( + this.tracks.map(function(track){ + var ids = track.get('ids'), effect = track.get('effect'), options = track.get('options'); + var elements = [$(ids) || $$(ids)].flatten(); + return elements.map(function(e){ return new effect(e, Object.extend({ sync:true }, options)) }); + }).flatten(), + this.options + ); + } +}); + +Element.CSS_PROPERTIES = $w( + 'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' + + 'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' + + 'borderRightColor borderRightStyle borderRightWidth borderSpacing ' + + 'borderTopColor borderTopStyle borderTopWidth bottom clip color ' + + 'fontSize fontWeight height left letterSpacing lineHeight ' + + 'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+ + 'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' + + 'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' + + 'right textIndent top width wordSpacing zIndex'); + +Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/; + +String.__parseStyleElement = document.createElement('div'); +String.prototype.parseStyle = function(){ + var style, styleRules = $H(); + if (Prototype.Browser.WebKit) + style = new Element('div',{style:this}).style; + else { + String.__parseStyleElement.innerHTML = '
    '; + style = String.__parseStyleElement.childNodes[0].style; + } + + Element.CSS_PROPERTIES.each(function(property){ + if (style[property]) styleRules.set(property, style[property]); + }); + + if (Prototype.Browser.IE && this.include('opacity')) + styleRules.set('opacity', this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]); + + return styleRules; +}; + +if (document.defaultView && document.defaultView.getComputedStyle) { + Element.getStyles = function(element) { + var css = document.defaultView.getComputedStyle($(element), null); + return Element.CSS_PROPERTIES.inject({ }, function(styles, property) { + styles[property] = css[property]; + return styles; + }); + }; +} else { + Element.getStyles = function(element) { + element = $(element); + var css = element.currentStyle, styles; + styles = Element.CSS_PROPERTIES.inject({ }, function(results, property) { + results[property] = css[property]; + return results; + }); + if (!styles.opacity) styles.opacity = element.getOpacity(); + return styles; + }; +} + +Effect.Methods = { + morph: function(element, style) { + element = $(element); + new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || { })); + return element; + }, + visualEffect: function(element, effect, options) { + element = $(element); + var s = effect.dasherize().camelize(), klass = s.charAt(0).toUpperCase() + s.substring(1); + new Effect[klass](element, options); + return element; + }, + highlight: function(element, options) { + element = $(element); + new Effect.Highlight(element, options); + return element; + } +}; + +$w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+ + 'pulsate shake puff squish switchOff dropOut').each( + function(effect) { + Effect.Methods[effect] = function(element, options){ + element = $(element); + Effect[effect.charAt(0).toUpperCase() + effect.substring(1)](element, options); + return element; + }; + } +); + +$w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each( + function(f) { Effect.Methods[f] = Element[f]; } +); + +Element.addMethods(Effect.Methods); \ No newline at end of file diff --git a/public/javascripts/prototype.js b/public/javascripts/prototype.js new file mode 100644 index 0000000..06249a6 --- /dev/null +++ b/public/javascripts/prototype.js @@ -0,0 +1,6001 @@ +/* Prototype JavaScript framework, version 1.7_rc2 + * (c) 2005-2010 Sam Stephenson + * + * Prototype is freely distributable under the terms of an MIT-style license. + * For details, see the Prototype web site: http://www.prototypejs.org/ + * + *--------------------------------------------------------------------------*/ + +var Prototype = { + + Version: '1.7_rc2', + + Browser: (function(){ + var ua = navigator.userAgent; + var isOpera = Object.prototype.toString.call(window.opera) == '[object Opera]'; + return { + IE: !!window.attachEvent && !isOpera, + Opera: isOpera, + WebKit: ua.indexOf('AppleWebKit/') > -1, + Gecko: ua.indexOf('Gecko') > -1 && ua.indexOf('KHTML') === -1, + MobileSafari: /Apple.*Mobile/.test(ua) + } + })(), + + BrowserFeatures: { + XPath: !!document.evaluate, + + SelectorsAPI: !!document.querySelector, + + ElementExtensions: (function() { + var constructor = window.Element || window.HTMLElement; + return !!(constructor && constructor.prototype); + })(), + SpecificElementExtensions: (function() { + if (typeof window.HTMLDivElement !== 'undefined') + return true; + + var div = document.createElement('div'), + form = document.createElement('form'), + isSupported = false; + + if (div['__proto__'] && (div['__proto__'] !== form['__proto__'])) { + isSupported = true; + } + + div = form = null; + + return isSupported; + })() + }, + + ScriptFragment: ']*>([\\S\\s]*?)<\/script>', + JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/, + + emptyFunction: function() { }, + + K: function(x) { return x } +}; + +if (Prototype.Browser.MobileSafari) + Prototype.BrowserFeatures.SpecificElementExtensions = false; + + +var Abstract = { }; + + +var Try = { + these: function() { + var returnValue; + + for (var i = 0, length = arguments.length; i < length; i++) { + var lambda = arguments[i]; + try { + returnValue = lambda(); + break; + } catch (e) { } + } + + return returnValue; + } +}; + +/* Based on Alex Arnell's inheritance implementation. */ + +var Class = (function() { + + var IS_DONTENUM_BUGGY = (function(){ + for (var p in { toString: 1 }) { + if (p === 'toString') return false; + } + return true; + })(); + + function subclass() {}; + function create() { + var parent = null, properties = $A(arguments); + if (Object.isFunction(properties[0])) + parent = properties.shift(); + + function klass() { + this.initialize.apply(this, arguments); + } + + Object.extend(klass, Class.Methods); + klass.superclass = parent; + klass.subclasses = []; + + if (parent) { + subclass.prototype = parent.prototype; + klass.prototype = new subclass; + parent.subclasses.push(klass); + } + + for (var i = 0, length = properties.length; i < length; i++) + klass.addMethods(properties[i]); + + if (!klass.prototype.initialize) + klass.prototype.initialize = Prototype.emptyFunction; + + klass.prototype.constructor = klass; + return klass; + } + + function addMethods(source) { + var ancestor = this.superclass && this.superclass.prototype, + properties = Object.keys(source); + + if (IS_DONTENUM_BUGGY) { + if (source.toString != Object.prototype.toString) + properties.push("toString"); + if (source.valueOf != Object.prototype.valueOf) + properties.push("valueOf"); + } + + for (var i = 0, length = properties.length; i < length; i++) { + var property = properties[i], value = source[property]; + if (ancestor && Object.isFunction(value) && + value.argumentNames()[0] == "$super") { + var method = value; + value = (function(m) { + return function() { return ancestor[m].apply(this, arguments); }; + })(property).wrap(method); + + value.valueOf = method.valueOf.bind(method); + value.toString = method.toString.bind(method); + } + this.prototype[property] = value; + } + + return this; + } + + return { + create: create, + Methods: { + addMethods: addMethods + } + }; +})(); +(function() { + + var _toString = Object.prototype.toString, + NULL_TYPE = 'Null', + UNDEFINED_TYPE = 'Undefined', + BOOLEAN_TYPE = 'Boolean', + NUMBER_TYPE = 'Number', + STRING_TYPE = 'String', + OBJECT_TYPE = 'Object', + BOOLEAN_CLASS = '[object Boolean]', + NUMBER_CLASS = '[object Number]', + STRING_CLASS = '[object String]', + ARRAY_CLASS = '[object Array]', + NATIVE_JSON_STRINGIFY_SUPPORT = window.JSON && + typeof JSON.stringify === 'function' && + JSON.stringify(0) === '0' && + typeof JSON.stringify(Prototype.K) === 'undefined'; + + function Type(o) { + switch(o) { + case null: return NULL_TYPE; + case (void 0): return UNDEFINED_TYPE; + } + var type = typeof o; + switch(type) { + case 'boolean': return BOOLEAN_TYPE; + case 'number': return NUMBER_TYPE; + case 'string': return STRING_TYPE; + } + return OBJECT_TYPE; + } + + function extend(destination, source) { + for (var property in source) + destination[property] = source[property]; + return destination; + } + + function inspect(object) { + try { + if (isUndefined(object)) return 'undefined'; + if (object === null) return 'null'; + return object.inspect ? object.inspect() : String(object); + } catch (e) { + if (e instanceof RangeError) return '...'; + throw e; + } + } + + function toJSON(value) { + return Str('', { '': value }, []); + } + + function Str(key, holder, stack) { + var value = holder[key], + type = typeof value; + + if (Type(value) === OBJECT_TYPE && typeof value.toJSON === 'function') { + value = value.toJSON(key); + } + + var _class = _toString.call(value); + + switch (_class) { + case NUMBER_CLASS: + case BOOLEAN_CLASS: + case STRING_CLASS: + value = value.valueOf(); + } + + switch (value) { + case null: return 'null'; + case true: return 'true'; + case false: return 'false'; + } + + type = typeof value; + switch (type) { + case 'string': + return value.inspect(true); + case 'number': + return isFinite(value) ? String(value) : 'null'; + case 'object': + + for (var i = 0, length = stack.length; i < length; i++) { + if (stack[i] === value) { throw new TypeError(); } + } + stack.push(value); + + var partial = []; + if (_class === ARRAY_CLASS) { + for (var i = 0, length = value.length; i < length; i++) { + var str = Str(i, value, stack); + partial.push(typeof str === 'undefined' ? 'null' : str); + } + partial = '[' + partial.join(',') + ']'; + } else { + var keys = Object.keys(value); + for (var i = 0, length = keys.length; i < length; i++) { + var key = keys[i], str = Str(key, value, stack); + if (typeof str !== "undefined") { + partial.push(key.inspect(true)+ ':' + str); + } + } + partial = '{' + partial.join(',') + '}'; + } + stack.pop(); + return partial; + } + } + + function stringify(object) { + return JSON.stringify(object); + } + + function toQueryString(object) { + return $H(object).toQueryString(); + } + + function toHTML(object) { + return object && object.toHTML ? object.toHTML() : String.interpret(object); + } + + function keys(object) { + if (Type(object) !== OBJECT_TYPE) { throw new TypeError(); } + var results = []; + for (var property in object) { + if (object.hasOwnProperty(property)) { + results.push(property); + } + } + return results; + } + + function values(object) { + var results = []; + for (var property in object) + results.push(object[property]); + return results; + } + + function clone(object) { + return extend({ }, object); + } + + function isElement(object) { + return !!(object && object.nodeType == 1); + } + + function isArray(object) { + return _toString.call(object) === ARRAY_CLASS; + } + + var hasNativeIsArray = (typeof Array.isArray == 'function') + && Array.isArray([]) && !Array.isArray({}); + + if (hasNativeIsArray) { + isArray = Array.isArray; + } + + function isHash(object) { + return object instanceof Hash; + } + + function isFunction(object) { + return typeof object === "function"; + } + + function isString(object) { + return _toString.call(object) === STRING_CLASS; + } + + function isNumber(object) { + return _toString.call(object) === NUMBER_CLASS; + } + + function isUndefined(object) { + return typeof object === "undefined"; + } + + extend(Object, { + extend: extend, + inspect: inspect, + toJSON: NATIVE_JSON_STRINGIFY_SUPPORT ? stringify : toJSON, + toQueryString: toQueryString, + toHTML: toHTML, + keys: Object.keys || keys, + values: values, + clone: clone, + isElement: isElement, + isArray: isArray, + isHash: isHash, + isFunction: isFunction, + isString: isString, + isNumber: isNumber, + isUndefined: isUndefined + }); +})(); +Object.extend(Function.prototype, (function() { + var slice = Array.prototype.slice; + + function update(array, args) { + var arrayLength = array.length, length = args.length; + while (length--) array[arrayLength + length] = args[length]; + return array; + } + + function merge(array, args) { + array = slice.call(array, 0); + return update(array, args); + } + + function argumentNames() { + var names = this.toString().match(/^[\s\(]*function[^(]*\(([^)]*)\)/)[1] + .replace(/\/\/.*?[\r\n]|\/\*(?:.|[\r\n])*?\*\//g, '') + .replace(/\s+/g, '').split(','); + return names.length == 1 && !names[0] ? [] : names; + } + + function bind(context) { + if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this; + var __method = this, args = slice.call(arguments, 1); + return function() { + var a = merge(args, arguments); + return __method.apply(context, a); + } + } + + function bindAsEventListener(context) { + var __method = this, args = slice.call(arguments, 1); + return function(event) { + var a = update([event || window.event], args); + return __method.apply(context, a); + } + } + + function curry() { + if (!arguments.length) return this; + var __method = this, args = slice.call(arguments, 0); + return function() { + var a = merge(args, arguments); + return __method.apply(this, a); + } + } + + function delay(timeout) { + var __method = this, args = slice.call(arguments, 1); + timeout = timeout * 1000; + return window.setTimeout(function() { + return __method.apply(__method, args); + }, timeout); + } + + function defer() { + var args = update([0.01], arguments); + return this.delay.apply(this, args); + } + + function wrap(wrapper) { + var __method = this; + return function() { + var a = update([__method.bind(this)], arguments); + return wrapper.apply(this, a); + } + } + + function methodize() { + if (this._methodized) return this._methodized; + var __method = this; + return this._methodized = function() { + var a = update([this], arguments); + return __method.apply(null, a); + }; + } + + return { + argumentNames: argumentNames, + bind: bind, + bindAsEventListener: bindAsEventListener, + curry: curry, + delay: delay, + defer: defer, + wrap: wrap, + methodize: methodize + } +})()); + + + +(function(proto) { + + + function toISOString() { + return this.getUTCFullYear() + '-' + + (this.getUTCMonth() + 1).toPaddedString(2) + '-' + + this.getUTCDate().toPaddedString(2) + 'T' + + this.getUTCHours().toPaddedString(2) + ':' + + this.getUTCMinutes().toPaddedString(2) + ':' + + this.getUTCSeconds().toPaddedString(2) + 'Z'; + } + + + function toJSON() { + return this.toISOString(); + } + + if (!proto.toISOString) proto.toISOString = toISOString; + if (!proto.toJSON) proto.toJSON = toJSON; + +})(Date.prototype); + + +RegExp.prototype.match = RegExp.prototype.test; + +RegExp.escape = function(str) { + return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1'); +}; +var PeriodicalExecuter = Class.create({ + initialize: function(callback, frequency) { + this.callback = callback; + this.frequency = frequency; + this.currentlyExecuting = false; + + this.registerCallback(); + }, + + registerCallback: function() { + this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + execute: function() { + this.callback(this); + }, + + stop: function() { + if (!this.timer) return; + clearInterval(this.timer); + this.timer = null; + }, + + onTimerEvent: function() { + if (!this.currentlyExecuting) { + try { + this.currentlyExecuting = true; + this.execute(); + this.currentlyExecuting = false; + } catch(e) { + this.currentlyExecuting = false; + throw e; + } + } + } +}); +Object.extend(String, { + interpret: function(value) { + return value == null ? '' : String(value); + }, + specialChar: { + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '\\': '\\\\' + } +}); + +Object.extend(String.prototype, (function() { + var NATIVE_JSON_PARSE_SUPPORT = window.JSON && + typeof JSON.parse === 'function' && + JSON.parse('{"test": true}').test; + + function prepareReplacement(replacement) { + if (Object.isFunction(replacement)) return replacement; + var template = new Template(replacement); + return function(match) { return template.evaluate(match) }; + } + + function gsub(pattern, replacement) { + var result = '', source = this, match; + replacement = prepareReplacement(replacement); + + if (Object.isString(pattern)) + pattern = RegExp.escape(pattern); + + if (!(pattern.length || pattern.source)) { + replacement = replacement(''); + return replacement + source.split('').join(replacement) + replacement; + } + + while (source.length > 0) { + if (match = source.match(pattern)) { + result += source.slice(0, match.index); + result += String.interpret(replacement(match)); + source = source.slice(match.index + match[0].length); + } else { + result += source, source = ''; + } + } + return result; + } + + function sub(pattern, replacement, count) { + replacement = prepareReplacement(replacement); + count = Object.isUndefined(count) ? 1 : count; + + return this.gsub(pattern, function(match) { + if (--count < 0) return match[0]; + return replacement(match); + }); + } + + function scan(pattern, iterator) { + this.gsub(pattern, iterator); + return String(this); + } + + function truncate(length, truncation) { + length = length || 30; + truncation = Object.isUndefined(truncation) ? '...' : truncation; + return this.length > length ? + this.slice(0, length - truncation.length) + truncation : String(this); + } + + function strip() { + return this.replace(/^\s+/, '').replace(/\s+$/, ''); + } + + function stripTags() { + return this.replace(/<\w+(\s+("[^"]*"|'[^']*'|[^>])+)?>|<\/\w+>/gi, ''); + } + + function stripScripts() { + return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); + } + + function extractScripts() { + var matchAll = new RegExp(Prototype.ScriptFragment, 'img'), + matchOne = new RegExp(Prototype.ScriptFragment, 'im'); + return (this.match(matchAll) || []).map(function(scriptTag) { + return (scriptTag.match(matchOne) || ['', ''])[1]; + }); + } + + function evalScripts() { + return this.extractScripts().map(function(script) { return eval(script) }); + } + + function escapeHTML() { + return this.replace(/&/g,'&').replace(//g,'>'); + } + + function unescapeHTML() { + return this.stripTags().replace(/</g,'<').replace(/>/g,'>').replace(/&/g,'&'); + } + + + function toQueryParams(separator) { + var match = this.strip().match(/([^?#]*)(#.*)?$/); + if (!match) return { }; + + return match[1].split(separator || '&').inject({ }, function(hash, pair) { + if ((pair = pair.split('='))[0]) { + var key = decodeURIComponent(pair.shift()), + value = pair.length > 1 ? pair.join('=') : pair[0]; + + if (value != undefined) value = decodeURIComponent(value); + + if (key in hash) { + if (!Object.isArray(hash[key])) hash[key] = [hash[key]]; + hash[key].push(value); + } + else hash[key] = value; + } + return hash; + }); + } + + function toArray() { + return this.split(''); + } + + function succ() { + return this.slice(0, this.length - 1) + + String.fromCharCode(this.charCodeAt(this.length - 1) + 1); + } + + function times(count) { + return count < 1 ? '' : new Array(count + 1).join(this); + } + + function camelize() { + return this.replace(/-+(.)?/g, function(match, chr) { + return chr ? chr.toUpperCase() : ''; + }); + } + + function capitalize() { + return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase(); + } + + function underscore() { + return this.replace(/::/g, '/') + .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2') + .replace(/([a-z\d])([A-Z])/g, '$1_$2') + .replace(/-/g, '_') + .toLowerCase(); + } + + function dasherize() { + return this.replace(/_/g, '-'); + } + + function inspect(useDoubleQuotes) { + var escapedString = this.replace(/[\x00-\x1f\\]/g, function(character) { + if (character in String.specialChar) { + return String.specialChar[character]; + } + return '\\u00' + character.charCodeAt().toPaddedString(2, 16); + }); + if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"'; + return "'" + escapedString.replace(/'/g, '\\\'') + "'"; + } + + function unfilterJSON(filter) { + return this.replace(filter || Prototype.JSONFilter, '$1'); + } + + function isJSON() { + var str = this; + if (str.blank()) return false; + str = str.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@'); + str = str.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'); + str = str.replace(/(?:^|:|,)(?:\s*\[)+/g, ''); + return (/^[\],:{}\s]*$/).test(str); + } + + function evalJSON(sanitize) { + var json = this.unfilterJSON(), + cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; + if (cx.test(json)) { + json = json.replace(cx, function (a) { + return '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }); + } + try { + if (!sanitize || json.isJSON()) return eval('(' + json + ')'); + } catch (e) { } + throw new SyntaxError('Badly formed JSON string: ' + this.inspect()); + } + + function parseJSON() { + var json = this.unfilterJSON(); + return JSON.parse(json); + } + + function include(pattern) { + return this.indexOf(pattern) > -1; + } + + function startsWith(pattern) { + return this.lastIndexOf(pattern, 0) === 0; + } + + function endsWith(pattern) { + var d = this.length - pattern.length; + return d >= 0 && this.indexOf(pattern, d) === d; + } + + function empty() { + return this == ''; + } + + function blank() { + return /^\s*$/.test(this); + } + + function interpolate(object, pattern) { + return new Template(this, pattern).evaluate(object); + } + + return { + gsub: gsub, + sub: sub, + scan: scan, + truncate: truncate, + strip: String.prototype.trim || strip, + stripTags: stripTags, + stripScripts: stripScripts, + extractScripts: extractScripts, + evalScripts: evalScripts, + escapeHTML: escapeHTML, + unescapeHTML: unescapeHTML, + toQueryParams: toQueryParams, + parseQuery: toQueryParams, + toArray: toArray, + succ: succ, + times: times, + camelize: camelize, + capitalize: capitalize, + underscore: underscore, + dasherize: dasherize, + inspect: inspect, + unfilterJSON: unfilterJSON, + isJSON: isJSON, + evalJSON: NATIVE_JSON_PARSE_SUPPORT ? parseJSON : evalJSON, + include: include, + startsWith: startsWith, + endsWith: endsWith, + empty: empty, + blank: blank, + interpolate: interpolate + }; +})()); + +var Template = Class.create({ + initialize: function(template, pattern) { + this.template = template.toString(); + this.pattern = pattern || Template.Pattern; + }, + + evaluate: function(object) { + if (object && Object.isFunction(object.toTemplateReplacements)) + object = object.toTemplateReplacements(); + + return this.template.gsub(this.pattern, function(match) { + if (object == null) return (match[1] + ''); + + var before = match[1] || ''; + if (before == '\\') return match[2]; + + var ctx = object, expr = match[3], + pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/; + + match = pattern.exec(expr); + if (match == null) return before; + + while (match != null) { + var comp = match[1].startsWith('[') ? match[2].replace(/\\\\]/g, ']') : match[1]; + ctx = ctx[comp]; + if (null == ctx || '' == match[3]) break; + expr = expr.substring('[' == match[3] ? match[1].length : match[0].length); + match = pattern.exec(expr); + } + + return before + String.interpret(ctx); + }); + } +}); +Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; + +var $break = { }; + +var Enumerable = (function() { + function each(iterator, context) { + var index = 0; + try { + this._each(function(value) { + iterator.call(context, value, index++); + }); + } catch (e) { + if (e != $break) throw e; + } + return this; + } + + function eachSlice(number, iterator, context) { + var index = -number, slices = [], array = this.toArray(); + if (number < 1) return array; + while ((index += number) < array.length) + slices.push(array.slice(index, index+number)); + return slices.collect(iterator, context); + } + + function all(iterator, context) { + iterator = iterator || Prototype.K; + var result = true; + this.each(function(value, index) { + result = result && !!iterator.call(context, value, index); + if (!result) throw $break; + }); + return result; + } + + function any(iterator, context) { + iterator = iterator || Prototype.K; + var result = false; + this.each(function(value, index) { + if (result = !!iterator.call(context, value, index)) + throw $break; + }); + return result; + } + + function collect(iterator, context) { + iterator = iterator || Prototype.K; + var results = []; + this.each(function(value, index) { + results.push(iterator.call(context, value, index)); + }); + return results; + } + + function detect(iterator, context) { + var result; + this.each(function(value, index) { + if (iterator.call(context, value, index)) { + result = value; + throw $break; + } + }); + return result; + } + + function findAll(iterator, context) { + var results = []; + this.each(function(value, index) { + if (iterator.call(context, value, index)) + results.push(value); + }); + return results; + } + + function grep(filter, iterator, context) { + iterator = iterator || Prototype.K; + var results = []; + + if (Object.isString(filter)) + filter = new RegExp(RegExp.escape(filter)); + + this.each(function(value, index) { + if (filter.match(value)) + results.push(iterator.call(context, value, index)); + }); + return results; + } + + function include(object) { + if (Object.isFunction(this.indexOf)) + if (this.indexOf(object) != -1) return true; + + var found = false; + this.each(function(value) { + if (value == object) { + found = true; + throw $break; + } + }); + return found; + } + + function inGroupsOf(number, fillWith) { + fillWith = Object.isUndefined(fillWith) ? null : fillWith; + return this.eachSlice(number, function(slice) { + while(slice.length < number) slice.push(fillWith); + return slice; + }); + } + + function inject(memo, iterator, context) { + this.each(function(value, index) { + memo = iterator.call(context, memo, value, index); + }); + return memo; + } + + function invoke(method) { + var args = $A(arguments).slice(1); + return this.map(function(value) { + return value[method].apply(value, args); + }); + } + + function max(iterator, context) { + iterator = iterator || Prototype.K; + var result; + this.each(function(value, index) { + value = iterator.call(context, value, index); + if (result == null || value >= result) + result = value; + }); + return result; + } + + function min(iterator, context) { + iterator = iterator || Prototype.K; + var result; + this.each(function(value, index) { + value = iterator.call(context, value, index); + if (result == null || value < result) + result = value; + }); + return result; + } + + function partition(iterator, context) { + iterator = iterator || Prototype.K; + var trues = [], falses = []; + this.each(function(value, index) { + (iterator.call(context, value, index) ? + trues : falses).push(value); + }); + return [trues, falses]; + } + + function pluck(property) { + var results = []; + this.each(function(value) { + results.push(value[property]); + }); + return results; + } + + function reject(iterator, context) { + var results = []; + this.each(function(value, index) { + if (!iterator.call(context, value, index)) + results.push(value); + }); + return results; + } + + function sortBy(iterator, context) { + return this.map(function(value, index) { + return { + value: value, + criteria: iterator.call(context, value, index) + }; + }).sort(function(left, right) { + var a = left.criteria, b = right.criteria; + return a < b ? -1 : a > b ? 1 : 0; + }).pluck('value'); + } + + function toArray() { + return this.map(); + } + + function zip() { + var iterator = Prototype.K, args = $A(arguments); + if (Object.isFunction(args.last())) + iterator = args.pop(); + + var collections = [this].concat(args).map($A); + return this.map(function(value, index) { + return iterator(collections.pluck(index)); + }); + } + + function size() { + return this.toArray().length; + } + + function inspect() { + return '#'; + } + + + + + + + + + + return { + each: each, + eachSlice: eachSlice, + all: all, + every: all, + any: any, + some: any, + collect: collect, + map: collect, + detect: detect, + findAll: findAll, + select: findAll, + filter: findAll, + grep: grep, + include: include, + member: include, + inGroupsOf: inGroupsOf, + inject: inject, + invoke: invoke, + max: max, + min: min, + partition: partition, + pluck: pluck, + reject: reject, + sortBy: sortBy, + toArray: toArray, + entries: toArray, + zip: zip, + size: size, + inspect: inspect, + find: detect + }; +})(); + +function $A(iterable) { + if (!iterable) return []; + if ('toArray' in Object(iterable)) return iterable.toArray(); + var length = iterable.length || 0, results = new Array(length); + while (length--) results[length] = iterable[length]; + return results; +} + + +function $w(string) { + if (!Object.isString(string)) return []; + string = string.strip(); + return string ? string.split(/\s+/) : []; +} + +Array.from = $A; + + +(function() { + var arrayProto = Array.prototype, + slice = arrayProto.slice, + _each = arrayProto.forEach; // use native browser JS 1.6 implementation if available + + function each(iterator) { + for (var i = 0, length = this.length; i < length; i++) + iterator(this[i]); + } + if (!_each) _each = each; + + function clear() { + this.length = 0; + return this; + } + + function first() { + return this[0]; + } + + function last() { + return this[this.length - 1]; + } + + function compact() { + return this.select(function(value) { + return value != null; + }); + } + + function flatten() { + return this.inject([], function(array, value) { + if (Object.isArray(value)) + return array.concat(value.flatten()); + array.push(value); + return array; + }); + } + + function without() { + var values = slice.call(arguments, 0); + return this.select(function(value) { + return !values.include(value); + }); + } + + function reverse(inline) { + return (inline === false ? this.toArray() : this)._reverse(); + } + + function uniq(sorted) { + return this.inject([], function(array, value, index) { + if (0 == index || (sorted ? array.last() != value : !array.include(value))) + array.push(value); + return array; + }); + } + + function intersect(array) { + return this.uniq().findAll(function(item) { + return array.detect(function(value) { return item === value }); + }); + } + + + function clone() { + return slice.call(this, 0); + } + + function size() { + return this.length; + } + + function inspect() { + return '[' + this.map(Object.inspect).join(', ') + ']'; + } + + function indexOf(item, i) { + i || (i = 0); + var length = this.length; + if (i < 0) i = length + i; + for (; i < length; i++) + if (this[i] === item) return i; + return -1; + } + + function lastIndexOf(item, i) { + i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1; + var n = this.slice(0, i).reverse().indexOf(item); + return (n < 0) ? n : i - n - 1; + } + + function concat() { + var array = slice.call(this, 0), item; + for (var i = 0, length = arguments.length; i < length; i++) { + item = arguments[i]; + if (Object.isArray(item) && !('callee' in item)) { + for (var j = 0, arrayLength = item.length; j < arrayLength; j++) + array.push(item[j]); + } else { + array.push(item); + } + } + return array; + } + + Object.extend(arrayProto, Enumerable); + + if (!arrayProto._reverse) + arrayProto._reverse = arrayProto.reverse; + + Object.extend(arrayProto, { + _each: _each, + clear: clear, + first: first, + last: last, + compact: compact, + flatten: flatten, + without: without, + reverse: reverse, + uniq: uniq, + intersect: intersect, + clone: clone, + toArray: clone, + size: size, + inspect: inspect + }); + + var CONCAT_ARGUMENTS_BUGGY = (function() { + return [].concat(arguments)[0][0] !== 1; + })(1,2) + + if (CONCAT_ARGUMENTS_BUGGY) arrayProto.concat = concat; + + if (!arrayProto.indexOf) arrayProto.indexOf = indexOf; + if (!arrayProto.lastIndexOf) arrayProto.lastIndexOf = lastIndexOf; +})(); +function $H(object) { + return new Hash(object); +}; + +var Hash = Class.create(Enumerable, (function() { + function initialize(object) { + this._object = Object.isHash(object) ? object.toObject() : Object.clone(object); + } + + + function _each(iterator) { + for (var key in this._object) { + var value = this._object[key], pair = [key, value]; + pair.key = key; + pair.value = value; + iterator(pair); + } + } + + function set(key, value) { + return this._object[key] = value; + } + + function get(key) { + if (this._object[key] !== Object.prototype[key]) + return this._object[key]; + } + + function unset(key) { + var value = this._object[key]; + delete this._object[key]; + return value; + } + + function toObject() { + return Object.clone(this._object); + } + + + + function keys() { + return this.pluck('key'); + } + + function values() { + return this.pluck('value'); + } + + function index(value) { + var match = this.detect(function(pair) { + return pair.value === value; + }); + return match && match.key; + } + + function merge(object) { + return this.clone().update(object); + } + + function update(object) { + return new Hash(object).inject(this, function(result, pair) { + result.set(pair.key, pair.value); + return result; + }); + } + + function toQueryPair(key, value) { + if (Object.isUndefined(value)) return key; + return key + '=' + encodeURIComponent(String.interpret(value)); + } + + function toQueryString() { + return this.inject([], function(results, pair) { + var key = encodeURIComponent(pair.key), values = pair.value; + + if (values && typeof values == 'object') { + if (Object.isArray(values)) + return results.concat(values.map(toQueryPair.curry(key))); + } else results.push(toQueryPair(key, values)); + return results; + }).join('&'); + } + + function inspect() { + return '#'; + } + + function clone() { + return new Hash(this); + } + + return { + initialize: initialize, + _each: _each, + set: set, + get: get, + unset: unset, + toObject: toObject, + toTemplateReplacements: toObject, + keys: keys, + values: values, + index: index, + merge: merge, + update: update, + toQueryString: toQueryString, + inspect: inspect, + toJSON: toObject, + clone: clone + }; +})()); + +Hash.from = $H; +Object.extend(Number.prototype, (function() { + function toColorPart() { + return this.toPaddedString(2, 16); + } + + function succ() { + return this + 1; + } + + function times(iterator, context) { + $R(0, this, true).each(iterator, context); + return this; + } + + function toPaddedString(length, radix) { + var string = this.toString(radix || 10); + return '0'.times(length - string.length) + string; + } + + function abs() { + return Math.abs(this); + } + + function round() { + return Math.round(this); + } + + function ceil() { + return Math.ceil(this); + } + + function floor() { + return Math.floor(this); + } + + return { + toColorPart: toColorPart, + succ: succ, + times: times, + toPaddedString: toPaddedString, + abs: abs, + round: round, + ceil: ceil, + floor: floor + }; +})()); + +function $R(start, end, exclusive) { + return new ObjectRange(start, end, exclusive); +} + +var ObjectRange = Class.create(Enumerable, (function() { + function initialize(start, end, exclusive) { + this.start = start; + this.end = end; + this.exclusive = exclusive; + } + + function _each(iterator) { + var value = this.start; + while (this.include(value)) { + iterator(value); + value = value.succ(); + } + } + + function include(value) { + if (value < this.start) + return false; + if (this.exclusive) + return value < this.end; + return value <= this.end; + } + + return { + initialize: initialize, + _each: _each, + include: include + }; +})()); + + + +var Ajax = { + getTransport: function() { + return Try.these( + function() {return new XMLHttpRequest()}, + function() {return new ActiveXObject('Msxml2.XMLHTTP')}, + function() {return new ActiveXObject('Microsoft.XMLHTTP')} + ) || false; + }, + + activeRequestCount: 0 +}; + +Ajax.Responders = { + responders: [], + + _each: function(iterator) { + this.responders._each(iterator); + }, + + register: function(responder) { + if (!this.include(responder)) + this.responders.push(responder); + }, + + unregister: function(responder) { + this.responders = this.responders.without(responder); + }, + + dispatch: function(callback, request, transport, json) { + this.each(function(responder) { + if (Object.isFunction(responder[callback])) { + try { + responder[callback].apply(responder, [request, transport, json]); + } catch (e) { } + } + }); + } +}; + +Object.extend(Ajax.Responders, Enumerable); + +Ajax.Responders.register({ + onCreate: function() { Ajax.activeRequestCount++ }, + onComplete: function() { Ajax.activeRequestCount-- } +}); +Ajax.Base = Class.create({ + initialize: function(options) { + this.options = { + method: 'post', + asynchronous: true, + contentType: 'application/x-www-form-urlencoded', + encoding: 'UTF-8', + parameters: '', + evalJSON: true, + evalJS: true + }; + Object.extend(this.options, options || { }); + + this.options.method = this.options.method.toLowerCase(); + + if (Object.isString(this.options.parameters)) + this.options.parameters = this.options.parameters.toQueryParams(); + else if (Object.isHash(this.options.parameters)) + this.options.parameters = this.options.parameters.toObject(); + } +}); +Ajax.Request = Class.create(Ajax.Base, { + _complete: false, + + initialize: function($super, url, options) { + $super(options); + this.transport = Ajax.getTransport(); + this.request(url); + }, + + request: function(url) { + this.url = url; + this.method = this.options.method; + var params = Object.clone(this.options.parameters); + + if (!['get', 'post'].include(this.method)) { + params['_method'] = this.method; + this.method = 'post'; + } + + this.parameters = params; + + if (params = Object.toQueryString(params)) { + if (this.method == 'get') + this.url += (this.url.include('?') ? '&' : '?') + params; + else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) + params += '&_='; + } + + try { + var response = new Ajax.Response(this); + if (this.options.onCreate) this.options.onCreate(response); + Ajax.Responders.dispatch('onCreate', this, response); + + this.transport.open(this.method.toUpperCase(), this.url, + this.options.asynchronous); + + if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1); + + this.transport.onreadystatechange = this.onStateChange.bind(this); + this.setRequestHeaders(); + + this.body = this.method == 'post' ? (this.options.postBody || params) : null; + this.transport.send(this.body); + + /* Force Firefox to handle ready state 4 for synchronous requests */ + if (!this.options.asynchronous && this.transport.overrideMimeType) + this.onStateChange(); + + } + catch (e) { + this.dispatchException(e); + } + }, + + onStateChange: function() { + var readyState = this.transport.readyState; + if (readyState > 1 && !((readyState == 4) && this._complete)) + this.respondToReadyState(this.transport.readyState); + }, + + setRequestHeaders: function() { + var headers = { + 'X-Requested-With': 'XMLHttpRequest', + 'X-Prototype-Version': Prototype.Version, + 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' + }; + + if (this.method == 'post') { + headers['Content-type'] = this.options.contentType + + (this.options.encoding ? '; charset=' + this.options.encoding : ''); + + /* Force "Connection: close" for older Mozilla browsers to work + * around a bug where XMLHttpRequest sends an incorrect + * Content-length header. See Mozilla Bugzilla #246651. + */ + if (this.transport.overrideMimeType && + (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005) + headers['Connection'] = 'close'; + } + + if (typeof this.options.requestHeaders == 'object') { + var extras = this.options.requestHeaders; + + if (Object.isFunction(extras.push)) + for (var i = 0, length = extras.length; i < length; i += 2) + headers[extras[i]] = extras[i+1]; + else + $H(extras).each(function(pair) { headers[pair.key] = pair.value }); + } + + for (var name in headers) + this.transport.setRequestHeader(name, headers[name]); + }, + + success: function() { + var status = this.getStatus(); + return !status || (status >= 200 && status < 300); + }, + + getStatus: function() { + try { + return this.transport.status || 0; + } catch (e) { return 0 } + }, + + respondToReadyState: function(readyState) { + var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this); + + if (state == 'Complete') { + try { + this._complete = true; + (this.options['on' + response.status] + || this.options['on' + (this.success() ? 'Success' : 'Failure')] + || Prototype.emptyFunction)(response, response.headerJSON); + } catch (e) { + this.dispatchException(e); + } + + var contentType = response.getHeader('Content-type'); + if (this.options.evalJS == 'force' + || (this.options.evalJS && this.isSameOrigin() && contentType + && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i))) + this.evalResponse(); + } + + try { + (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON); + Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON); + } catch (e) { + this.dispatchException(e); + } + + if (state == 'Complete') { + this.transport.onreadystatechange = Prototype.emptyFunction; + } + }, + + isSameOrigin: function() { + var m = this.url.match(/^\s*https?:\/\/[^\/]*/); + return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({ + protocol: location.protocol, + domain: document.domain, + port: location.port ? ':' + location.port : '' + })); + }, + + getHeader: function(name) { + try { + return this.transport.getResponseHeader(name) || null; + } catch (e) { return null; } + }, + + evalResponse: function() { + try { + return eval((this.transport.responseText || '').unfilterJSON()); + } catch (e) { + this.dispatchException(e); + } + }, + + dispatchException: function(exception) { + (this.options.onException || Prototype.emptyFunction)(this, exception); + Ajax.Responders.dispatch('onException', this, exception); + } +}); + +Ajax.Request.Events = + ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; + + + + + + + + +Ajax.Response = Class.create({ + initialize: function(request){ + this.request = request; + var transport = this.transport = request.transport, + readyState = this.readyState = transport.readyState; + + if ((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) { + this.status = this.getStatus(); + this.statusText = this.getStatusText(); + this.responseText = String.interpret(transport.responseText); + this.headerJSON = this._getHeaderJSON(); + } + + if (readyState == 4) { + var xml = transport.responseXML; + this.responseXML = Object.isUndefined(xml) ? null : xml; + this.responseJSON = this._getResponseJSON(); + } + }, + + status: 0, + + statusText: '', + + getStatus: Ajax.Request.prototype.getStatus, + + getStatusText: function() { + try { + return this.transport.statusText || ''; + } catch (e) { return '' } + }, + + getHeader: Ajax.Request.prototype.getHeader, + + getAllHeaders: function() { + try { + return this.getAllResponseHeaders(); + } catch (e) { return null } + }, + + getResponseHeader: function(name) { + return this.transport.getResponseHeader(name); + }, + + getAllResponseHeaders: function() { + return this.transport.getAllResponseHeaders(); + }, + + _getHeaderJSON: function() { + var json = this.getHeader('X-JSON'); + if (!json) return null; + json = decodeURIComponent(escape(json)); + try { + return json.evalJSON(this.request.options.sanitizeJSON || + !this.request.isSameOrigin()); + } catch (e) { + this.request.dispatchException(e); + } + }, + + _getResponseJSON: function() { + var options = this.request.options; + if (!options.evalJSON || (options.evalJSON != 'force' && + !(this.getHeader('Content-type') || '').include('application/json')) || + this.responseText.blank()) + return null; + try { + return this.responseText.evalJSON(options.sanitizeJSON || + !this.request.isSameOrigin()); + } catch (e) { + this.request.dispatchException(e); + } + } +}); + +Ajax.Updater = Class.create(Ajax.Request, { + initialize: function($super, container, url, options) { + this.container = { + success: (container.success || container), + failure: (container.failure || (container.success ? null : container)) + }; + + options = Object.clone(options); + var onComplete = options.onComplete; + options.onComplete = (function(response, json) { + this.updateContent(response.responseText); + if (Object.isFunction(onComplete)) onComplete(response, json); + }).bind(this); + + $super(url, options); + }, + + updateContent: function(responseText) { + var receiver = this.container[this.success() ? 'success' : 'failure'], + options = this.options; + + if (!options.evalScripts) responseText = responseText.stripScripts(); + + if (receiver = $(receiver)) { + if (options.insertion) { + if (Object.isString(options.insertion)) { + var insertion = { }; insertion[options.insertion] = responseText; + receiver.insert(insertion); + } + else options.insertion(receiver, responseText); + } + else receiver.update(responseText); + } + } +}); + +Ajax.PeriodicalUpdater = Class.create(Ajax.Base, { + initialize: function($super, container, url, options) { + $super(options); + this.onComplete = this.options.onComplete; + + this.frequency = (this.options.frequency || 2); + this.decay = (this.options.decay || 1); + + this.updater = { }; + this.container = container; + this.url = url; + + this.start(); + }, + + start: function() { + this.options.onComplete = this.updateComplete.bind(this); + this.onTimerEvent(); + }, + + stop: function() { + this.updater.options.onComplete = undefined; + clearTimeout(this.timer); + (this.onComplete || Prototype.emptyFunction).apply(this, arguments); + }, + + updateComplete: function(response) { + if (this.options.decay) { + this.decay = (response.responseText == this.lastText ? + this.decay * this.options.decay : 1); + + this.lastText = response.responseText; + } + this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency); + }, + + onTimerEvent: function() { + this.updater = new Ajax.Updater(this.container, this.url, this.options); + } +}); + + +function $(element) { + if (arguments.length > 1) { + for (var i = 0, elements = [], length = arguments.length; i < length; i++) + elements.push($(arguments[i])); + return elements; + } + if (Object.isString(element)) + element = document.getElementById(element); + return Element.extend(element); +} + +if (Prototype.BrowserFeatures.XPath) { + document._getElementsByXPath = function(expression, parentElement) { + var results = []; + var query = document.evaluate(expression, $(parentElement) || document, + null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); + for (var i = 0, length = query.snapshotLength; i < length; i++) + results.push(Element.extend(query.snapshotItem(i))); + return results; + }; +} + +/*--------------------------------------------------------------------------*/ + +if (!Node) var Node = { }; + +if (!Node.ELEMENT_NODE) { + Object.extend(Node, { + ELEMENT_NODE: 1, + ATTRIBUTE_NODE: 2, + TEXT_NODE: 3, + CDATA_SECTION_NODE: 4, + ENTITY_REFERENCE_NODE: 5, + ENTITY_NODE: 6, + PROCESSING_INSTRUCTION_NODE: 7, + COMMENT_NODE: 8, + DOCUMENT_NODE: 9, + DOCUMENT_TYPE_NODE: 10, + DOCUMENT_FRAGMENT_NODE: 11, + NOTATION_NODE: 12 + }); +} + + + +(function(global) { + + var HAS_EXTENDED_CREATE_ELEMENT_SYNTAX = (function(){ + try { + var el = document.createElement(''); + return el.tagName.toLowerCase() === 'input' && el.name === 'x'; + } + catch(err) { + return false; + } + })(); + + var element = global.Element; + + global.Element = function(tagName, attributes) { + attributes = attributes || { }; + tagName = tagName.toLowerCase(); + var cache = Element.cache; + if (HAS_EXTENDED_CREATE_ELEMENT_SYNTAX && attributes.name) { + tagName = '<' + tagName + ' name="' + attributes.name + '">'; + delete attributes.name; + return Element.writeAttribute(document.createElement(tagName), attributes); + } + if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName)); + return Element.writeAttribute(cache[tagName].cloneNode(false), attributes); + }; + + Object.extend(global.Element, element || { }); + if (element) global.Element.prototype = element.prototype; + +})(this); + +Element.idCounter = 1; +Element.cache = { }; + +function purgeElement(element) { + var uid = element._prototypeUID; + if (uid) { + Element.stopObserving(element); + element._prototypeUID = void 0; + delete Element.Storage[uid]; + } +} + +Element.Methods = { + visible: function(element) { + return $(element).style.display != 'none'; + }, + + toggle: function(element) { + element = $(element); + Element[Element.visible(element) ? 'hide' : 'show'](element); + return element; + }, + + hide: function(element) { + element = $(element); + element.style.display = 'none'; + return element; + }, + + show: function(element) { + element = $(element); + element.style.display = ''; + return element; + }, + + remove: function(element) { + element = $(element); + element.parentNode.removeChild(element); + return element; + }, + + update: (function(){ + + var SELECT_ELEMENT_INNERHTML_BUGGY = (function(){ + var el = document.createElement("select"), + isBuggy = true; + el.innerHTML = ""; + if (el.options && el.options[0]) { + isBuggy = el.options[0].nodeName.toUpperCase() !== "OPTION"; + } + el = null; + return isBuggy; + })(); + + var TABLE_ELEMENT_INNERHTML_BUGGY = (function(){ + try { + var el = document.createElement("table"); + if (el && el.tBodies) { + el.innerHTML = "test"; + var isBuggy = typeof el.tBodies[0] == "undefined"; + el = null; + return isBuggy; + } + } catch (e) { + return true; + } + })(); + + var SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING = (function () { + var s = document.createElement("script"), + isBuggy = false; + try { + s.appendChild(document.createTextNode("")); + isBuggy = !s.firstChild || + s.firstChild && s.firstChild.nodeType !== 3; + } catch (e) { + isBuggy = true; + } + s = null; + return isBuggy; + })(); + + function update(element, content) { + element = $(element); + + var descendants = element.getElementsByTagName('*'), + i = descendants.length; + while (i--) purgeElement(descendants[i]); + + if (content && content.toElement) + content = content.toElement(); + + if (Object.isElement(content)) + return element.update().insert(content); + + content = Object.toHTML(content); + + var tagName = element.tagName.toUpperCase(); + + if (tagName === 'SCRIPT' && SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING) { + element.text = content; + return element; + } + + if (SELECT_ELEMENT_INNERHTML_BUGGY || TABLE_ELEMENT_INNERHTML_BUGGY) { + if (tagName in Element._insertionTranslations.tags) { + while (element.firstChild) { + element.removeChild(element.firstChild); + } + Element._getContentFromAnonymousElement(tagName, content.stripScripts()) + .each(function(node) { + element.appendChild(node) + }); + } + else { + element.innerHTML = content.stripScripts(); + } + } + else { + element.innerHTML = content.stripScripts(); + } + + content.evalScripts.bind(content).defer(); + return element; + } + + return update; + })(), + + replace: function(element, content) { + element = $(element); + if (content && content.toElement) content = content.toElement(); + else if (!Object.isElement(content)) { + content = Object.toHTML(content); + var range = element.ownerDocument.createRange(); + range.selectNode(element); + content.evalScripts.bind(content).defer(); + content = range.createContextualFragment(content.stripScripts()); + } + element.parentNode.replaceChild(content, element); + return element; + }, + + insert: function(element, insertions) { + element = $(element); + + if (Object.isString(insertions) || Object.isNumber(insertions) || + Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML))) + insertions = {bottom:insertions}; + + var content, insert, tagName, childNodes; + + for (var position in insertions) { + content = insertions[position]; + position = position.toLowerCase(); + insert = Element._insertionTranslations[position]; + + if (content && content.toElement) content = content.toElement(); + if (Object.isElement(content)) { + insert(element, content); + continue; + } + + content = Object.toHTML(content); + + tagName = ((position == 'before' || position == 'after') + ? element.parentNode : element).tagName.toUpperCase(); + + childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); + + if (position == 'top' || position == 'after') childNodes.reverse(); + childNodes.each(insert.curry(element)); + + content.evalScripts.bind(content).defer(); + } + + return element; + }, + + wrap: function(element, wrapper, attributes) { + element = $(element); + if (Object.isElement(wrapper)) + $(wrapper).writeAttribute(attributes || { }); + else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes); + else wrapper = new Element('div', wrapper); + if (element.parentNode) + element.parentNode.replaceChild(wrapper, element); + wrapper.appendChild(element); + return wrapper; + }, + + inspect: function(element) { + element = $(element); + var result = '<' + element.tagName.toLowerCase(); + $H({'id': 'id', 'className': 'class'}).each(function(pair) { + var property = pair.first(), + attribute = pair.last(), + value = (element[property] || '').toString(); + if (value) result += ' ' + attribute + '=' + value.inspect(true); + }); + return result + '>'; + }, + + recursivelyCollect: function(element, property, maximumLength) { + element = $(element); + maximumLength = maximumLength || -1; + var elements = []; + + while (element = element[property]) { + if (element.nodeType == 1) + elements.push(Element.extend(element)); + if (elements.length == maximumLength) + break; + } + + return elements; + }, + + ancestors: function(element) { + return Element.recursivelyCollect(element, 'parentNode'); + }, + + descendants: function(element) { + return Element.select(element, "*"); + }, + + firstDescendant: function(element) { + element = $(element).firstChild; + while (element && element.nodeType != 1) element = element.nextSibling; + return $(element); + }, + + immediateDescendants: function(element) { + var results = [], child = $(element).firstChild; + while (child) { + if (child.nodeType === 1) { + results.push(Element.extend(child)); + } + child = child.nextSibling; + } + return results; + }, + + previousSiblings: function(element, maximumLength) { + return Element.recursivelyCollect(element, 'previousSibling'); + }, + + nextSiblings: function(element) { + return Element.recursivelyCollect(element, 'nextSibling'); + }, + + siblings: function(element) { + element = $(element); + return Element.previousSiblings(element).reverse() + .concat(Element.nextSiblings(element)); + }, + + match: function(element, selector) { + element = $(element); + if (Object.isString(selector)) + return Prototype.Selector.match(element, selector); + return selector.match(element); + }, + + up: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return $(element.parentNode); + var ancestors = Element.ancestors(element); + return Object.isNumber(expression) ? ancestors[expression] : + Prototype.Selector.find(ancestors, expression, index); + }, + + down: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return Element.firstDescendant(element); + return Object.isNumber(expression) ? Element.descendants(element)[expression] : + Element.select(element, expression)[index || 0]; + }, + + previous: function(element, expression, index) { + element = $(element); + if (Object.isNumber(expression)) index = expression, expression = false; + if (!Object.isNumber(index)) index = 0; + + if (expression) { + return Prototype.Selector.find(element.previousSiblings(), expression, index); + } else { + return element.recursivelyCollect("previousSibling", index + 1)[index]; + } + }, + + next: function(element, expression, index) { + element = $(element); + if (Object.isNumber(expression)) index = expression, expression = false; + if (!Object.isNumber(index)) index = 0; + + if (expression) { + return Prototype.Selector.find(element.nextSiblings(), expression, index); + } else { + var maximumLength = Object.isNumber(index) ? index + 1 : 1; + return element.recursivelyCollect("nextSibling", index + 1)[index]; + } + }, + + + select: function(element) { + element = $(element); + var expressions = Array.prototype.slice.call(arguments, 1).join(', '); + return Prototype.Selector.select(expressions, element); + }, + + adjacent: function(element) { + element = $(element); + var expressions = Array.prototype.slice.call(arguments, 1).join(', '); + return Prototype.Selector.select(expressions, element.parentNode).without(element); + }, + + identify: function(element) { + element = $(element); + var id = Element.readAttribute(element, 'id'); + if (id) return id; + do { id = 'anonymous_element_' + Element.idCounter++ } while ($(id)); + Element.writeAttribute(element, 'id', id); + return id; + }, + + readAttribute: function(element, name) { + element = $(element); + if (Prototype.Browser.IE) { + var t = Element._attributeTranslations.read; + if (t.values[name]) return t.values[name](element, name); + if (t.names[name]) name = t.names[name]; + if (name.include(':')) { + return (!element.attributes || !element.attributes[name]) ? null : + element.attributes[name].value; + } + } + return element.getAttribute(name); + }, + + writeAttribute: function(element, name, value) { + element = $(element); + var attributes = { }, t = Element._attributeTranslations.write; + + if (typeof name == 'object') attributes = name; + else attributes[name] = Object.isUndefined(value) ? true : value; + + for (var attr in attributes) { + name = t.names[attr] || attr; + value = attributes[attr]; + if (t.values[attr]) name = t.values[attr](element, value); + if (value === false || value === null) + element.removeAttribute(name); + else if (value === true) + element.setAttribute(name, name); + else element.setAttribute(name, value); + } + return element; + }, + + getHeight: function(element) { + return Element.getDimensions(element).height; + }, + + getWidth: function(element) { + return Element.getDimensions(element).width; + }, + + classNames: function(element) { + return new Element.ClassNames(element); + }, + + hasClassName: function(element, className) { + if (!(element = $(element))) return; + var elementClassName = element.className; + return (elementClassName.length > 0 && (elementClassName == className || + new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName))); + }, + + addClassName: function(element, className) { + if (!(element = $(element))) return; + if (!Element.hasClassName(element, className)) + element.className += (element.className ? ' ' : '') + className; + return element; + }, + + removeClassName: function(element, className) { + if (!(element = $(element))) return; + element.className = element.className.replace( + new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip(); + return element; + }, + + toggleClassName: function(element, className) { + if (!(element = $(element))) return; + return Element[Element.hasClassName(element, className) ? + 'removeClassName' : 'addClassName'](element, className); + }, + + cleanWhitespace: function(element) { + element = $(element); + var node = element.firstChild; + while (node) { + var nextNode = node.nextSibling; + if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) + element.removeChild(node); + node = nextNode; + } + return element; + }, + + empty: function(element) { + return $(element).innerHTML.blank(); + }, + + descendantOf: function(element, ancestor) { + element = $(element), ancestor = $(ancestor); + + if (element.compareDocumentPosition) + return (element.compareDocumentPosition(ancestor) & 8) === 8; + + if (ancestor.contains) + return ancestor.contains(element) && ancestor !== element; + + while (element = element.parentNode) + if (element == ancestor) return true; + + return false; + }, + + scrollTo: function(element) { + element = $(element); + var pos = Element.cumulativeOffset(element); + window.scrollTo(pos[0], pos[1]); + return element; + }, + + getStyle: function(element, style) { + element = $(element); + style = style == 'float' ? 'cssFloat' : style.camelize(); + var value = element.style[style]; + if (!value || value == 'auto') { + var css = document.defaultView.getComputedStyle(element, null); + value = css ? css[style] : null; + } + if (style == 'opacity') return value ? parseFloat(value) : 1.0; + return value == 'auto' ? null : value; + }, + + getOpacity: function(element) { + return $(element).getStyle('opacity'); + }, + + setStyle: function(element, styles) { + element = $(element); + var elementStyle = element.style, match; + if (Object.isString(styles)) { + element.style.cssText += ';' + styles; + return styles.include('opacity') ? + element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element; + } + for (var property in styles) + if (property == 'opacity') element.setOpacity(styles[property]); + else + elementStyle[(property == 'float' || property == 'cssFloat') ? + (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') : + property] = styles[property]; + + return element; + }, + + setOpacity: function(element, value) { + element = $(element); + element.style.opacity = (value == 1 || value === '') ? '' : + (value < 0.00001) ? 0 : value; + return element; + }, + + makePositioned: function(element) { + element = $(element); + var pos = Element.getStyle(element, 'position'); + if (pos == 'static' || !pos) { + element._madePositioned = true; + element.style.position = 'relative'; + if (Prototype.Browser.Opera) { + element.style.top = 0; + element.style.left = 0; + } + } + return element; + }, + + undoPositioned: function(element) { + element = $(element); + if (element._madePositioned) { + element._madePositioned = undefined; + element.style.position = + element.style.top = + element.style.left = + element.style.bottom = + element.style.right = ''; + } + return element; + }, + + makeClipping: function(element) { + element = $(element); + if (element._overflow) return element; + element._overflow = Element.getStyle(element, 'overflow') || 'auto'; + if (element._overflow !== 'hidden') + element.style.overflow = 'hidden'; + return element; + }, + + undoClipping: function(element) { + element = $(element); + if (!element._overflow) return element; + element.style.overflow = element._overflow == 'auto' ? '' : element._overflow; + element._overflow = null; + return element; + }, + + cumulativeOffset: function(element) { + var valueT = 0, valueL = 0; + if (element.parentNode) { + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + } while (element); + } + return Element._returnOffset(valueL, valueT); + }, + + positionedOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + if (element) { + if (element.tagName.toUpperCase() == 'BODY') break; + var p = Element.getStyle(element, 'position'); + if (p !== 'static') break; + } + } while (element); + return Element._returnOffset(valueL, valueT); + }, + + absolutize: function(element) { + element = $(element); + if (Element.getStyle(element, 'position') == 'absolute') return element; + + var offsets = Element.positionedOffset(element), + top = offsets[1], + left = offsets[0], + width = element.clientWidth, + height = element.clientHeight; + + element._originalLeft = left - parseFloat(element.style.left || 0); + element._originalTop = top - parseFloat(element.style.top || 0); + element._originalWidth = element.style.width; + element._originalHeight = element.style.height; + + element.style.position = 'absolute'; + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.width = width + 'px'; + element.style.height = height + 'px'; + return element; + }, + + relativize: function(element) { + element = $(element); + if (Element.getStyle(element, 'position') == 'relative') return element; + + element.style.position = 'relative'; + var top = parseFloat(element.style.top || 0) - (element._originalTop || 0), + left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); + + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.height = element._originalHeight; + element.style.width = element._originalWidth; + return element; + }, + + cumulativeScrollOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.scrollTop || 0; + valueL += element.scrollLeft || 0; + element = element.parentNode; + } while (element); + return Element._returnOffset(valueL, valueT); + }, + + getOffsetParent: function(element) { + if (element.offsetParent) return $(element.offsetParent); + if (element == document.body) return $(element); + + while ((element = element.parentNode) && element != document.body) + if (Element.getStyle(element, 'position') != 'static') + return $(element); + + return $(document.body); + }, + + viewportOffset: function(forElement) { + var valueT = 0, + valueL = 0, + element = forElement; + + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + + if (element.offsetParent == document.body && + Element.getStyle(element, 'position') == 'absolute') break; + + } while (element = element.offsetParent); + + element = forElement; + do { + if (!Prototype.Browser.Opera || (element.tagName && (element.tagName.toUpperCase() == 'BODY'))) { + valueT -= element.scrollTop || 0; + valueL -= element.scrollLeft || 0; + } + } while (element = element.parentNode); + + return Element._returnOffset(valueL, valueT); + }, + + clonePosition: function(element, source) { + var options = Object.extend({ + setLeft: true, + setTop: true, + setWidth: true, + setHeight: true, + offsetTop: 0, + offsetLeft: 0 + }, arguments[2] || { }); + + source = $(source); + var p = Element.viewportOffset(source), delta = [0, 0], parent = null; + + element = $(element); + + if (Element.getStyle(element, 'position') == 'absolute') { + parent = Element.getOffsetParent(element); + delta = Element.viewportOffset(parent); + } + + if (parent == document.body) { + delta[0] -= document.body.offsetLeft; + delta[1] -= document.body.offsetTop; + } + + if (options.setLeft) element.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; + if (options.setTop) element.style.top = (p[1] - delta[1] + options.offsetTop) + 'px'; + if (options.setWidth) element.style.width = source.offsetWidth + 'px'; + if (options.setHeight) element.style.height = source.offsetHeight + 'px'; + return element; + } +}; + +Object.extend(Element.Methods, { + getElementsBySelector: Element.Methods.select, + + childElements: Element.Methods.immediateDescendants +}); + +Element._attributeTranslations = { + write: { + names: { + className: 'class', + htmlFor: 'for' + }, + values: { } + } +}; + +if (Prototype.Browser.Opera) { + Element.Methods.getStyle = Element.Methods.getStyle.wrap( + function(proceed, element, style) { + switch (style) { + case 'left': case 'top': case 'right': case 'bottom': + if (proceed(element, 'position') === 'static') return null; + case 'height': case 'width': + if (!Element.visible(element)) return null; + + var dim = parseInt(proceed(element, style), 10); + + if (dim !== element['offset' + style.capitalize()]) + return dim + 'px'; + + var properties; + if (style === 'height') { + properties = ['border-top-width', 'padding-top', + 'padding-bottom', 'border-bottom-width']; + } + else { + properties = ['border-left-width', 'padding-left', + 'padding-right', 'border-right-width']; + } + return properties.inject(dim, function(memo, property) { + var val = proceed(element, property); + return val === null ? memo : memo - parseInt(val, 10); + }) + 'px'; + default: return proceed(element, style); + } + } + ); + + Element.Methods.readAttribute = Element.Methods.readAttribute.wrap( + function(proceed, element, attribute) { + if (attribute === 'title') return element.title; + return proceed(element, attribute); + } + ); +} + +else if (Prototype.Browser.IE) { + Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap( + function(proceed, element) { + element = $(element); + if (!element.parentNode) return $(document.body); + var position = element.getStyle('position'); + if (position !== 'static') return proceed(element); + element.setStyle({ position: 'relative' }); + var value = proceed(element); + element.setStyle({ position: position }); + return value; + } + ); + + $w('positionedOffset viewportOffset').each(function(method) { + Element.Methods[method] = Element.Methods[method].wrap( + function(proceed, element) { + element = $(element); + if (!element.parentNode) return Element._returnOffset(0, 0); + var position = element.getStyle('position'); + if (position !== 'static') return proceed(element); + var offsetParent = element.getOffsetParent(); + if (offsetParent && offsetParent.getStyle('position') === 'fixed') + offsetParent.setStyle({ zoom: 1 }); + element.setStyle({ position: 'relative' }); + var value = proceed(element); + element.setStyle({ position: position }); + return value; + } + ); + }); + + Element.Methods.getStyle = function(element, style) { + element = $(element); + style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize(); + var value = element.style[style]; + if (!value && element.currentStyle) value = element.currentStyle[style]; + + if (style == 'opacity') { + if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/)) + if (value[1]) return parseFloat(value[1]) / 100; + return 1.0; + } + + if (value == 'auto') { + if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none')) + return element['offset' + style.capitalize()] + 'px'; + return null; + } + return value; + }; + + Element.Methods.setOpacity = function(element, value) { + function stripAlpha(filter){ + return filter.replace(/alpha\([^\)]*\)/gi,''); + } + element = $(element); + var currentStyle = element.currentStyle; + if ((currentStyle && !currentStyle.hasLayout) || + (!currentStyle && element.style.zoom == 'normal')) + element.style.zoom = 1; + + var filter = element.getStyle('filter'), style = element.style; + if (value == 1 || value === '') { + (filter = stripAlpha(filter)) ? + style.filter = filter : style.removeAttribute('filter'); + return element; + } else if (value < 0.00001) value = 0; + style.filter = stripAlpha(filter) + + 'alpha(opacity=' + (value * 100) + ')'; + return element; + }; + + Element._attributeTranslations = (function(){ + + var classProp = 'className', + forProp = 'for', + el = document.createElement('div'); + + el.setAttribute(classProp, 'x'); + + if (el.className !== 'x') { + el.setAttribute('class', 'x'); + if (el.className === 'x') { + classProp = 'class'; + } + } + el = null; + + el = document.createElement('label'); + el.setAttribute(forProp, 'x'); + if (el.htmlFor !== 'x') { + el.setAttribute('htmlFor', 'x'); + if (el.htmlFor === 'x') { + forProp = 'htmlFor'; + } + } + el = null; + + return { + read: { + names: { + 'class': classProp, + 'className': classProp, + 'for': forProp, + 'htmlFor': forProp + }, + values: { + _getAttr: function(element, attribute) { + return element.getAttribute(attribute); + }, + _getAttr2: function(element, attribute) { + return element.getAttribute(attribute, 2); + }, + _getAttrNode: function(element, attribute) { + var node = element.getAttributeNode(attribute); + return node ? node.value : ""; + }, + _getEv: (function(){ + + var el = document.createElement('div'), f; + el.onclick = Prototype.emptyFunction; + var value = el.getAttribute('onclick'); + + if (String(value).indexOf('{') > -1) { + f = function(element, attribute) { + attribute = element.getAttribute(attribute); + if (!attribute) return null; + attribute = attribute.toString(); + attribute = attribute.split('{')[1]; + attribute = attribute.split('}')[0]; + return attribute.strip(); + }; + } + else if (value === '') { + f = function(element, attribute) { + attribute = element.getAttribute(attribute); + if (!attribute) return null; + return attribute.strip(); + }; + } + el = null; + return f; + })(), + _flag: function(element, attribute) { + return $(element).hasAttribute(attribute) ? attribute : null; + }, + style: function(element) { + return element.style.cssText.toLowerCase(); + }, + title: function(element) { + return element.title; + } + } + } + } + })(); + + Element._attributeTranslations.write = { + names: Object.extend({ + cellpadding: 'cellPadding', + cellspacing: 'cellSpacing' + }, Element._attributeTranslations.read.names), + values: { + checked: function(element, value) { + element.checked = !!value; + }, + + style: function(element, value) { + element.style.cssText = value ? value : ''; + } + } + }; + + Element._attributeTranslations.has = {}; + + $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' + + 'encType maxLength readOnly longDesc frameBorder').each(function(attr) { + Element._attributeTranslations.write.names[attr.toLowerCase()] = attr; + Element._attributeTranslations.has[attr.toLowerCase()] = attr; + }); + + (function(v) { + Object.extend(v, { + href: v._getAttr2, + src: v._getAttr2, + type: v._getAttr, + action: v._getAttrNode, + disabled: v._flag, + checked: v._flag, + readonly: v._flag, + multiple: v._flag, + onload: v._getEv, + onunload: v._getEv, + onclick: v._getEv, + ondblclick: v._getEv, + onmousedown: v._getEv, + onmouseup: v._getEv, + onmouseover: v._getEv, + onmousemove: v._getEv, + onmouseout: v._getEv, + onfocus: v._getEv, + onblur: v._getEv, + onkeypress: v._getEv, + onkeydown: v._getEv, + onkeyup: v._getEv, + onsubmit: v._getEv, + onreset: v._getEv, + onselect: v._getEv, + onchange: v._getEv + }); + })(Element._attributeTranslations.read.values); + + if (Prototype.BrowserFeatures.ElementExtensions) { + (function() { + function _descendants(element) { + var nodes = element.getElementsByTagName('*'), results = []; + for (var i = 0, node; node = nodes[i]; i++) + if (node.tagName !== "!") // Filter out comment nodes. + results.push(node); + return results; + } + + Element.Methods.down = function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return element.firstDescendant(); + return Object.isNumber(expression) ? _descendants(element)[expression] : + Element.select(element, expression)[index || 0]; + } + })(); + } + +} + +else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) { + Element.Methods.setOpacity = function(element, value) { + element = $(element); + element.style.opacity = (value == 1) ? 0.999999 : + (value === '') ? '' : (value < 0.00001) ? 0 : value; + return element; + }; +} + +else if (Prototype.Browser.WebKit) { + Element.Methods.setOpacity = function(element, value) { + element = $(element); + element.style.opacity = (value == 1 || value === '') ? '' : + (value < 0.00001) ? 0 : value; + + if (value == 1) + if (element.tagName.toUpperCase() == 'IMG' && element.width) { + element.width++; element.width--; + } else try { + var n = document.createTextNode(' '); + element.appendChild(n); + element.removeChild(n); + } catch (e) { } + + return element; + }; + + Element.Methods.cumulativeOffset = function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + if (element.offsetParent == document.body) + if (Element.getStyle(element, 'position') == 'absolute') break; + + element = element.offsetParent; + } while (element); + + return Element._returnOffset(valueL, valueT); + }; +} + +if ('outerHTML' in document.documentElement) { + Element.Methods.replace = function(element, content) { + element = $(element); + + if (content && content.toElement) content = content.toElement(); + if (Object.isElement(content)) { + element.parentNode.replaceChild(content, element); + return element; + } + + content = Object.toHTML(content); + var parent = element.parentNode, tagName = parent.tagName.toUpperCase(); + + if (Element._insertionTranslations.tags[tagName]) { + var nextSibling = element.next(), + fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); + parent.removeChild(element); + if (nextSibling) + fragments.each(function(node) { parent.insertBefore(node, nextSibling) }); + else + fragments.each(function(node) { parent.appendChild(node) }); + } + else element.outerHTML = content.stripScripts(); + + content.evalScripts.bind(content).defer(); + return element; + }; +} + +Element._returnOffset = function(l, t) { + var result = [l, t]; + result.left = l; + result.top = t; + return result; +}; + +Element._getContentFromAnonymousElement = function(tagName, html) { + var div = new Element('div'), + t = Element._insertionTranslations.tags[tagName]; + if (t) { + div.innerHTML = t[0] + html + t[1]; + for (var i = t[2]; i--; ) { + div = div.firstChild; + } + } + else { + div.innerHTML = html; + } + return $A(div.childNodes); +}; + +Element._insertionTranslations = { + before: function(element, node) { + element.parentNode.insertBefore(node, element); + }, + top: function(element, node) { + element.insertBefore(node, element.firstChild); + }, + bottom: function(element, node) { + element.appendChild(node); + }, + after: function(element, node) { + element.parentNode.insertBefore(node, element.nextSibling); + }, + tags: { + TABLE: ['', '
    ', 1], + TBODY: ['', '
    ', 2], + TR: ['', '
    ', 3], + TD: ['
    ', '
    ', 4], + SELECT: ['', 1] + } +}; + +(function() { + var tags = Element._insertionTranslations.tags; + Object.extend(tags, { + THEAD: tags.TBODY, + TFOOT: tags.TBODY, + TH: tags.TD + }); +})(); + +Element.Methods.Simulated = { + hasAttribute: function(element, attribute) { + attribute = Element._attributeTranslations.has[attribute] || attribute; + var node = $(element).getAttributeNode(attribute); + return !!(node && node.specified); + } +}; + +Element.Methods.ByTag = { }; + +Object.extend(Element, Element.Methods); + +(function(div) { + + if (!Prototype.BrowserFeatures.ElementExtensions && div['__proto__']) { + window.HTMLElement = { }; + window.HTMLElement.prototype = div['__proto__']; + Prototype.BrowserFeatures.ElementExtensions = true; + } + + div = null; + +})(document.createElement('div')); + +Element.extend = (function() { + + function checkDeficiency(tagName) { + if (typeof window.Element != 'undefined') { + var proto = window.Element.prototype; + if (proto) { + var id = '_' + (Math.random()+'').slice(2), + el = document.createElement(tagName); + proto[id] = 'x'; + var isBuggy = (el[id] !== 'x'); + delete proto[id]; + el = null; + return isBuggy; + } + } + return false; + } + + function extendElementWith(element, methods) { + for (var property in methods) { + var value = methods[property]; + if (Object.isFunction(value) && !(property in element)) + element[property] = value.methodize(); + } + } + + var HTMLOBJECTELEMENT_PROTOTYPE_BUGGY = checkDeficiency('object'); + + if (Prototype.BrowserFeatures.SpecificElementExtensions) { + if (HTMLOBJECTELEMENT_PROTOTYPE_BUGGY) { + return function(element) { + if (element && typeof element._extendedByPrototype == 'undefined') { + var t = element.tagName; + if (t && (/^(?:object|applet|embed)$/i.test(t))) { + extendElementWith(element, Element.Methods); + extendElementWith(element, Element.Methods.Simulated); + extendElementWith(element, Element.Methods.ByTag[t.toUpperCase()]); + } + } + return element; + } + } + return Prototype.K; + } + + var Methods = { }, ByTag = Element.Methods.ByTag; + + var extend = Object.extend(function(element) { + if (!element || typeof element._extendedByPrototype != 'undefined' || + element.nodeType != 1 || element == window) return element; + + var methods = Object.clone(Methods), + tagName = element.tagName.toUpperCase(); + + if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]); + + extendElementWith(element, methods); + + element._extendedByPrototype = Prototype.emptyFunction; + return element; + + }, { + refresh: function() { + if (!Prototype.BrowserFeatures.ElementExtensions) { + Object.extend(Methods, Element.Methods); + Object.extend(Methods, Element.Methods.Simulated); + } + } + }); + + extend.refresh(); + return extend; +})(); + +if (document.documentElement.hasAttribute) { + Element.hasAttribute = function(element, attribute) { + return element.hasAttribute(attribute); + }; +} +else { + Element.hasAttribute = Element.Methods.Simulated.hasAttribute; +} + +Element.addMethods = function(methods) { + var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag; + + if (!methods) { + Object.extend(Form, Form.Methods); + Object.extend(Form.Element, Form.Element.Methods); + Object.extend(Element.Methods.ByTag, { + "FORM": Object.clone(Form.Methods), + "INPUT": Object.clone(Form.Element.Methods), + "SELECT": Object.clone(Form.Element.Methods), + "TEXTAREA": Object.clone(Form.Element.Methods) + }); + } + + if (arguments.length == 2) { + var tagName = methods; + methods = arguments[1]; + } + + if (!tagName) Object.extend(Element.Methods, methods || { }); + else { + if (Object.isArray(tagName)) tagName.each(extend); + else extend(tagName); + } + + function extend(tagName) { + tagName = tagName.toUpperCase(); + if (!Element.Methods.ByTag[tagName]) + Element.Methods.ByTag[tagName] = { }; + Object.extend(Element.Methods.ByTag[tagName], methods); + } + + function copy(methods, destination, onlyIfAbsent) { + onlyIfAbsent = onlyIfAbsent || false; + for (var property in methods) { + var value = methods[property]; + if (!Object.isFunction(value)) continue; + if (!onlyIfAbsent || !(property in destination)) + destination[property] = value.methodize(); + } + } + + function findDOMClass(tagName) { + var klass; + var trans = { + "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph", + "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList", + "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading", + "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote", + "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION": + "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD": + "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR": + "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET": + "FrameSet", "IFRAME": "IFrame" + }; + if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element'; + if (window[klass]) return window[klass]; + klass = 'HTML' + tagName + 'Element'; + if (window[klass]) return window[klass]; + klass = 'HTML' + tagName.capitalize() + 'Element'; + if (window[klass]) return window[klass]; + + var element = document.createElement(tagName), + proto = element['__proto__'] || element.constructor.prototype; + + element = null; + return proto; + } + + var elementPrototype = window.HTMLElement ? HTMLElement.prototype : + Element.prototype; + + if (F.ElementExtensions) { + copy(Element.Methods, elementPrototype); + copy(Element.Methods.Simulated, elementPrototype, true); + } + + if (F.SpecificElementExtensions) { + for (var tag in Element.Methods.ByTag) { + var klass = findDOMClass(tag); + if (Object.isUndefined(klass)) continue; + copy(T[tag], klass.prototype); + } + } + + Object.extend(Element, Element.Methods); + delete Element.ByTag; + + if (Element.extend.refresh) Element.extend.refresh(); + Element.cache = { }; +}; + + +document.viewport = { + + getDimensions: function() { + return { width: this.getWidth(), height: this.getHeight() }; + }, + + getScrollOffsets: function() { + return Element._returnOffset( + window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft, + window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop); + } +}; + +(function(viewport) { + var B = Prototype.Browser, doc = document, element, property = {}; + + function getRootElement() { + if (B.WebKit && !doc.evaluate) + return document; + + if (B.Opera && window.parseFloat(window.opera.version()) < 9.5) + return document.body; + + return document.documentElement; + } + + function define(D) { + if (!element) element = getRootElement(); + + property[D] = 'client' + D; + + viewport['get' + D] = function() { return element[property[D]] }; + return viewport['get' + D](); + } + + viewport.getWidth = define.curry('Width'); + + viewport.getHeight = define.curry('Height'); +})(document.viewport); + + +Element.Storage = { + UID: 1 +}; + +Element.addMethods({ + getStorage: function(element) { + if (!(element = $(element))) return; + + var uid; + if (element === window) { + uid = 0; + } else { + if (typeof element._prototypeUID === "undefined") + element._prototypeUID = Element.Storage.UID++; + uid = element._prototypeUID; + } + + if (!Element.Storage[uid]) + Element.Storage[uid] = $H(); + + return Element.Storage[uid]; + }, + + store: function(element, key, value) { + if (!(element = $(element))) return; + + if (arguments.length === 2) { + Element.getStorage(element).update(key); + } else { + Element.getStorage(element).set(key, value); + } + + return element; + }, + + retrieve: function(element, key, defaultValue) { + if (!(element = $(element))) return; + var hash = Element.getStorage(element), value = hash.get(key); + + if (Object.isUndefined(value)) { + hash.set(key, defaultValue); + value = defaultValue; + } + + return value; + }, + + clone: function(element, deep) { + if (!(element = $(element))) return; + var clone = element.cloneNode(deep); + clone._prototypeUID = void 0; + if (deep) { + var descendants = Element.select(clone, '*'), + i = descendants.length; + while (i--) { + descendants[i]._prototypeUID = void 0; + } + } + return Element.extend(clone); + }, + + purge: function(element) { + if (!(element = $(element))) return; + purgeElement(element); + + var descendants = element.getElementsByTagName('*'), + i = descendants.length; + + while (i--) purgeElement(descendants[i]); + + return null; + } +}); + +(function() { + + function toDecimal(pctString) { + var match = pctString.match(/^(\d+)%?$/i); + if (!match) return null; + return (Number(match[1]) / 100); + } + + function getPixelValue(value, property) { + if (Object.isElement(value)) { + element = value; + value = element.getStyle(property); + } + if (value === null) { + return null; + } + + if ((/^(?:-)?\d+(\.\d+)?(px)?$/i).test(value)) { + return window.parseFloat(value); + } + + if (/\d/.test(value) && element.runtimeStyle) { + var style = element.style.left, rStyle = element.runtimeStyle.left; + element.runtimeStyle.left = element.currentStyle.left; + element.style.left = value || 0; + value = element.style.pixelLeft; + element.style.left = style; + element.runtimeStyle.left = rStyle; + + return value; + } + + if (value.include('%')) { + var decimal = toDecimal(value); + var whole; + if (property.include('left') || property.include('right') || + property.include('width')) { + whole = $(element.parentNode).measure('width'); + } else if (property.include('top') || property.include('bottom') || + property.include('height')) { + whole = $(element.parentNode).measure('height'); + } + + return whole * decimal; + } + + return 0; + } + + function toCSSPixels(number) { + if (Object.isString(number) && number.endsWith('px')) { + return number; + } + return number + 'px'; + } + + function isDisplayed(element) { + var originalElement = element; + while (element && element.parentNode) { + var display = element.getStyle('display'); + if (display === 'none') { + return false; + } + element = $(element.parentNode); + } + return true; + } + + var hasLayout = Prototype.K; + if ('currentStyle' in document.documentElement) { + hasLayout = function(element) { + if (!element.currentStyle.hasLayout) { + element.style.zoom = 1; + } + return element; + }; + } + + function cssNameFor(key) { + if (key.include('border')) key = key + '-width'; + return key.camelize(); + } + + Element.Layout = Class.create(Hash, { + initialize: function($super, element, preCompute) { + $super(); + this.element = $(element); + + Element.Layout.PROPERTIES.each( function(property) { + this._set(property, null); + }, this); + + if (preCompute) { + this._preComputing = true; + this._begin(); + Element.Layout.PROPERTIES.each( this._compute, this ); + this._end(); + this._preComputing = false; + } + }, + + _set: function(property, value) { + return Hash.prototype.set.call(this, property, value); + }, + + set: function(property, value) { + throw "Properties of Element.Layout are read-only."; + }, + + get: function($super, property) { + var value = $super(property); + return value === null ? this._compute(property) : value; + }, + + _begin: function() { + if (this._prepared) return; + + var element = this.element; + if (isDisplayed(element)) { + this._prepared = true; + return; + } + + var originalStyles = { + position: element.style.position || '', + width: element.style.width || '', + visibility: element.style.visibility || '', + display: element.style.display || '' + }; + + element.store('prototype_original_styles', originalStyles); + + var position = element.getStyle('position'), + width = element.getStyle('width'); + + element.setStyle({ + position: 'absolute', + visibility: 'hidden', + display: 'block' + }); + + var positionedWidth = element.getStyle('width'); + + var newWidth; + if (width && (positionedWidth === width)) { + newWidth = getPixelValue(width); + } else if (width && (position === 'absolute' || position === 'fixed')) { + newWidth = getPixelValue(width); + } else { + var parent = element.parentNode, pLayout = $(parent).getLayout(); + + newWidth = pLayout.get('width') - + this.get('margin-left') - + this.get('border-left') - + this.get('padding-left') - + this.get('padding-right') - + this.get('border-right') - + this.get('margin-right'); + } + + element.setStyle({ width: newWidth + 'px' }); + + this._prepared = true; + }, + + _end: function() { + var element = this.element; + var originalStyles = element.retrieve('prototype_original_styles'); + element.store('prototype_original_styles', null); + element.setStyle(originalStyles); + this._prepared = false; + }, + + _compute: function(property) { + var COMPUTATIONS = Element.Layout.COMPUTATIONS; + if (!(property in COMPUTATIONS)) { + throw "Property not found."; + } + return this._set(property, COMPUTATIONS[property].call(this, this.element)); + }, + + toObject: function() { + var args = $A(arguments); + var keys = (args.length === 0) ? Element.Layout.PROPERTIES : + args.join(' ').split(' '); + var obj = {}; + keys.each( function(key) { + if (!Element.Layout.PROPERTIES.include(key)) return; + var value = this.get(key); + if (value != null) obj[key] = value; + }, this); + return obj; + }, + + toHash: function() { + var obj = this.toObject.apply(this, arguments); + return new Hash(obj); + }, + + toCSS: function() { + var args = $A(arguments); + var keys = (args.length === 0) ? Element.Layout.PROPERTIES : + args.join(' ').split(' '); + var css = {}; + + keys.each( function(key) { + if (!Element.Layout.PROPERTIES.include(key)) return; + if (Element.Layout.COMPOSITE_PROPERTIES.include(key)) return; + + var value = this.get(key); + if (value != null) css[cssNameFor(key)] = value + 'px'; + }, this); + return css; + }, + + inspect: function() { + return "#"; + } + }); + + Object.extend(Element.Layout, { + PROPERTIES: $w('height width top left right bottom border-left border-right border-top border-bottom padding-left padding-right padding-top padding-bottom margin-top margin-bottom margin-left margin-right padding-box-width padding-box-height border-box-width border-box-height margin-box-width margin-box-height'), + + COMPOSITE_PROPERTIES: $w('padding-box-width padding-box-height margin-box-width margin-box-height border-box-width border-box-height'), + + COMPUTATIONS: { + 'height': function(element) { + if (!this._preComputing) this._begin(); + + var bHeight = this.get('border-box-height'); + if (bHeight <= 0) return 0; + + var bTop = this.get('border-top'), + bBottom = this.get('border-bottom'); + + var pTop = this.get('padding-top'), + pBottom = this.get('padding-bottom'); + + if (!this._preComputing) this._end(); + + return bHeight - bTop - bBottom - pTop - pBottom; + }, + + 'width': function(element) { + if (!this._preComputing) this._begin(); + + var bWidth = this.get('border-box-width'); + if (bWidth <= 0) return 0; + + var bLeft = this.get('border-left'), + bRight = this.get('border-right'); + + var pLeft = this.get('padding-left'), + pRight = this.get('padding-right'); + + if (!this._preComputing) this._end(); + + return bWidth - bLeft - bRight - pLeft - pRight; + }, + + 'padding-box-height': function(element) { + var height = this.get('height'), + pTop = this.get('padding-top'), + pBottom = this.get('padding-bottom'); + + return height + pTop + pBottom; + }, + + 'padding-box-width': function(element) { + var width = this.get('width'), + pLeft = this.get('padding-left'), + pRight = this.get('padding-right'); + + return width + pLeft + pRight; + }, + + 'border-box-height': function(element) { + return element.offsetHeight; + }, + + 'border-box-width': function(element) { + return element.offsetWidth; + }, + + 'margin-box-height': function(element) { + var bHeight = this.get('border-box-height'), + mTop = this.get('margin-top'), + mBottom = this.get('margin-bottom'); + + if (bHeight <= 0) return 0; + + return bHeight + mTop + mBottom; + }, + + 'margin-box-width': function(element) { + var bWidth = this.get('border-box-width'), + mLeft = this.get('margin-left'), + mRight = this.get('margin-right'); + + if (bWidth <= 0) return 0; + + return bWidth + mLeft + mRight; + }, + + 'top': function(element) { + var offset = element.positionedOffset(); + return offset.top; + }, + + 'bottom': function(element) { + var offset = element.positionedOffset(), + parent = element.getOffsetParent(), + pHeight = parent.measure('height'); + + var mHeight = this.get('border-box-height'); + + return pHeight - mHeight - offset.top; + }, + + 'left': function(element) { + var offset = element.positionedOffset(); + return offset.left; + }, + + 'right': function(element) { + var offset = element.positionedOffset(), + parent = element.getOffsetParent(), + pWidth = parent.measure('width'); + + var mWidth = this.get('border-box-width'); + + return pWidth - mWidth - offset.left; + }, + + 'padding-top': function(element) { + return getPixelValue(element, 'paddingTop'); + }, + + 'padding-bottom': function(element) { + return getPixelValue(element, 'paddingBottom'); + }, + + 'padding-left': function(element) { + return getPixelValue(element, 'paddingLeft'); + }, + + 'padding-right': function(element) { + return getPixelValue(element, 'paddingRight'); + }, + + 'border-top': function(element) { + return Object.isNumber(element.clientTop) ? element.clientTop : + getPixelValue(element, 'borderTopWidth'); + }, + + 'border-bottom': function(element) { + return Object.isNumber(element.clientBottom) ? element.clientBottom : + getPixelValue(element, 'borderBottomWidth'); + }, + + 'border-left': function(element) { + return Object.isNumber(element.clientLeft) ? element.clientLeft : + getPixelValue(element, 'borderLeftWidth'); + }, + + 'border-right': function(element) { + return Object.isNumber(element.clientRight) ? element.clientRight : + getPixelValue(element, 'borderRightWidth'); + }, + + 'margin-top': function(element) { + return getPixelValue(element, 'marginTop'); + }, + + 'margin-bottom': function(element) { + return getPixelValue(element, 'marginBottom'); + }, + + 'margin-left': function(element) { + return getPixelValue(element, 'marginLeft'); + }, + + 'margin-right': function(element) { + return getPixelValue(element, 'marginRight'); + } + } + }); + + if ('getBoundingClientRect' in document.documentElement) { + Object.extend(Element.Layout.COMPUTATIONS, { + 'right': function(element) { + var parent = hasLayout(element.getOffsetParent()); + var rect = element.getBoundingClientRect(), + pRect = parent.getBoundingClientRect(); + + return (pRect.right - rect.right).round(); + }, + + 'bottom': function(element) { + var parent = hasLayout(element.getOffsetParent()); + var rect = element.getBoundingClientRect(), + pRect = parent.getBoundingClientRect(); + + return (pRect.bottom - rect.bottom).round(); + } + }); + } + + Element.Offset = Class.create({ + initialize: function(left, top) { + this.left = left.round(); + this.top = top.round(); + + this[0] = this.left; + this[1] = this.top; + }, + + relativeTo: function(offset) { + return new Element.Offset( + this.left - offset.left, + this.top - offset.top + ); + }, + + inspect: function() { + return "#".interpolate(this); + }, + + toString: function() { + return "[#{left}, #{top}]".interpolate(this); + }, + + toArray: function() { + return [this.left, this.top]; + } + }); + + function getLayout(element, preCompute) { + return new Element.Layout(element, preCompute); + } + + function measure(element, property) { + return $(element).getLayout().get(property); + } + + function getDimensions(element) { + var layout = $(element).getLayout(); + return { + width: layout.get('width'), + height: layout.get('height') + }; + } + + function getOffsetParent(element) { + if (isDetached(element)) return $(document.body); + + var isInline = (Element.getStyle(element, 'display') === 'inline'); + if (!isInline && element.offsetParent) return $(element.offsetParent); + if (element === document.body) return $(element); + + while ((element = element.parentNode) && element !== document.body) { + if (Element.getStyle(element, 'position') !== 'static') { + return (element.nodeName === 'HTML') ? $(document.body) : $(element); + } + } + + return $(document.body); + } + + + function cumulativeOffset(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + } while (element); + return new Element.Offset(valueL, valueT); + } + + function positionedOffset(element) { + var layout = element.getLayout(); + + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + if (element) { + if (isBody(element)) break; + var p = Element.getStyle(element, 'position'); + if (p !== 'static') break; + } + } while (element); + + valueL -= layout.get('margin-top'); + valueT -= layout.get('margin-left'); + + return new Element.Offset(valueL, valueT); + } + + function cumulativeScrollOffset(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.scrollTop || 0; + valueL += element.scrollLeft || 0; + element = element.parentNode; + } while (element); + return new Element.Offset(valueL, valueT); + } + + function viewportOffset(forElement) { + var valueT = 0, valueL = 0, docBody = document.body; + + var element = forElement; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + if (element.offsetParent == docBody && + Element.getStyle(element, 'position') == 'absolute') break; + } while (element = element.offsetParent); + + element = forElement; + do { + if (element != docBody) { + valueT -= element.scrollTop || 0; + valueL -= element.scrollLeft || 0; + } + } while (element = element.parentNode); + return new Element.Offset(valueL, valueT); + } + + function absolutize(element) { + element = $(element); + + if (Element.getStyle(element, 'position') === 'absolute') { + return element; + } + + var offsetParent = getOffsetParent(element); + var eOffset = element.viewportOffset(), + pOffset = offsetParent.viewportOffset(); + + var offset = eOffset.relativeTo(pOffset); + var layout = element.getLayout(); + + element.store('prototype_absolutize_original_styles', { + left: element.getStyle('left'), + top: element.getStyle('top'), + width: element.getStyle('width'), + height: element.getStyle('height') + }); + + element.setStyle({ + position: 'absolute', + top: offset.top + 'px', + left: offset.left + 'px', + width: layout.get('width') + 'px', + height: layout.get('height') + 'px' + }); + + return element; + } + + function relativize(element) { + element = $(element); + if (Element.getStyle(element, 'position') === 'relative') { + return element; + } + + var originalStyles = + element.retrieve('prototype_absolutize_original_styles'); + + if (originalStyles) element.setStyle(originalStyles); + return element; + } + + Element.addMethods({ + getLayout: getLayout, + measure: measure, + getDimensions: getDimensions, + getOffsetParent: getOffsetParent, + cumulativeOffset: cumulativeOffset, + positionedOffset: positionedOffset, + cumulativeScrollOffset: cumulativeScrollOffset, + viewportOffset: viewportOffset, + absolutize: absolutize, + relativize: relativize + }); + + function isBody(element) { + return element.nodeName.toUpperCase() === 'BODY'; + } + + function isDetached(element) { + return element !== document.body && + !Element.descendantOf(element, document.body); + } + + if ('getBoundingClientRect' in document.documentElement) { + Element.addMethods({ + viewportOffset: function(element) { + element = $(element); + if (isDetached(element)) return new Element.Offset(0, 0); + + var rect = element.getBoundingClientRect(), + docEl = document.documentElement; + return new Element.Offset(rect.left - docEl.clientLeft, + rect.top - docEl.clientTop); + }, + + positionedOffset: function(element) { + element = $(element); + var parent = element.getOffsetParent(); + if (isDetached(element)) return new Element.Offset(0, 0); + + if (element.offsetParent && + element.offsetParent.nodeName.toUpperCase() === 'HTML') { + return positionedOffset(element); + } + + var eOffset = element.viewportOffset(), + pOffset = isBody(parent) ? viewportOffset(parent) : + parent.viewportOffset(); + var retOffset = eOffset.relativeTo(pOffset); + + var layout = element.getLayout(); + var top = retOffset.top - layout.get('margin-top'); + var left = retOffset.left - layout.get('margin-left'); + + return new Element.Offset(left, top); + } + }); + } +})(); +window.$$ = function() { + var expression = $A(arguments).join(', '); + return Prototype.Selector.select(expression, document); +}; + +Prototype.Selector = (function() { + + function select() { + throw new Error('Method "Prototype.Selector.select" must be defined.'); + } + + function match() { + throw new Error('Method "Prototype.Selector.match" must be defined.'); + } + + function find(elements, expression, index) { + index = index || 0; + var match = Prototype.Selector.match, length = elements.length, matchIndex = 0, i; + + for (i = 0; i < length; i++) { + if (match(elements[i], expression) && index == matchIndex++) { + return Element.extend(elements[i]); + } + } + } + + function extendElements(elements) { + for (var i = 0, length = elements.length; i < length; i++) { + Element.extend(elements[i]); + } + return elements; + } + + + var K = Prototype.K; + + return { + select: select, + match: match, + find: find, + extendElements: (Element.extend === K) ? K : extendElements, + extendElement: Element.extend + }; +})(); +Prototype._original_property = window.Sizzle; +/*! + * Sizzle CSS Selector Engine - v1.0 + * Copyright 2009, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * More information: http://sizzlejs.com/ + */ +(function(){ + +var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, + done = 0, + toString = Object.prototype.toString, + hasDuplicate = false, + baseHasDuplicate = true; + +[0, 0].sort(function(){ + baseHasDuplicate = false; + return 0; +}); + +var Sizzle = function(selector, context, results, seed) { + results = results || []; + var origContext = context = context || document; + + if ( context.nodeType !== 1 && context.nodeType !== 9 ) { + return []; + } + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + var parts = [], m, set, checkSet, check, mode, extra, prune = true, contextXML = isXML(context), + soFar = selector; + + while ( (chunker.exec(""), m = chunker.exec(soFar)) !== null ) { + soFar = m[3]; + + parts.push( m[1] ); + + if ( m[2] ) { + extra = m[3]; + break; + } + } + + if ( parts.length > 1 && origPOS.exec( selector ) ) { + if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { + set = posProcess( parts[0] + parts[1], context ); + } else { + set = Expr.relative[ parts[0] ] ? + [ context ] : + Sizzle( parts.shift(), context ); + + while ( parts.length ) { + selector = parts.shift(); + + if ( Expr.relative[ selector ] ) + selector += parts.shift(); + + set = posProcess( selector, set ); + } + } + } else { + if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && + Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { + var ret = Sizzle.find( parts.shift(), context, contextXML ); + context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0]; + } + + if ( context ) { + var ret = seed ? + { expr: parts.pop(), set: makeArray(seed) } : + Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); + set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set; + + if ( parts.length > 0 ) { + checkSet = makeArray(set); + } else { + prune = false; + } + + while ( parts.length ) { + var cur = parts.pop(), pop = cur; + + if ( !Expr.relative[ cur ] ) { + cur = ""; + } else { + pop = parts.pop(); + } + + if ( pop == null ) { + pop = context; + } + + Expr.relative[ cur ]( checkSet, pop, contextXML ); + } + } else { + checkSet = parts = []; + } + } + + if ( !checkSet ) { + checkSet = set; + } + + if ( !checkSet ) { + throw "Syntax error, unrecognized expression: " + (cur || selector); + } + + if ( toString.call(checkSet) === "[object Array]" ) { + if ( !prune ) { + results.push.apply( results, checkSet ); + } else if ( context && context.nodeType === 1 ) { + for ( var i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && contains(context, checkSet[i])) ) { + results.push( set[i] ); + } + } + } else { + for ( var i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && checkSet[i].nodeType === 1 ) { + results.push( set[i] ); + } + } + } + } else { + makeArray( checkSet, results ); + } + + if ( extra ) { + Sizzle( extra, origContext, results, seed ); + Sizzle.uniqueSort( results ); + } + + return results; +}; + +Sizzle.uniqueSort = function(results){ + if ( sortOrder ) { + hasDuplicate = baseHasDuplicate; + results.sort(sortOrder); + + if ( hasDuplicate ) { + for ( var i = 1; i < results.length; i++ ) { + if ( results[i] === results[i-1] ) { + results.splice(i--, 1); + } + } + } + } + + return results; +}; + +Sizzle.matches = function(expr, set){ + return Sizzle(expr, null, null, set); +}; + +Sizzle.find = function(expr, context, isXML){ + var set, match; + + if ( !expr ) { + return []; + } + + for ( var i = 0, l = Expr.order.length; i < l; i++ ) { + var type = Expr.order[i], match; + + if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { + var left = match[1]; + match.splice(1,1); + + if ( left.substr( left.length - 1 ) !== "\\" ) { + match[1] = (match[1] || "").replace(/\\/g, ""); + set = Expr.find[ type ]( match, context, isXML ); + if ( set != null ) { + expr = expr.replace( Expr.match[ type ], "" ); + break; + } + } + } + } + + if ( !set ) { + set = context.getElementsByTagName("*"); + } + + return {set: set, expr: expr}; +}; + +Sizzle.filter = function(expr, set, inplace, not){ + var old = expr, result = [], curLoop = set, match, anyFound, + isXMLFilter = set && set[0] && isXML(set[0]); + + while ( expr && set.length ) { + for ( var type in Expr.filter ) { + if ( (match = Expr.match[ type ].exec( expr )) != null ) { + var filter = Expr.filter[ type ], found, item; + anyFound = false; + + if ( curLoop == result ) { + result = []; + } + + if ( Expr.preFilter[ type ] ) { + match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); + + if ( !match ) { + anyFound = found = true; + } else if ( match === true ) { + continue; + } + } + + if ( match ) { + for ( var i = 0; (item = curLoop[i]) != null; i++ ) { + if ( item ) { + found = filter( item, match, i, curLoop ); + var pass = not ^ !!found; + + if ( inplace && found != null ) { + if ( pass ) { + anyFound = true; + } else { + curLoop[i] = false; + } + } else if ( pass ) { + result.push( item ); + anyFound = true; + } + } + } + } + + if ( found !== undefined ) { + if ( !inplace ) { + curLoop = result; + } + + expr = expr.replace( Expr.match[ type ], "" ); + + if ( !anyFound ) { + return []; + } + + break; + } + } + } + + if ( expr == old ) { + if ( anyFound == null ) { + throw "Syntax error, unrecognized expression: " + expr; + } else { + break; + } + } + + old = expr; + } + + return curLoop; +}; + +var Expr = Sizzle.selectors = { + order: [ "ID", "NAME", "TAG" ], + match: { + ID: /#((?:[\w\u00c0-\uFFFF-]|\\.)+)/, + CLASS: /\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/, + NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/, + ATTR: /\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/, + TAG: /^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/, + CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/, + POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/, + PSEUDO: /:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]*)((?:\([^\)]+\)|[^\2\(\)]*)+)\2\))?/ + }, + leftMatch: {}, + attrMap: { + "class": "className", + "for": "htmlFor" + }, + attrHandle: { + href: function(elem){ + return elem.getAttribute("href"); + } + }, + relative: { + "+": function(checkSet, part, isXML){ + var isPartStr = typeof part === "string", + isTag = isPartStr && !/\W/.test(part), + isPartStrNotTag = isPartStr && !isTag; + + if ( isTag && !isXML ) { + part = part.toUpperCase(); + } + + for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { + if ( (elem = checkSet[i]) ) { + while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} + + checkSet[i] = isPartStrNotTag || elem && elem.nodeName === part ? + elem || false : + elem === part; + } + } + + if ( isPartStrNotTag ) { + Sizzle.filter( part, checkSet, true ); + } + }, + ">": function(checkSet, part, isXML){ + var isPartStr = typeof part === "string"; + + if ( isPartStr && !/\W/.test(part) ) { + part = isXML ? part : part.toUpperCase(); + + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + var parent = elem.parentNode; + checkSet[i] = parent.nodeName === part ? parent : false; + } + } + } else { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + checkSet[i] = isPartStr ? + elem.parentNode : + elem.parentNode === part; + } + } + + if ( isPartStr ) { + Sizzle.filter( part, checkSet, true ); + } + } + }, + "": function(checkSet, part, isXML){ + var doneName = done++, checkFn = dirCheck; + + if ( !/\W/.test(part) ) { + var nodeCheck = part = isXML ? part : part.toUpperCase(); + checkFn = dirNodeCheck; + } + + checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML); + }, + "~": function(checkSet, part, isXML){ + var doneName = done++, checkFn = dirCheck; + + if ( typeof part === "string" && !/\W/.test(part) ) { + var nodeCheck = part = isXML ? part : part.toUpperCase(); + checkFn = dirNodeCheck; + } + + checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML); + } + }, + find: { + ID: function(match, context, isXML){ + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + return m ? [m] : []; + } + }, + NAME: function(match, context, isXML){ + if ( typeof context.getElementsByName !== "undefined" ) { + var ret = [], results = context.getElementsByName(match[1]); + + for ( var i = 0, l = results.length; i < l; i++ ) { + if ( results[i].getAttribute("name") === match[1] ) { + ret.push( results[i] ); + } + } + + return ret.length === 0 ? null : ret; + } + }, + TAG: function(match, context){ + return context.getElementsByTagName(match[1]); + } + }, + preFilter: { + CLASS: function(match, curLoop, inplace, result, not, isXML){ + match = " " + match[1].replace(/\\/g, "") + " "; + + if ( isXML ) { + return match; + } + + for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { + if ( elem ) { + if ( not ^ (elem.className && (" " + elem.className + " ").indexOf(match) >= 0) ) { + if ( !inplace ) + result.push( elem ); + } else if ( inplace ) { + curLoop[i] = false; + } + } + } + + return false; + }, + ID: function(match){ + return match[1].replace(/\\/g, ""); + }, + TAG: function(match, curLoop){ + for ( var i = 0; curLoop[i] === false; i++ ){} + return curLoop[i] && isXML(curLoop[i]) ? match[1] : match[1].toUpperCase(); + }, + CHILD: function(match){ + if ( match[1] == "nth" ) { + var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec( + match[2] == "even" && "2n" || match[2] == "odd" && "2n+1" || + !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); + + match[2] = (test[1] + (test[2] || 1)) - 0; + match[3] = test[3] - 0; + } + + match[0] = done++; + + return match; + }, + ATTR: function(match, curLoop, inplace, result, not, isXML){ + var name = match[1].replace(/\\/g, ""); + + if ( !isXML && Expr.attrMap[name] ) { + match[1] = Expr.attrMap[name]; + } + + if ( match[2] === "~=" ) { + match[4] = " " + match[4] + " "; + } + + return match; + }, + PSEUDO: function(match, curLoop, inplace, result, not){ + if ( match[1] === "not" ) { + if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { + match[3] = Sizzle(match[3], null, null, curLoop); + } else { + var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); + if ( !inplace ) { + result.push.apply( result, ret ); + } + return false; + } + } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { + return true; + } + + return match; + }, + POS: function(match){ + match.unshift( true ); + return match; + } + }, + filters: { + enabled: function(elem){ + return elem.disabled === false && elem.type !== "hidden"; + }, + disabled: function(elem){ + return elem.disabled === true; + }, + checked: function(elem){ + return elem.checked === true; + }, + selected: function(elem){ + elem.parentNode.selectedIndex; + return elem.selected === true; + }, + parent: function(elem){ + return !!elem.firstChild; + }, + empty: function(elem){ + return !elem.firstChild; + }, + has: function(elem, i, match){ + return !!Sizzle( match[3], elem ).length; + }, + header: function(elem){ + return /h\d/i.test( elem.nodeName ); + }, + text: function(elem){ + return "text" === elem.type; + }, + radio: function(elem){ + return "radio" === elem.type; + }, + checkbox: function(elem){ + return "checkbox" === elem.type; + }, + file: function(elem){ + return "file" === elem.type; + }, + password: function(elem){ + return "password" === elem.type; + }, + submit: function(elem){ + return "submit" === elem.type; + }, + image: function(elem){ + return "image" === elem.type; + }, + reset: function(elem){ + return "reset" === elem.type; + }, + button: function(elem){ + return "button" === elem.type || elem.nodeName.toUpperCase() === "BUTTON"; + }, + input: function(elem){ + return /input|select|textarea|button/i.test(elem.nodeName); + } + }, + setFilters: { + first: function(elem, i){ + return i === 0; + }, + last: function(elem, i, match, array){ + return i === array.length - 1; + }, + even: function(elem, i){ + return i % 2 === 0; + }, + odd: function(elem, i){ + return i % 2 === 1; + }, + lt: function(elem, i, match){ + return i < match[3] - 0; + }, + gt: function(elem, i, match){ + return i > match[3] - 0; + }, + nth: function(elem, i, match){ + return match[3] - 0 == i; + }, + eq: function(elem, i, match){ + return match[3] - 0 == i; + } + }, + filter: { + PSEUDO: function(elem, match, i, array){ + var name = match[1], filter = Expr.filters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } else if ( name === "contains" ) { + return (elem.textContent || elem.innerText || "").indexOf(match[3]) >= 0; + } else if ( name === "not" ) { + var not = match[3]; + + for ( var i = 0, l = not.length; i < l; i++ ) { + if ( not[i] === elem ) { + return false; + } + } + + return true; + } + }, + CHILD: function(elem, match){ + var type = match[1], node = elem; + switch (type) { + case 'only': + case 'first': + while ( (node = node.previousSibling) ) { + if ( node.nodeType === 1 ) return false; + } + if ( type == 'first') return true; + node = elem; + case 'last': + while ( (node = node.nextSibling) ) { + if ( node.nodeType === 1 ) return false; + } + return true; + case 'nth': + var first = match[2], last = match[3]; + + if ( first == 1 && last == 0 ) { + return true; + } + + var doneName = match[0], + parent = elem.parentNode; + + if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) { + var count = 0; + for ( node = parent.firstChild; node; node = node.nextSibling ) { + if ( node.nodeType === 1 ) { + node.nodeIndex = ++count; + } + } + parent.sizcache = doneName; + } + + var diff = elem.nodeIndex - last; + if ( first == 0 ) { + return diff == 0; + } else { + return ( diff % first == 0 && diff / first >= 0 ); + } + } + }, + ID: function(elem, match){ + return elem.nodeType === 1 && elem.getAttribute("id") === match; + }, + TAG: function(elem, match){ + return (match === "*" && elem.nodeType === 1) || elem.nodeName === match; + }, + CLASS: function(elem, match){ + return (" " + (elem.className || elem.getAttribute("class")) + " ") + .indexOf( match ) > -1; + }, + ATTR: function(elem, match){ + var name = match[1], + result = Expr.attrHandle[ name ] ? + Expr.attrHandle[ name ]( elem ) : + elem[ name ] != null ? + elem[ name ] : + elem.getAttribute( name ), + value = result + "", + type = match[2], + check = match[4]; + + return result == null ? + type === "!=" : + type === "=" ? + value === check : + type === "*=" ? + value.indexOf(check) >= 0 : + type === "~=" ? + (" " + value + " ").indexOf(check) >= 0 : + !check ? + value && result !== false : + type === "!=" ? + value != check : + type === "^=" ? + value.indexOf(check) === 0 : + type === "$=" ? + value.substr(value.length - check.length) === check : + type === "|=" ? + value === check || value.substr(0, check.length + 1) === check + "-" : + false; + }, + POS: function(elem, match, i, array){ + var name = match[2], filter = Expr.setFilters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } + } + } +}; + +var origPOS = Expr.match.POS; + +for ( var type in Expr.match ) { + Expr.match[ type ] = new RegExp( Expr.match[ type ].source + /(?![^\[]*\])(?![^\(]*\))/.source ); + Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source ); +} + +var makeArray = function(array, results) { + array = Array.prototype.slice.call( array, 0 ); + + if ( results ) { + results.push.apply( results, array ); + return results; + } + + return array; +}; + +try { + Array.prototype.slice.call( document.documentElement.childNodes, 0 ); + +} catch(e){ + makeArray = function(array, results) { + var ret = results || []; + + if ( toString.call(array) === "[object Array]" ) { + Array.prototype.push.apply( ret, array ); + } else { + if ( typeof array.length === "number" ) { + for ( var i = 0, l = array.length; i < l; i++ ) { + ret.push( array[i] ); + } + } else { + for ( var i = 0; array[i]; i++ ) { + ret.push( array[i] ); + } + } + } + + return ret; + }; +} + +var sortOrder; + +if ( document.documentElement.compareDocumentPosition ) { + sortOrder = function( a, b ) { + if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { + if ( a == b ) { + hasDuplicate = true; + } + return 0; + } + + var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1; + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} else if ( "sourceIndex" in document.documentElement ) { + sortOrder = function( a, b ) { + if ( !a.sourceIndex || !b.sourceIndex ) { + if ( a == b ) { + hasDuplicate = true; + } + return 0; + } + + var ret = a.sourceIndex - b.sourceIndex; + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} else if ( document.createRange ) { + sortOrder = function( a, b ) { + if ( !a.ownerDocument || !b.ownerDocument ) { + if ( a == b ) { + hasDuplicate = true; + } + return 0; + } + + var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange(); + aRange.setStart(a, 0); + aRange.setEnd(a, 0); + bRange.setStart(b, 0); + bRange.setEnd(b, 0); + var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange); + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} + +(function(){ + var form = document.createElement("div"), + id = "script" + (new Date).getTime(); + form.innerHTML = ""; + + var root = document.documentElement; + root.insertBefore( form, root.firstChild ); + + if ( !!document.getElementById( id ) ) { + Expr.find.ID = function(match, context, isXML){ + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : []; + } + }; + + Expr.filter.ID = function(elem, match){ + var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); + return elem.nodeType === 1 && node && node.nodeValue === match; + }; + } + + root.removeChild( form ); + root = form = null; // release memory in IE +})(); + +(function(){ + + var div = document.createElement("div"); + div.appendChild( document.createComment("") ); + + if ( div.getElementsByTagName("*").length > 0 ) { + Expr.find.TAG = function(match, context){ + var results = context.getElementsByTagName(match[1]); + + if ( match[1] === "*" ) { + var tmp = []; + + for ( var i = 0; results[i]; i++ ) { + if ( results[i].nodeType === 1 ) { + tmp.push( results[i] ); + } + } + + results = tmp; + } + + return results; + }; + } + + div.innerHTML = ""; + if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && + div.firstChild.getAttribute("href") !== "#" ) { + Expr.attrHandle.href = function(elem){ + return elem.getAttribute("href", 2); + }; + } + + div = null; // release memory in IE +})(); + +if ( document.querySelectorAll ) (function(){ + var oldSizzle = Sizzle, div = document.createElement("div"); + div.innerHTML = "

    "; + + if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { + return; + } + + Sizzle = function(query, context, extra, seed){ + context = context || document; + + if ( !seed && context.nodeType === 9 && !isXML(context) ) { + try { + return makeArray( context.querySelectorAll(query), extra ); + } catch(e){} + } + + return oldSizzle(query, context, extra, seed); + }; + + for ( var prop in oldSizzle ) { + Sizzle[ prop ] = oldSizzle[ prop ]; + } + + div = null; // release memory in IE +})(); + +if ( document.getElementsByClassName && document.documentElement.getElementsByClassName ) (function(){ + var div = document.createElement("div"); + div.innerHTML = "
    "; + + if ( div.getElementsByClassName("e").length === 0 ) + return; + + div.lastChild.className = "e"; + + if ( div.getElementsByClassName("e").length === 1 ) + return; + + Expr.order.splice(1, 0, "CLASS"); + Expr.find.CLASS = function(match, context, isXML) { + if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { + return context.getElementsByClassName(match[1]); + } + }; + + div = null; // release memory in IE +})(); + +function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + var sibDir = dir == "previousSibling" && !isXML; + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + if ( sibDir && elem.nodeType === 1 ){ + elem.sizcache = doneName; + elem.sizset = i; + } + elem = elem[dir]; + var match = false; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 && !isXML ){ + elem.sizcache = doneName; + elem.sizset = i; + } + + if ( elem.nodeName === cur ) { + match = elem; + break; + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + var sibDir = dir == "previousSibling" && !isXML; + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + if ( sibDir && elem.nodeType === 1 ) { + elem.sizcache = doneName; + elem.sizset = i; + } + elem = elem[dir]; + var match = false; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 ) { + if ( !isXML ) { + elem.sizcache = doneName; + elem.sizset = i; + } + if ( typeof cur !== "string" ) { + if ( elem === cur ) { + match = true; + break; + } + + } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { + match = elem; + break; + } + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +var contains = document.compareDocumentPosition ? function(a, b){ + return a.compareDocumentPosition(b) & 16; +} : function(a, b){ + return a !== b && (a.contains ? a.contains(b) : true); +}; + +var isXML = function(elem){ + return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" || + !!elem.ownerDocument && elem.ownerDocument.documentElement.nodeName !== "HTML"; +}; + +var posProcess = function(selector, context){ + var tmpSet = [], later = "", match, + root = context.nodeType ? [context] : context; + + while ( (match = Expr.match.PSEUDO.exec( selector )) ) { + later += match[0]; + selector = selector.replace( Expr.match.PSEUDO, "" ); + } + + selector = Expr.relative[selector] ? selector + "*" : selector; + + for ( var i = 0, l = root.length; i < l; i++ ) { + Sizzle( selector, root[i], tmpSet ); + } + + return Sizzle.filter( later, tmpSet ); +}; + + +window.Sizzle = Sizzle; + +})(); + +;(function(engine) { + var extendElements = Prototype.Selector.extendElements; + + function select(selector, scope) { + return extendElements(engine(selector, scope || document)); + } + + function match(element, selector) { + return engine.matches(selector, [element]).length == 1; + } + + Prototype.Selector.engine = engine; + Prototype.Selector.select = select; + Prototype.Selector.match = match; +})(Sizzle); + +window.Sizzle = Prototype._original_property; +delete Prototype._original_property; + +var Form = { + reset: function(form) { + form = $(form); + form.reset(); + return form; + }, + + serializeElements: function(elements, options) { + if (typeof options != 'object') options = { hash: !!options }; + else if (Object.isUndefined(options.hash)) options.hash = true; + var key, value, submitted = false, submit = options.submit; + + var data = elements.inject({ }, function(result, element) { + if (!element.disabled && element.name) { + key = element.name; value = $(element).getValue(); + if (value != null && element.type != 'file' && (element.type != 'submit' || (!submitted && + submit !== false && (!submit || key == submit) && (submitted = true)))) { + if (key in result) { + if (!Object.isArray(result[key])) result[key] = [result[key]]; + result[key].push(value); + } + else result[key] = value; + } + } + return result; + }); + + return options.hash ? data : Object.toQueryString(data); + } +}; + +Form.Methods = { + serialize: function(form, options) { + return Form.serializeElements(Form.getElements(form), options); + }, + + getElements: function(form) { + var elements = $(form).getElementsByTagName('*'), + element, + arr = [ ], + serializers = Form.Element.Serializers; + for (var i = 0; element = elements[i]; i++) { + arr.push(element); + } + return arr.inject([], function(elements, child) { + if (serializers[child.tagName.toLowerCase()]) + elements.push(Element.extend(child)); + return elements; + }) + }, + + getInputs: function(form, typeName, name) { + form = $(form); + var inputs = form.getElementsByTagName('input'); + + if (!typeName && !name) return $A(inputs).map(Element.extend); + + for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) { + var input = inputs[i]; + if ((typeName && input.type != typeName) || (name && input.name != name)) + continue; + matchingInputs.push(Element.extend(input)); + } + + return matchingInputs; + }, + + disable: function(form) { + form = $(form); + Form.getElements(form).invoke('disable'); + return form; + }, + + enable: function(form) { + form = $(form); + Form.getElements(form).invoke('enable'); + return form; + }, + + findFirstElement: function(form) { + var elements = $(form).getElements().findAll(function(element) { + return 'hidden' != element.type && !element.disabled; + }); + var firstByIndex = elements.findAll(function(element) { + return element.hasAttribute('tabIndex') && element.tabIndex >= 0; + }).sortBy(function(element) { return element.tabIndex }).first(); + + return firstByIndex ? firstByIndex : elements.find(function(element) { + return /^(?:input|select|textarea)$/i.test(element.tagName); + }); + }, + + focusFirstElement: function(form) { + form = $(form); + form.findFirstElement().activate(); + return form; + }, + + request: function(form, options) { + form = $(form), options = Object.clone(options || { }); + + var params = options.parameters, action = form.readAttribute('action') || ''; + if (action.blank()) action = window.location.href; + options.parameters = form.serialize(true); + + if (params) { + if (Object.isString(params)) params = params.toQueryParams(); + Object.extend(options.parameters, params); + } + + if (form.hasAttribute('method') && !options.method) + options.method = form.method; + + return new Ajax.Request(action, options); + } +}; + +/*--------------------------------------------------------------------------*/ + + +Form.Element = { + focus: function(element) { + $(element).focus(); + return element; + }, + + select: function(element) { + $(element).select(); + return element; + } +}; + +Form.Element.Methods = { + + serialize: function(element) { + element = $(element); + if (!element.disabled && element.name) { + var value = element.getValue(); + if (value != undefined) { + var pair = { }; + pair[element.name] = value; + return Object.toQueryString(pair); + } + } + return ''; + }, + + getValue: function(element) { + element = $(element); + var method = element.tagName.toLowerCase(); + return Form.Element.Serializers[method](element); + }, + + setValue: function(element, value) { + element = $(element); + var method = element.tagName.toLowerCase(); + Form.Element.Serializers[method](element, value); + return element; + }, + + clear: function(element) { + $(element).value = ''; + return element; + }, + + present: function(element) { + return $(element).value != ''; + }, + + activate: function(element) { + element = $(element); + try { + element.focus(); + if (element.select && (element.tagName.toLowerCase() != 'input' || + !(/^(?:button|reset|submit)$/i.test(element.type)))) + element.select(); + } catch (e) { } + return element; + }, + + disable: function(element) { + element = $(element); + element.disabled = true; + return element; + }, + + enable: function(element) { + element = $(element); + element.disabled = false; + return element; + } +}; + +/*--------------------------------------------------------------------------*/ + +var Field = Form.Element; + +var $F = Form.Element.Methods.getValue; + +/*--------------------------------------------------------------------------*/ + +Form.Element.Serializers = { + input: function(element, value) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + return Form.Element.Serializers.inputSelector(element, value); + default: + return Form.Element.Serializers.textarea(element, value); + } + }, + + inputSelector: function(element, value) { + if (Object.isUndefined(value)) return element.checked ? element.value : null; + else element.checked = !!value; + }, + + textarea: function(element, value) { + if (Object.isUndefined(value)) return element.value; + else element.value = value; + }, + + select: function(element, value) { + if (Object.isUndefined(value)) + return this[element.type == 'select-one' ? + 'selectOne' : 'selectMany'](element); + else { + var opt, currentValue, single = !Object.isArray(value); + for (var i = 0, length = element.length; i < length; i++) { + opt = element.options[i]; + currentValue = this.optionValue(opt); + if (single) { + if (currentValue == value) { + opt.selected = true; + return; + } + } + else opt.selected = value.include(currentValue); + } + } + }, + + selectOne: function(element) { + var index = element.selectedIndex; + return index >= 0 ? this.optionValue(element.options[index]) : null; + }, + + selectMany: function(element) { + var values, length = element.length; + if (!length) return null; + + for (var i = 0, values = []; i < length; i++) { + var opt = element.options[i]; + if (opt.selected) values.push(this.optionValue(opt)); + } + return values; + }, + + optionValue: function(opt) { + return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text; + } +}; + +/*--------------------------------------------------------------------------*/ + + +Abstract.TimedObserver = Class.create(PeriodicalExecuter, { + initialize: function($super, element, frequency, callback) { + $super(callback, frequency); + this.element = $(element); + this.lastValue = this.getValue(); + }, + + execute: function() { + var value = this.getValue(); + if (Object.isString(this.lastValue) && Object.isString(value) ? + this.lastValue != value : String(this.lastValue) != String(value)) { + this.callback(this.element, value); + this.lastValue = value; + } + } +}); + +Form.Element.Observer = Class.create(Abstract.TimedObserver, { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.Observer = Class.create(Abstract.TimedObserver, { + getValue: function() { + return Form.serialize(this.element); + } +}); + +/*--------------------------------------------------------------------------*/ + +Abstract.EventObserver = Class.create({ + initialize: function(element, callback) { + this.element = $(element); + this.callback = callback; + + this.lastValue = this.getValue(); + if (this.element.tagName.toLowerCase() == 'form') + this.registerFormCallbacks(); + else + this.registerCallback(this.element); + }, + + onElementEvent: function() { + var value = this.getValue(); + if (this.lastValue != value) { + this.callback(this.element, value); + this.lastValue = value; + } + }, + + registerFormCallbacks: function() { + Form.getElements(this.element).each(this.registerCallback, this); + }, + + registerCallback: function(element) { + if (element.type) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + Event.observe(element, 'click', this.onElementEvent.bind(this)); + break; + default: + Event.observe(element, 'change', this.onElementEvent.bind(this)); + break; + } + } + } +}); + +Form.Element.EventObserver = Class.create(Abstract.EventObserver, { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.EventObserver = Class.create(Abstract.EventObserver, { + getValue: function() { + return Form.serialize(this.element); + } +}); +(function() { + + var Event = { + KEY_BACKSPACE: 8, + KEY_TAB: 9, + KEY_RETURN: 13, + KEY_ESC: 27, + KEY_LEFT: 37, + KEY_UP: 38, + KEY_RIGHT: 39, + KEY_DOWN: 40, + KEY_DELETE: 46, + KEY_HOME: 36, + KEY_END: 35, + KEY_PAGEUP: 33, + KEY_PAGEDOWN: 34, + KEY_INSERT: 45, + + cache: {} + }; + + var docEl = document.documentElement; + var MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED = 'onmouseenter' in docEl + && 'onmouseleave' in docEl; + + var _isButton; + if (Prototype.Browser.IE) { + var buttonMap = { 0: 1, 1: 4, 2: 2 }; + _isButton = function(event, code) { + return event.button === buttonMap[code]; + }; + } else if (Prototype.Browser.WebKit) { + _isButton = function(event, code) { + switch (code) { + case 0: return event.which == 1 && !event.metaKey; + case 1: return event.which == 1 && event.metaKey; + default: return false; + } + }; + } else { + _isButton = function(event, code) { + return event.which ? (event.which === code + 1) : (event.button === code); + }; + } + + function isLeftClick(event) { return _isButton(event, 0) } + + function isMiddleClick(event) { return _isButton(event, 1) } + + function isRightClick(event) { return _isButton(event, 2) } + + function element(event) { + event = Event.extend(event); + + var node = event.target, type = event.type, + currentTarget = event.currentTarget; + + if (currentTarget && currentTarget.tagName) { + if (type === 'load' || type === 'error' || + (type === 'click' && currentTarget.tagName.toLowerCase() === 'input' + && currentTarget.type === 'radio')) + node = currentTarget; + } + + if (node.nodeType == Node.TEXT_NODE) + node = node.parentNode; + + return Element.extend(node); + } + + function findElement(event, expression) { + var element = Event.element(event); + if (!expression) return element; + while (element) { + if (Object.isElement(element) && Prototype.Selector.match(element, expression)) { + return Element.extend(element); + } + element = element.parentNode; + } + } + + function pointer(event) { + return { x: pointerX(event), y: pointerY(event) }; + } + + function pointerX(event) { + var docElement = document.documentElement, + body = document.body || { scrollLeft: 0 }; + + return event.pageX || (event.clientX + + (docElement.scrollLeft || body.scrollLeft) - + (docElement.clientLeft || 0)); + } + + function pointerY(event) { + var docElement = document.documentElement, + body = document.body || { scrollTop: 0 }; + + return event.pageY || (event.clientY + + (docElement.scrollTop || body.scrollTop) - + (docElement.clientTop || 0)); + } + + + function stop(event) { + Event.extend(event); + event.preventDefault(); + event.stopPropagation(); + + event.stopped = true; + } + + Event.Methods = { + isLeftClick: isLeftClick, + isMiddleClick: isMiddleClick, + isRightClick: isRightClick, + + element: element, + findElement: findElement, + + pointer: pointer, + pointerX: pointerX, + pointerY: pointerY, + + stop: stop + }; + + + var methods = Object.keys(Event.Methods).inject({ }, function(m, name) { + m[name] = Event.Methods[name].methodize(); + return m; + }); + + if (Prototype.Browser.IE) { + function _relatedTarget(event) { + var element; + switch (event.type) { + case 'mouseover': element = event.fromElement; break; + case 'mouseout': element = event.toElement; break; + default: return null; + } + return Element.extend(element); + } + + Object.extend(methods, { + stopPropagation: function() { this.cancelBubble = true }, + preventDefault: function() { this.returnValue = false }, + inspect: function() { return '[object Event]' } + }); + + Event.extend = function(event, element) { + if (!event) return false; + if (event._extendedByPrototype) return event; + + event._extendedByPrototype = Prototype.emptyFunction; + var pointer = Event.pointer(event); + + Object.extend(event, { + target: event.srcElement || element, + relatedTarget: _relatedTarget(event), + pageX: pointer.x, + pageY: pointer.y + }); + + return Object.extend(event, methods); + }; + } else { + Event.prototype = window.Event.prototype || document.createEvent('HTMLEvents').__proto__; + Object.extend(Event.prototype, methods); + Event.extend = Prototype.K; + } + + function _createResponder(element, eventName, handler) { + var registry = Element.retrieve(element, 'prototype_event_registry'); + + if (Object.isUndefined(registry)) { + CACHE.push(element); + registry = Element.retrieve(element, 'prototype_event_registry', $H()); + } + + var respondersForEvent = registry.get(eventName); + if (Object.isUndefined(respondersForEvent)) { + respondersForEvent = []; + registry.set(eventName, respondersForEvent); + } + + if (respondersForEvent.pluck('handler').include(handler)) return false; + + var responder; + if (eventName.include(":")) { + responder = function(event) { + if (Object.isUndefined(event.eventName)) + return false; + + if (event.eventName !== eventName) + return false; + + Event.extend(event, element); + handler.call(element, event); + }; + } else { + if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED && + (eventName === "mouseenter" || eventName === "mouseleave")) { + if (eventName === "mouseenter" || eventName === "mouseleave") { + responder = function(event) { + Event.extend(event, element); + + var parent = event.relatedTarget; + while (parent && parent !== element) { + try { parent = parent.parentNode; } + catch(e) { parent = element; } + } + + if (parent === element) return; + + handler.call(element, event); + }; + } + } else { + responder = function(event) { + Event.extend(event, element); + handler.call(element, event); + }; + } + } + + responder.handler = handler; + respondersForEvent.push(responder); + return responder; + } + + function _destroyCache() { + for (var i = 0, length = CACHE.length; i < length; i++) { + Event.stopObserving(CACHE[i]); + CACHE[i] = null; + } + } + + var CACHE = []; + + if (Prototype.Browser.IE) + window.attachEvent('onunload', _destroyCache); + + if (Prototype.Browser.WebKit) + window.addEventListener('unload', Prototype.emptyFunction, false); + + + var _getDOMEventName = Prototype.K, + translations = { mouseenter: "mouseover", mouseleave: "mouseout" }; + + if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED) { + _getDOMEventName = function(eventName) { + return (translations[eventName] || eventName); + }; + } + + function observe(element, eventName, handler) { + element = $(element); + + var responder = _createResponder(element, eventName, handler); + + if (!responder) return element; + + if (eventName.include(':')) { + if (element.addEventListener) + element.addEventListener("dataavailable", responder, false); + else { + element.attachEvent("ondataavailable", responder); + element.attachEvent("onfilterchange", responder); + } + } else { + var actualEventName = _getDOMEventName(eventName); + + if (element.addEventListener) + element.addEventListener(actualEventName, responder, false); + else + element.attachEvent("on" + actualEventName, responder); + } + + return element; + } + + function stopObserving(element, eventName, handler) { + element = $(element); + + var registry = Element.retrieve(element, 'prototype_event_registry'); + if (!registry) return element; + + if (!eventName) { + registry.each( function(pair) { + var eventName = pair.key; + stopObserving(element, eventName); + }); + return element; + } + + var responders = registry.get(eventName); + if (!responders) return element; + + if (!handler) { + responders.each(function(r) { + stopObserving(element, eventName, r.handler); + }); + return element; + } + + var responder = responders.find( function(r) { return r.handler === handler; }); + if (!responder) return element; + + if (eventName.include(':')) { + if (element.removeEventListener) + element.removeEventListener("dataavailable", responder, false); + else { + element.detachEvent("ondataavailable", responder); + element.detachEvent("onfilterchange", responder); + } + } else { + var actualEventName = _getDOMEventName(eventName); + if (element.removeEventListener) + element.removeEventListener(actualEventName, responder, false); + else + element.detachEvent('on' + actualEventName, responder); + } + + registry.set(eventName, responders.without(responder)); + + return element; + } + + function fire(element, eventName, memo, bubble) { + element = $(element); + + if (Object.isUndefined(bubble)) + bubble = true; + + if (element == document && document.createEvent && !element.dispatchEvent) + element = document.documentElement; + + var event; + if (document.createEvent) { + event = document.createEvent('HTMLEvents'); + event.initEvent('dataavailable', true, true); + } else { + event = document.createEventObject(); + event.eventType = bubble ? 'ondataavailable' : 'onfilterchange'; + } + + event.eventName = eventName; + event.memo = memo || { }; + + if (document.createEvent) + element.dispatchEvent(event); + else + element.fireEvent(event.eventType, event); + + return Event.extend(event); + } + + Event.Handler = Class.create({ + initialize: function(element, eventName, selector, callback) { + this.element = $(element); + this.eventName = eventName; + this.selector = selector; + this.callback = callback; + this.handler = this.handleEvent.bind(this); + }, + + start: function() { + Event.observe(this.element, this.eventName, this.handler); + return this; + }, + + stop: function() { + Event.stopObserving(this.element, this.eventName, this.handler); + return this; + }, + + handleEvent: function(event) { + var element = event.findElement(this.selector); + if (element) this.callback.call(this.element, event, element); + } + }); + + function on(element, eventName, selector, callback) { + element = $(element); + if (Object.isFunction(selector) && Object.isUndefined(callback)) { + callback = selector, selector = null; + } + + return new Event.Handler(element, eventName, selector, callback).start(); + } + + Object.extend(Event, Event.Methods); + + Object.extend(Event, { + fire: fire, + observe: observe, + stopObserving: stopObserving, + on: on + }); + + Element.addMethods({ + fire: fire, + + observe: observe, + + stopObserving: stopObserving, + + on: on + }); + + Object.extend(document, { + fire: fire.methodize(), + + observe: observe.methodize(), + + stopObserving: stopObserving.methodize(), + + on: on.methodize(), + + loaded: false + }); + + if (window.Event) Object.extend(window.Event, Event); + else window.Event = Event; +})(); + +(function() { + /* Support for the DOMContentLoaded event is based on work by Dan Webb, + Matthias Miller, Dean Edwards, John Resig, and Diego Perini. */ + + var timer; + + function fireContentLoadedEvent() { + if (document.loaded) return; + if (timer) window.clearTimeout(timer); + document.loaded = true; + document.fire('dom:loaded'); + } + + function checkReadyState() { + if (document.readyState === 'complete') { + document.stopObserving('readystatechange', checkReadyState); + fireContentLoadedEvent(); + } + } + + function pollDoScroll() { + try { document.documentElement.doScroll('left'); } + catch(e) { + timer = pollDoScroll.defer(); + return; + } + fireContentLoadedEvent(); + } + + if (document.addEventListener) { + document.addEventListener('DOMContentLoaded', fireContentLoadedEvent, false); + } else { + document.observe('readystatechange', checkReadyState); + if (window == top) + timer = pollDoScroll.defer(); + } + + Event.observe(window, 'load', fireContentLoadedEvent); +})(); + +Element.addMethods(); + +/*------------------------------- DEPRECATED -------------------------------*/ + +Hash.toQueryString = Object.toQueryString; + +var Toggle = { display: Element.toggle }; + +Element.Methods.childOf = Element.Methods.descendantOf; + +var Insertion = { + Before: function(element, content) { + return Element.insert(element, {before:content}); + }, + + Top: function(element, content) { + return Element.insert(element, {top:content}); + }, + + Bottom: function(element, content) { + return Element.insert(element, {bottom:content}); + }, + + After: function(element, content) { + return Element.insert(element, {after:content}); + } +}; + +var $continue = new Error('"throw $continue" is deprecated, use "return" instead'); + +var Position = { + includeScrollOffsets: false, + + prepare: function() { + this.deltaX = window.pageXOffset + || document.documentElement.scrollLeft + || document.body.scrollLeft + || 0; + this.deltaY = window.pageYOffset + || document.documentElement.scrollTop + || document.body.scrollTop + || 0; + }, + + within: function(element, x, y) { + if (this.includeScrollOffsets) + return this.withinIncludingScrolloffsets(element, x, y); + this.xcomp = x; + this.ycomp = y; + this.offset = Element.cumulativeOffset(element); + + return (y >= this.offset[1] && + y < this.offset[1] + element.offsetHeight && + x >= this.offset[0] && + x < this.offset[0] + element.offsetWidth); + }, + + withinIncludingScrolloffsets: function(element, x, y) { + var offsetcache = Element.cumulativeScrollOffset(element); + + this.xcomp = x + offsetcache[0] - this.deltaX; + this.ycomp = y + offsetcache[1] - this.deltaY; + this.offset = Element.cumulativeOffset(element); + + return (this.ycomp >= this.offset[1] && + this.ycomp < this.offset[1] + element.offsetHeight && + this.xcomp >= this.offset[0] && + this.xcomp < this.offset[0] + element.offsetWidth); + }, + + overlap: function(mode, element) { + if (!mode) return 0; + if (mode == 'vertical') + return ((this.offset[1] + element.offsetHeight) - this.ycomp) / + element.offsetHeight; + if (mode == 'horizontal') + return ((this.offset[0] + element.offsetWidth) - this.xcomp) / + element.offsetWidth; + }, + + + cumulativeOffset: Element.Methods.cumulativeOffset, + + positionedOffset: Element.Methods.positionedOffset, + + absolutize: function(element) { + Position.prepare(); + return Element.absolutize(element); + }, + + relativize: function(element) { + Position.prepare(); + return Element.relativize(element); + }, + + realOffset: Element.Methods.cumulativeScrollOffset, + + offsetParent: Element.Methods.getOffsetParent, + + page: Element.Methods.viewportOffset, + + clone: function(source, target, options) { + options = options || { }; + return Element.clonePosition(target, source, options); + } +}; + +/*--------------------------------------------------------------------------*/ + +if (!document.getElementsByClassName) document.getElementsByClassName = function(instanceMethods){ + function iter(name) { + return name.blank() ? null : "[contains(concat(' ', @class, ' '), ' " + name + " ')]"; + } + + instanceMethods.getElementsByClassName = Prototype.BrowserFeatures.XPath ? + function(element, className) { + className = className.toString().strip(); + var cond = /\s/.test(className) ? $w(className).map(iter).join('') : iter(className); + return cond ? document._getElementsByXPath('.//*' + cond, element) : []; + } : function(element, className) { + className = className.toString().strip(); + var elements = [], classNames = (/\s/.test(className) ? $w(className) : null); + if (!classNames && !className) return elements; + + var nodes = $(element).getElementsByTagName('*'); + className = ' ' + className + ' '; + + for (var i = 0, child, cn; child = nodes[i]; i++) { + if (child.className && (cn = ' ' + child.className + ' ') && (cn.include(className) || + (classNames && classNames.all(function(name) { + return !name.toString().blank() && cn.include(' ' + name + ' '); + })))) + elements.push(Element.extend(child)); + } + return elements; + }; + + return function(className, parentElement) { + return $(parentElement || document.body).getElementsByClassName(className); + }; +}(Element.Methods); + +/*--------------------------------------------------------------------------*/ + +Element.ClassNames = Class.create(); +Element.ClassNames.prototype = { + initialize: function(element) { + this.element = $(element); + }, + + _each: function(iterator) { + this.element.className.split(/\s+/).select(function(name) { + return name.length > 0; + })._each(iterator); + }, + + set: function(className) { + this.element.className = className; + }, + + add: function(classNameToAdd) { + if (this.include(classNameToAdd)) return; + this.set($A(this).concat(classNameToAdd).join(' ')); + }, + + remove: function(classNameToRemove) { + if (!this.include(classNameToRemove)) return; + this.set($A(this).without(classNameToRemove).join(' ')); + }, + + toString: function() { + return $A(this).join(' '); + } +}; + +Object.extend(Element.ClassNames.prototype, Enumerable); + +/*--------------------------------------------------------------------------*/ + +(function() { + window.Selector = Class.create({ + initialize: function(expression) { + this.expression = expression.strip(); + }, + + findElements: function(rootElement) { + return Prototype.Selector.select(this.expression, rootElement); + }, + + match: function(element) { + return Prototype.Selector.match(element, this.expression); + }, + + toString: function() { + return this.expression; + }, + + inspect: function() { + return "#"; + } + }); + + Object.extend(Selector, { + matchElements: function(elements, expression) { + var match = Prototype.Selector.match, + results = []; + + for (var i = 0, length = elements.length; i < length; i++) { + var element = elements[i]; + if (match(element, expression)) { + results.push(Element.extend(element)); + } + } + return results; + }, + + findElement: function(elements, expression, index) { + index = index || 0; + var matchIndex = 0, element; + for (var i = 0, length = elements.length; i < length; i++) { + element = elements[i]; + if (Prototype.Selector.match(element, expression) && index === matchIndex++) { + return Element.extend(element); + } + } + }, + + findChildElements: function(element, expressions) { + var selector = expressions.toArray().join(', '); + return Prototype.Selector.select(selector, element || document); + } + }); +})(); diff --git a/public/javascripts/rails.js b/public/javascripts/rails.js new file mode 100644 index 0000000..aed6aed --- /dev/null +++ b/public/javascripts/rails.js @@ -0,0 +1,191 @@ +(function() { + // Technique from Juriy Zaytsev + // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/ + function isEventSupported(eventName) { + var el = document.createElement('div'); + eventName = 'on' + eventName; + var isSupported = (eventName in el); + if (!isSupported) { + el.setAttribute(eventName, 'return;'); + isSupported = typeof el[eventName] == 'function'; + } + el = null; + return isSupported; + } + + function isForm(element) { + return Object.isElement(element) && element.nodeName.toUpperCase() == 'FORM' + } + + function isInput(element) { + if (Object.isElement(element)) { + var name = element.nodeName.toUpperCase() + return name == 'INPUT' || name == 'SELECT' || name == 'TEXTAREA' + } + else return false + } + + var submitBubbles = isEventSupported('submit'), + changeBubbles = isEventSupported('change') + + if (!submitBubbles || !changeBubbles) { + // augment the Event.Handler class to observe custom events when needed + Event.Handler.prototype.initialize = Event.Handler.prototype.initialize.wrap( + function(init, element, eventName, selector, callback) { + init(element, eventName, selector, callback) + // is the handler being attached to an element that doesn't support this event? + if ( (!submitBubbles && this.eventName == 'submit' && !isForm(this.element)) || + (!changeBubbles && this.eventName == 'change' && !isInput(this.element)) ) { + // "submit" => "emulated:submit" + this.eventName = 'emulated:' + this.eventName + } + } + ) + } + + if (!submitBubbles) { + // discover forms on the page by observing focus events which always bubble + document.on('focusin', 'form', function(focusEvent, form) { + // special handler for the real "submit" event (one-time operation) + if (!form.retrieve('emulated:submit')) { + form.on('submit', function(submitEvent) { + var emulated = form.fire('emulated:submit', submitEvent, true) + // if custom event received preventDefault, cancel the real one too + if (emulated.returnValue === false) submitEvent.preventDefault() + }) + form.store('emulated:submit', true) + } + }) + } + + if (!changeBubbles) { + // discover form inputs on the page + document.on('focusin', 'input, select, texarea', function(focusEvent, input) { + // special handler for real "change" events + if (!input.retrieve('emulated:change')) { + input.on('change', function(changeEvent) { + input.fire('emulated:change', changeEvent, true) + }) + input.store('emulated:change', true) + } + }) + } + + function handleRemote(element) { + var method, url, params; + + var event = element.fire("ajax:before"); + if (event.stopped) return false; + + if (element.tagName.toLowerCase() === 'form') { + method = element.readAttribute('method') || 'post'; + url = element.readAttribute('action'); + params = element.serialize(); + } else { + method = element.readAttribute('data-method') || 'get'; + url = element.readAttribute('href'); + params = {}; + } + + new Ajax.Request(url, { + method: method, + parameters: params, + evalScripts: true, + + onComplete: function(request) { element.fire("ajax:complete", request); }, + onSuccess: function(request) { element.fire("ajax:success", request); }, + onFailure: function(request) { element.fire("ajax:failure", request); } + }); + + element.fire("ajax:after"); + } + + function handleMethod(element) { + var method = element.readAttribute('data-method'), + url = element.readAttribute('href'), + csrf_param = $$('meta[name=csrf-param]')[0], + csrf_token = $$('meta[name=csrf-token]')[0]; + + var form = new Element('form', { method: "POST", action: url, style: "display: none;" }); + element.parentNode.insert(form); + + if (method !== 'post') { + var field = new Element('input', { type: 'hidden', name: '_method', value: method }); + form.insert(field); + } + + if (csrf_param) { + var param = csrf_param.readAttribute('content'), + token = csrf_token.readAttribute('content'), + field = new Element('input', { type: 'hidden', name: param, value: token }); + form.insert(field); + } + + form.submit(); + } + + + document.on("click", "*[data-confirm]", function(event, element) { + var message = element.readAttribute('data-confirm'); + if (!confirm(message)) event.stop(); + }); + + document.on("click", "a[data-remote]", function(event, element) { + if (event.stopped) return; + handleRemote(element); + event.stop(); + }); + + document.on("click", "a[data-method]", function(event, element) { + if (event.stopped) return; + handleMethod(element); + event.stop(); + }); + + document.on("submit", function(event) { + var element = event.findElement(), + message = element.readAttribute('data-confirm'); + if (message && !confirm(message)) { + event.stop(); + return false; + } + + var inputs = element.select("input[type=submit][data-disable-with]"); + inputs.each(function(input) { + input.disabled = true; + input.writeAttribute('data-original-value', input.value); + input.value = input.readAttribute('data-disable-with'); + }); + + var element = event.findElement("form[data-remote]"); + if (element) { + handleRemote(element); + event.stop(); + } + }); + + document.on("ajax:after", "form", function(event, element) { + var inputs = element.select("input[type=submit][disabled=true][data-disable-with]"); + inputs.each(function(input) { + input.value = input.readAttribute('data-original-value'); + input.removeAttribute('data-original-value'); + input.disabled = false; + }); + }); + + Ajax.Responders.register({ + onCreate: function(request) { + var csrf_meta_tag = $$('meta[name=csrf-token]')[0]; + + if (csrf_meta_tag) { + var header = 'X-CSRF-Token', + token = csrf_meta_tag.readAttribute('content'); + + if (!request.options.requestHeaders) { + request.options.requestHeaders = {}; + } + request.options.requestHeaders[header] = token; + } + } + }); +})(); diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..085187f --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,5 @@ +# See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file +# +# To ban all spiders from the entire site uncomment the next two lines: +# User-Agent: * +# Disallow: / diff --git a/public/stylesheets/.gitkeep b/public/stylesheets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/script/rails b/script/rails new file mode 100755 index 0000000..f8da2cf --- /dev/null +++ b/script/rails @@ -0,0 +1,6 @@ +#!/usr/bin/env ruby +# This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. + +APP_PATH = File.expand_path('../../config/application', __FILE__) +require File.expand_path('../../config/boot', __FILE__) +require 'rails/commands' diff --git a/test/performance/browsing_test.rb b/test/performance/browsing_test.rb new file mode 100644 index 0000000..867fc8c --- /dev/null +++ b/test/performance/browsing_test.rb @@ -0,0 +1,9 @@ +require 'test_helper' +require 'rails/performance_test_help' + +# Profiling results for each test method are written to tmp/performance. +class BrowsingTest < ActionDispatch::PerformanceTest + def test_homepage + get '/' + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb new file mode 100644 index 0000000..8bf1192 --- /dev/null +++ b/test/test_helper.rb @@ -0,0 +1,13 @@ +ENV["RAILS_ENV"] = "test" +require File.expand_path('../../config/environment', __FILE__) +require 'rails/test_help' + +class ActiveSupport::TestCase + # Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order. + # + # Note: You'll currently still have to declare fixtures explicitly in integration tests + # -- they do not yet inherit this setting + fixtures :all + + # Add more helper methods to be used by all tests here... +end diff --git a/vendor/plugins/.gitkeep b/vendor/plugins/.gitkeep new file mode 100644 index 0000000..e69de29 From 99e5a6aa1584838f6af16722e10e17d9b140d9f8 Mon Sep 17 00:00:00 2001 From: Maxwell Swadling Date: Sat, 7 May 2011 20:07:51 +1000 Subject: [PATCH 02/55] Added friendly_id. Signed-off-by: Maxwell Swadling --- Gemfile | 2 ++ Gemfile.lock | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/Gemfile b/Gemfile index 7105cee..ea571e4 100644 --- a/Gemfile +++ b/Gemfile @@ -7,6 +7,8 @@ gem 'rails', '3.0.7' gem 'sqlite3' +gem 'friendly_id' + # Use unicorn as the web server # gem 'unicorn' diff --git a/Gemfile.lock b/Gemfile.lock index 89394e1..b1e526f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -29,9 +29,12 @@ GEM activesupport (= 3.0.7) activesupport (3.0.7) arel (2.0.9) + babosa (0.3.3) builder (2.1.2) erubis (2.6.6) abstract (>= 1.0.0) + friendly_id (3.2.1.1) + babosa (~> 0.3.0) i18n (0.5.0) mail (2.2.19) activesupport (>= 2.3.6) @@ -69,5 +72,6 @@ PLATFORMS ruby DEPENDENCIES + friendly_id rails (= 3.0.7) sqlite3 From a7031c9cfb6a87d3ebbe13d1cf43fb210dae870c Mon Sep 17 00:00:00 2001 From: Maxwell Swadling Date: Sat, 7 May 2011 20:13:17 +1000 Subject: [PATCH 03/55] Added static content. Signed-off-by: Maxwell Swadling --- app/controllers/statics_controller.rb | 7 +++++++ app/helpers/statics_helper.rb | 2 ++ app/models/static.rb | 7 +++++++ app/views/statics/show.html.erb | 20 ++++++++++++++++++++ db/migrate/20110507094232_create_slugs.rb | 18 ++++++++++++++++++ db/migrate/20110507094645_create_statics.rb | 15 +++++++++++++++ 6 files changed, 69 insertions(+) create mode 100644 app/controllers/statics_controller.rb create mode 100644 app/helpers/statics_helper.rb create mode 100644 app/models/static.rb create mode 100644 app/views/statics/show.html.erb create mode 100644 db/migrate/20110507094232_create_slugs.rb create mode 100644 db/migrate/20110507094645_create_statics.rb diff --git a/app/controllers/statics_controller.rb b/app/controllers/statics_controller.rb new file mode 100644 index 0000000..2691714 --- /dev/null +++ b/app/controllers/statics_controller.rb @@ -0,0 +1,7 @@ +class StaticsController < ApplicationController + + def show + @static = Static.find(params[:id]) + end + +end diff --git a/app/helpers/statics_helper.rb b/app/helpers/statics_helper.rb new file mode 100644 index 0000000..7d485f2 --- /dev/null +++ b/app/helpers/statics_helper.rb @@ -0,0 +1,2 @@ +module StaticsHelper +end diff --git a/app/models/static.rb b/app/models/static.rb new file mode 100644 index 0000000..ac9c895 --- /dev/null +++ b/app/models/static.rb @@ -0,0 +1,7 @@ +class Static < ActiveRecord::Base + has_friendly_id :slug, :use_slug => true + + validates :title, :presence => true + validates :content, :presence => true + validates :slug, :presence => true +end diff --git a/app/views/statics/show.html.erb b/app/views/statics/show.html.erb new file mode 100644 index 0000000..cd07600 --- /dev/null +++ b/app/views/statics/show.html.erb @@ -0,0 +1,20 @@ +

    <%= notice %>

    + +

    + Title: + <%= @static.title %> +

    + +

    + Content: + <%= @static.content %> +

    + +

    + Slug: + <%= @static.slug %> +

    + + +<%= link_to 'Edit', edit_static_path(@static) %> | +<%= link_to 'Back', statics_path %> diff --git a/db/migrate/20110507094232_create_slugs.rb b/db/migrate/20110507094232_create_slugs.rb new file mode 100644 index 0000000..bd1f61a --- /dev/null +++ b/db/migrate/20110507094232_create_slugs.rb @@ -0,0 +1,18 @@ +class CreateSlugs < ActiveRecord::Migration + def self.up + create_table :slugs do |t| + t.string :name + t.integer :sluggable_id + t.integer :sequence, :null => false, :default => 1 + t.string :sluggable_type, :limit => 40 + t.string :scope + t.datetime :created_at + end + add_index :slugs, :sluggable_id + add_index :slugs, [:name, :sluggable_type, :sequence, :scope], :name => "index_slugs_on_n_s_s_and_s", :unique => true + end + + def self.down + drop_table :slugs + end +end diff --git a/db/migrate/20110507094645_create_statics.rb b/db/migrate/20110507094645_create_statics.rb new file mode 100644 index 0000000..e289506 --- /dev/null +++ b/db/migrate/20110507094645_create_statics.rb @@ -0,0 +1,15 @@ +class CreateStatics < ActiveRecord::Migration + def self.up + create_table :statics do |t| + t.string :title + t.text :content + t.string :slug + + t.timestamps + end + end + + def self.down + drop_table :statics + end +end From 48ec0a3f8e93020d109b061d08e6c27920fd6cc1 Mon Sep 17 00:00:00 2001 From: Maxwell Swadling Date: Sat, 7 May 2011 20:49:15 +1000 Subject: [PATCH 04/55] Added news item. Signed-off-by: Maxwell Swadling --- app/controllers/news_items_controller.rb | 25 ++++++++++++++++ app/helpers/news_items_helper.rb | 2 ++ app/models/news_item.rb | 15 ++++++++++ app/views/news_items/index.html.erb | 29 +++++++++++++++++++ app/views/news_items/show.html.erb | 25 ++++++++++++++++ .../20110507094824_create_news_items.rb | 16 ++++++++++ 6 files changed, 112 insertions(+) create mode 100644 app/controllers/news_items_controller.rb create mode 100644 app/helpers/news_items_helper.rb create mode 100644 app/models/news_item.rb create mode 100644 app/views/news_items/index.html.erb create mode 100644 app/views/news_items/show.html.erb create mode 100644 db/migrate/20110507094824_create_news_items.rb diff --git a/app/controllers/news_items_controller.rb b/app/controllers/news_items_controller.rb new file mode 100644 index 0000000..895e42d --- /dev/null +++ b/app/controllers/news_items_controller.rb @@ -0,0 +1,25 @@ +class NewsItemsController < ApplicationController + before_filter :public_post, :only => :show + + def index + @news_items = NewsItem.all + + respond_to do |format| + format.html + # format.xml # TODO: Create builder feed, use as RSS + Feedburner + end + end + + def show + @news_item = NewsItem.find(params[:id]) + end + + private + + def public_post + @news_item = NewsItem.find(params[:id]) + unless @news_item.published? + redirect_to(:action => :index, :notice => "Post not available yet.") + end + end +end diff --git a/app/helpers/news_items_helper.rb b/app/helpers/news_items_helper.rb new file mode 100644 index 0000000..0863be7 --- /dev/null +++ b/app/helpers/news_items_helper.rb @@ -0,0 +1,2 @@ +module NewsItemsHelper +end diff --git a/app/models/news_item.rb b/app/models/news_item.rb new file mode 100644 index 0000000..f1b23b2 --- /dev/null +++ b/app/models/news_item.rb @@ -0,0 +1,15 @@ +class NewsItem < ActiveRecord::Base + has_friendly_id :title, :use_slug => true + + validates :title, :presence => true + validates :content, :presence => true + validates :publish_date, :presence => true + validates :author, :presence => true + + scope :published, lambda { where "news_items.publish_date <= ?", Time.now} + + # If this post is visible yet + def published? + DateTime.now > self.publish_date + end +end diff --git a/app/views/news_items/index.html.erb b/app/views/news_items/index.html.erb new file mode 100644 index 0000000..5348d31 --- /dev/null +++ b/app/views/news_items/index.html.erb @@ -0,0 +1,29 @@ +

    Listing news_items

    + + + + + + + + + + + + +<% @news_items.each do |news_item| %> + + + + + + + + + +<% end %> +
    TitleContentPublish dateAuthor
    <%= news_item.title %><%= news_item.content %><%= news_item.publish_date %><%= news_item.author %><%= link_to 'Show', news_item %><%= link_to 'Edit', edit_news_item_path(news_item) %><%= link_to 'Destroy', news_item, :confirm => 'Are you sure?', :method => :delete %>
    + +
    + +<%= link_to 'New News item', new_news_item_path %> diff --git a/app/views/news_items/show.html.erb b/app/views/news_items/show.html.erb new file mode 100644 index 0000000..6edc662 --- /dev/null +++ b/app/views/news_items/show.html.erb @@ -0,0 +1,25 @@ +

    <%= notice %>

    + +

    + Title: + <%= @news_item.title %> +

    + +

    + Content: + <%= @news_item.content %> +

    + +

    + Publish date: + <%= @news_item.publish_date %> +

    + +

    + Author: + <%= @news_item.author %> +

    + + +<%= link_to 'Edit', edit_news_item_path(@news_item) %> | +<%= link_to 'Back', news_items_path %> diff --git a/db/migrate/20110507094824_create_news_items.rb b/db/migrate/20110507094824_create_news_items.rb new file mode 100644 index 0000000..e426db9 --- /dev/null +++ b/db/migrate/20110507094824_create_news_items.rb @@ -0,0 +1,16 @@ +class CreateNewsItems < ActiveRecord::Migration + def self.up + create_table :news_items do |t| + t.string :title + t.text :content + t.datetime :publish_date + t.integer :author + + t.timestamps + end + end + + def self.down + drop_table :news_items + end +end From bdd014beb6200f89af272f5183545e9f17b16eba Mon Sep 17 00:00:00 2001 From: Maxwell Swadling Date: Sat, 7 May 2011 21:06:58 +1000 Subject: [PATCH 05/55] Added events. Signed-off-by: Maxwell Swadling --- app/controllers/events_controller.rb | 26 ++++++++++ app/helpers/events_helper.rb | 2 + app/models/event.rb | 26 ++++++++++ app/models/news_item.rb | 6 ++- app/views/events/index.html.erb | 41 ++++++++++++++++ app/views/events/show.html.erb | 55 ++++++++++++++++++++++ db/migrate/20110507094950_create_events.rb | 22 +++++++++ 7 files changed, 176 insertions(+), 2 deletions(-) create mode 100644 app/controllers/events_controller.rb create mode 100644 app/helpers/events_helper.rb create mode 100644 app/models/event.rb create mode 100644 app/views/events/index.html.erb create mode 100644 app/views/events/show.html.erb create mode 100644 db/migrate/20110507094950_create_events.rb diff --git a/app/controllers/events_controller.rb b/app/controllers/events_controller.rb new file mode 100644 index 0000000..b929904 --- /dev/null +++ b/app/controllers/events_controller.rb @@ -0,0 +1,26 @@ +class EventsController < ApplicationController + before_filter :public_post, :only => :show + + def index + @events = Event.all + + respond_to do |format| + format.html # index.html.erb + # format.xml # TODO: Format as .ical + end + end + + def show + @event = Event.find(params[:id]) + end + + private + + def public_post + @event = Event.find(params[:id]) + unless @event.published? + redirect_to(:action => :index, :notice => "Event not available yet.") + end + end + +end diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb new file mode 100644 index 0000000..8a9a878 --- /dev/null +++ b/app/helpers/events_helper.rb @@ -0,0 +1,2 @@ +module EventsHelper +end diff --git a/app/models/event.rb b/app/models/event.rb new file mode 100644 index 0000000..52f1028 --- /dev/null +++ b/app/models/event.rb @@ -0,0 +1,26 @@ +class Event < ActiveRecord::Base + belongs_to :author, :class_name => "User" + + has_friendly_id :name, :use_slug => true + + validates :name, :presence => true + validates :time, :presence => true + validates :location, :presence => true + validates :description, :presence => true + validates :publish_date, :presence => true + validates :author, :presence => true + + validates :registration_required, :presence => true + validates :registration_email, + :format => {:with => /^([\w\.%\+\-]+)@([\w\-]+\.)+([\w]{2,})$/i} + validates :volunteers_required, :presence => true + validates :volunteers_email, + :format => {:with => /^([\w\.%\+\-]+)@([\w\-]+\.)+([\w]{2,})$/i} + + scope :published, lambda { where "events.publish_date <= ?", Time.now} + + # If this event is visible yet + def published? + DateTime.now >= self.publish_date + end +end diff --git a/app/models/news_item.rb b/app/models/news_item.rb index f1b23b2..2e470a5 100644 --- a/app/models/news_item.rb +++ b/app/models/news_item.rb @@ -1,4 +1,6 @@ class NewsItem < ActiveRecord::Base + belongs_to :author, :class_name => "User" + has_friendly_id :title, :use_slug => true validates :title, :presence => true @@ -8,8 +10,8 @@ class NewsItem < ActiveRecord::Base scope :published, lambda { where "news_items.publish_date <= ?", Time.now} - # If this post is visible yet + # If this news item is visible yet def published? - DateTime.now > self.publish_date + DateTime.now >= self.publish_date end end diff --git a/app/views/events/index.html.erb b/app/views/events/index.html.erb new file mode 100644 index 0000000..df2a3b2 --- /dev/null +++ b/app/views/events/index.html.erb @@ -0,0 +1,41 @@ +

    Listing events

    + + + + + + + + + + + + + + + + + + +<% @events.each do |event| %> + + + + + + + + + + + + + + + +<% end %> +
    NameTimeLocationRegistration requiredRegistration emailVolunteers requiredVolunteers emailDescriptionPublish dateAuthor
    <%= event.name %><%= event.time %><%= event.location %><%= event.registration_required %><%= event.registration_email %><%= event.volunteers_required %><%= event.volunteers_email %><%= event.description %><%= event.publish_date %><%= event.author %><%= link_to 'Show', event %><%= link_to 'Edit', edit_event_path(event) %><%= link_to 'Destroy', event, :confirm => 'Are you sure?', :method => :delete %>
    + +
    + +<%= link_to 'New Event', new_event_path %> diff --git a/app/views/events/show.html.erb b/app/views/events/show.html.erb new file mode 100644 index 0000000..6005414 --- /dev/null +++ b/app/views/events/show.html.erb @@ -0,0 +1,55 @@ +

    <%= notice %>

    + +

    + Name: + <%= @event.name %> +

    + +

    + Time: + <%= @event.time %> +

    + +

    + Location: + <%= @event.location %> +

    + +

    + Registration required: + <%= @event.registration_required %> +

    + +

    + Registration email: + <%= @event.registration_email %> +

    + +

    + Volunteers required: + <%= @event.volunteers_required %> +

    + +

    + Volunteers email: + <%= @event.volunteers_email %> +

    + +

    + Description: + <%= @event.description %> +

    + +

    + Publish date: + <%= @event.publish_date %> +

    + +

    + Author: + <%= @event.author %> +

    + + +<%= link_to 'Edit', edit_event_path(@event) %> | +<%= link_to 'Back', events_path %> diff --git a/db/migrate/20110507094950_create_events.rb b/db/migrate/20110507094950_create_events.rb new file mode 100644 index 0000000..9e72ad8 --- /dev/null +++ b/db/migrate/20110507094950_create_events.rb @@ -0,0 +1,22 @@ +class CreateEvents < ActiveRecord::Migration + def self.up + create_table :events do |t| + t.string :name + t.datetime :time + t.string :location + t.boolean :registration_required + t.string :registration_email + t.boolean :volunteers_required + t.string :volunteers_email + t.text :description + t.datetime :publish_date + t.integer :author + + t.timestamps + end + end + + def self.down + drop_table :events + end +end From c69520401f0b0dd6d7d94a1977b91791ef202059 Mon Sep 17 00:00:00 2001 From: Maxwell Swadling Date: Sat, 7 May 2011 21:21:40 +1000 Subject: [PATCH 06/55] Added sponsors. Signed-off-by: Maxwell Swadling --- Gemfile | 1 + Gemfile.lock | 4 +++ app/controllers/sponsors_controller.rb | 5 +++ app/helpers/sponsors_helper.rb | 2 ++ app/models/event.rb | 2 +- app/models/news_item.rb | 2 +- app/models/sponsor.rb | 17 +++++++++ app/views/sponsors/index.html.erb | 37 ++++++++++++++++++++ db/migrate/20110507100252_create_sponsors.rb | 25 +++++++++++++ 9 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 app/controllers/sponsors_controller.rb create mode 100644 app/helpers/sponsors_helper.rb create mode 100644 app/models/sponsor.rb create mode 100644 app/views/sponsors/index.html.erb create mode 100644 db/migrate/20110507100252_create_sponsors.rb diff --git a/Gemfile b/Gemfile index ea571e4..48371ed 100644 --- a/Gemfile +++ b/Gemfile @@ -8,6 +8,7 @@ gem 'rails', '3.0.7' gem 'sqlite3' gem 'friendly_id' +gem 'paperclip' # Use unicorn as the web server # gem 'unicorn' diff --git a/Gemfile.lock b/Gemfile.lock index b1e526f..9653f91 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -42,6 +42,9 @@ GEM mime-types (~> 1.16) treetop (~> 1.4.8) mime-types (1.16) + paperclip (2.3.11) + activerecord (>= 2.3.0) + activesupport (>= 2.3.2) polyglot (0.3.1) rack (1.2.2) rack-mount (0.6.14) @@ -73,5 +76,6 @@ PLATFORMS DEPENDENCIES friendly_id + paperclip rails (= 3.0.7) sqlite3 diff --git a/app/controllers/sponsors_controller.rb b/app/controllers/sponsors_controller.rb new file mode 100644 index 0000000..c232129 --- /dev/null +++ b/app/controllers/sponsors_controller.rb @@ -0,0 +1,5 @@ +class SponsorsController < ApplicationController + def index + @sponsors = Sponsor.visible + end +end diff --git a/app/helpers/sponsors_helper.rb b/app/helpers/sponsors_helper.rb new file mode 100644 index 0000000..25bb284 --- /dev/null +++ b/app/helpers/sponsors_helper.rb @@ -0,0 +1,2 @@ +module SponsorsHelper +end diff --git a/app/models/event.rb b/app/models/event.rb index 52f1028..276f2b1 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -21,6 +21,6 @@ class Event < ActiveRecord::Base # If this event is visible yet def published? - DateTime.now >= self.publish_date + self.publish_date <= DateTime.now end end diff --git a/app/models/news_item.rb b/app/models/news_item.rb index 2e470a5..ed6e961 100644 --- a/app/models/news_item.rb +++ b/app/models/news_item.rb @@ -12,6 +12,6 @@ class NewsItem < ActiveRecord::Base # If this news item is visible yet def published? - DateTime.now >= self.publish_date + self.publish_date <= DateTime.now end end diff --git a/app/models/sponsor.rb b/app/models/sponsor.rb new file mode 100644 index 0000000..1fb9fcc --- /dev/null +++ b/app/models/sponsor.rb @@ -0,0 +1,17 @@ +class Sponsor < ActiveRecord::Base + has_friendly_id :title, :use_slug => true + has_attached_file :image, :styles => { :medium => "300x300>", :thumb => "100x100>" } + + validates :name, :presence => true + validates :website, :presence => true + validates :alt_text, :presence => true + validates :start_date, :presence => true + validates :expiry_date, :presence => true + + scope :visible, lambda { where "sponsors.expiry_date >= ?", Time.now} + + # If this sponsor is currently visible + def visible? + self.expiry_date >= DateTime.now + end +end diff --git a/app/views/sponsors/index.html.erb b/app/views/sponsors/index.html.erb new file mode 100644 index 0000000..ca16f63 --- /dev/null +++ b/app/views/sponsors/index.html.erb @@ -0,0 +1,37 @@ +

    Listing sponsors

    + + + + + + + + + + + + + + + + +<% @sponsors.each do |sponsor| %> + + + + + + + + + + + + + +<% end %> +
    NameDescriptionWebsiteAlt textAmount paidStart dateExpiry dateHtml override
    <%= sponsor.name %><%= sponsor.description %><%= sponsor.website %><%= sponsor.alt_text %><%= sponsor.amount_paid %><%= sponsor.start_date %><%= sponsor.expiry_date %><%= sponsor.html_override %><%= link_to 'Show', sponsor %><%= link_to 'Edit', edit_sponsor_path(sponsor) %><%= link_to 'Destroy', sponsor, :confirm => 'Are you sure?', :method => :delete %>
    + +
    + +<%= link_to 'New Sponsor', new_sponsor_path %> diff --git a/db/migrate/20110507100252_create_sponsors.rb b/db/migrate/20110507100252_create_sponsors.rb new file mode 100644 index 0000000..c271c27 --- /dev/null +++ b/db/migrate/20110507100252_create_sponsors.rb @@ -0,0 +1,25 @@ +class CreateSponsors < ActiveRecord::Migration + def self.up + create_table :sponsors do |t| + t.string :name + t.text :description + t.string :website + t.string :alt_text + t.integer :amount_paid + t.datetime :start_date + t.datetime :expiry_date + t.text :html_override + + t.string :image_file_name + t.string :image_content_type + t.integer :image_file_size + t.datetime :image_updated_at + + t.timestamps + end + end + + def self.down + drop_table :sponsors + end +end From b008914e5d637bc3667b7887f88a2ae4b5302d3f Mon Sep 17 00:00:00 2001 From: Maxwell Swadling Date: Sat, 7 May 2011 21:26:06 +1000 Subject: [PATCH 07/55] Added suggestions. Signed-off-by: Maxwell Swadling --- app/controllers/suggestions_controller.rb | 24 +++++++++++++++ app/helpers/suggestions_helper.rb | 2 ++ app/models/comment.rb | 8 +++++ app/models/suggestion.rb | 8 +++++ app/views/suggestions/_form.html.erb | 29 +++++++++++++++++++ app/views/suggestions/index.html.erb | 27 +++++++++++++++++ app/views/suggestions/new.html.erb | 5 ++++ app/views/suggestions/show.html.erb | 20 +++++++++++++ .../20110507100507_create_suggestions.rb | 15 ++++++++++ db/migrate/20110507100554_create_comments.rb | 15 ++++++++++ 10 files changed, 153 insertions(+) create mode 100644 app/controllers/suggestions_controller.rb create mode 100644 app/helpers/suggestions_helper.rb create mode 100644 app/models/comment.rb create mode 100644 app/models/suggestion.rb create mode 100644 app/views/suggestions/_form.html.erb create mode 100644 app/views/suggestions/index.html.erb create mode 100644 app/views/suggestions/new.html.erb create mode 100644 app/views/suggestions/show.html.erb create mode 100644 db/migrate/20110507100507_create_suggestions.rb create mode 100644 db/migrate/20110507100554_create_comments.rb diff --git a/app/controllers/suggestions_controller.rb b/app/controllers/suggestions_controller.rb new file mode 100644 index 0000000..4cc811a --- /dev/null +++ b/app/controllers/suggestions_controller.rb @@ -0,0 +1,24 @@ +class SuggestionsController < ApplicationController + def index + @suggestions = Suggestion.all + end + + def show + @suggestion = Suggestion.find(params[:id]) + end + + def new + @suggestion = Suggestion.new + end + + def create + @suggestion = Suggestion.new(params[:suggestion]) + + if @suggestion.save + redirect_to(@suggestion, :notice => 'Thanks for your suggestion.') + else + render :action => "new" + end + end + +end diff --git a/app/helpers/suggestions_helper.rb b/app/helpers/suggestions_helper.rb new file mode 100644 index 0000000..0e358dd --- /dev/null +++ b/app/helpers/suggestions_helper.rb @@ -0,0 +1,2 @@ +module SuggestionsHelper +end diff --git a/app/models/comment.rb b/app/models/comment.rb new file mode 100644 index 0000000..c95ed58 --- /dev/null +++ b/app/models/comment.rb @@ -0,0 +1,8 @@ +class Comment < ActiveRecord::Base + belongs_to :suggestion + belongs_to :user + + validates :suggestion_id, :presence => true + validates :user, :presence => true + validates :comment, :presence => true +end diff --git a/app/models/suggestion.rb b/app/models/suggestion.rb new file mode 100644 index 0000000..affa2bb --- /dev/null +++ b/app/models/suggestion.rb @@ -0,0 +1,8 @@ +class Suggestion < ActiveRecord::Base + has_many :comments + belongs_to :user + + validates :subject, :presence => true + validates :message, :presence => true + validates :user, :presence => true +end diff --git a/app/views/suggestions/_form.html.erb b/app/views/suggestions/_form.html.erb new file mode 100644 index 0000000..176a721 --- /dev/null +++ b/app/views/suggestions/_form.html.erb @@ -0,0 +1,29 @@ +<%= form_for(@suggestion) do |f| %> + <% if @suggestion.errors.any? %> +
    +

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

    + +
      + <% @suggestion.errors.full_messages.each do |msg| %> +
    • <%= msg %>
    • + <% end %> +
    +
    + <% end %> + +
    + <%= f.label :subject %>
    + <%= f.text_field :subject %> +
    +
    + <%= f.label :message %>
    + <%= f.text_area :message %> +
    +
    + <%= f.label :user_id %>
    + <%= f.text_field :user_id %> +
    +
    + <%= f.submit %> +
    +<% end %> diff --git a/app/views/suggestions/index.html.erb b/app/views/suggestions/index.html.erb new file mode 100644 index 0000000..c660ea9 --- /dev/null +++ b/app/views/suggestions/index.html.erb @@ -0,0 +1,27 @@ +

    Listing suggestions

    + + + + + + + + + + + +<% @suggestions.each do |suggestion| %> + + + + + + + + +<% end %> +
    SubjectMessageUser
    <%= suggestion.subject %><%= suggestion.message %><%= suggestion.user_id %><%= link_to 'Show', suggestion %><%= link_to 'Edit', edit_suggestion_path(suggestion) %><%= link_to 'Destroy', suggestion, :confirm => 'Are you sure?', :method => :delete %>
    + +
    + +<%= link_to 'New Suggestion', new_suggestion_path %> diff --git a/app/views/suggestions/new.html.erb b/app/views/suggestions/new.html.erb new file mode 100644 index 0000000..e27e598 --- /dev/null +++ b/app/views/suggestions/new.html.erb @@ -0,0 +1,5 @@ +

    New suggestion

    + +<%= render 'form' %> + +<%= link_to 'Back', suggestions_path %> diff --git a/app/views/suggestions/show.html.erb b/app/views/suggestions/show.html.erb new file mode 100644 index 0000000..4412dcd --- /dev/null +++ b/app/views/suggestions/show.html.erb @@ -0,0 +1,20 @@ +

    <%= notice %>

    + +

    + Subject: + <%= @suggestion.subject %> +

    + +

    + Message: + <%= @suggestion.message %> +

    + +

    + User: + <%= @suggestion.user_id %> +

    + + +<%= link_to 'Edit', edit_suggestion_path(@suggestion) %> | +<%= link_to 'Back', suggestions_path %> diff --git a/db/migrate/20110507100507_create_suggestions.rb b/db/migrate/20110507100507_create_suggestions.rb new file mode 100644 index 0000000..59efc84 --- /dev/null +++ b/db/migrate/20110507100507_create_suggestions.rb @@ -0,0 +1,15 @@ +class CreateSuggestions < ActiveRecord::Migration + def self.up + create_table :suggestions do |t| + t.string :subject + t.text :message + t.integer :user_id + + t.timestamps + end + end + + def self.down + drop_table :suggestions + end +end diff --git a/db/migrate/20110507100554_create_comments.rb b/db/migrate/20110507100554_create_comments.rb new file mode 100644 index 0000000..93f8918 --- /dev/null +++ b/db/migrate/20110507100554_create_comments.rb @@ -0,0 +1,15 @@ +class CreateComments < ActiveRecord::Migration + def self.up + create_table :comments do |t| + t.integer :suggestion_id + t.integer :user_id + t.text :comment + + t.timestamps + end + end + + def self.down + drop_table :comments + end +end From 264a88473bbaf94de677ebade1223a95852c0347 Mon Sep 17 00:00:00 2001 From: Maxwell Swadling Date: Sat, 7 May 2011 21:26:16 +1000 Subject: [PATCH 08/55] Added routes and removed tests. Signed-off-by: Maxwell Swadling --- config/routes.rb | 12 ++++++++++++ test/performance/browsing_test.rb | 9 --------- test/test_helper.rb | 13 ------------- 3 files changed, 12 insertions(+), 22 deletions(-) delete mode 100644 test/performance/browsing_test.rb delete mode 100644 test/test_helper.rb diff --git a/config/routes.rb b/config/routes.rb index e8fa8bc..05db265 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,4 +1,16 @@ CSESocWebsite::Application.routes.draw do + resources :comments + + resources :suggestions + + resources :sponsors + + resources :events + + resources :news_items + + resources :statics + # The priority is based upon order of creation: # first created -> highest priority. diff --git a/test/performance/browsing_test.rb b/test/performance/browsing_test.rb deleted file mode 100644 index 867fc8c..0000000 --- a/test/performance/browsing_test.rb +++ /dev/null @@ -1,9 +0,0 @@ -require 'test_helper' -require 'rails/performance_test_help' - -# Profiling results for each test method are written to tmp/performance. -class BrowsingTest < ActionDispatch::PerformanceTest - def test_homepage - get '/' - end -end diff --git a/test/test_helper.rb b/test/test_helper.rb deleted file mode 100644 index 8bf1192..0000000 --- a/test/test_helper.rb +++ /dev/null @@ -1,13 +0,0 @@ -ENV["RAILS_ENV"] = "test" -require File.expand_path('../../config/environment', __FILE__) -require 'rails/test_help' - -class ActiveSupport::TestCase - # Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order. - # - # Note: You'll currently still have to declare fixtures explicitly in integration tests - # -- they do not yet inherit this setting - fixtures :all - - # Add more helper methods to be used by all tests here... -end From f8c34fbbd19a0d40e98aa0f5d0e6c7c159308f6c Mon Sep 17 00:00:00 2001 From: Maxwell Swadling Date: Sat, 7 May 2011 21:26:29 +1000 Subject: [PATCH 09/55] Added scaffold stylesheet. Signed-off-by: Maxwell Swadling --- public/stylesheets/scaffold.css | 56 +++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 public/stylesheets/scaffold.css diff --git a/public/stylesheets/scaffold.css b/public/stylesheets/scaffold.css new file mode 100644 index 0000000..1ae7000 --- /dev/null +++ b/public/stylesheets/scaffold.css @@ -0,0 +1,56 @@ +body { background-color: #fff; color: #333; } + +body, p, ol, ul, td { + font-family: verdana, arial, helvetica, sans-serif; + font-size: 13px; + line-height: 18px; +} + +pre { + background-color: #eee; + padding: 10px; + font-size: 11px; +} + +a { color: #000; } +a:visited { color: #666; } +a:hover { color: #fff; background-color:#000; } + +div.field, div.actions { + margin-bottom: 10px; +} + +#notice { + color: green; +} + +.field_with_errors { + padding: 2px; + background-color: red; + display: table; +} + +#error_explanation { + width: 450px; + border: 2px solid red; + padding: 7px; + padding-bottom: 0; + margin-bottom: 20px; + background-color: #f0f0f0; +} + +#error_explanation h2 { + text-align: left; + font-weight: bold; + padding: 5px 5px 5px 15px; + font-size: 12px; + margin: -7px; + margin-bottom: 0px; + background-color: #c00; + color: #fff; +} + +#error_explanation ul li { + font-size: 12px; + list-style: square; +} From 28a6bee308d4c14ed983d465c7f2267337d567d2 Mon Sep 17 00:00:00 2001 From: Maxwell Swadling Date: Sat, 7 May 2011 21:27:12 +1000 Subject: [PATCH 10/55] Added schema. Signed-off-by: Maxwell Swadling --- db/schema.rb | 88 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 db/schema.rb diff --git a/db/schema.rb b/db/schema.rb new file mode 100644 index 0000000..ff8bc1e --- /dev/null +++ b/db/schema.rb @@ -0,0 +1,88 @@ +# This file is auto-generated from the current state of the database. Instead +# of editing this file, please use the migrations feature of Active Record to +# incrementally modify your database, and then regenerate this schema definition. +# +# Note that this schema.rb definition is the authoritative source for your +# database schema. If you need to create the application database on another +# system, you should be using db:schema:load, not running all the migrations +# from scratch. The latter is a flawed and unsustainable approach (the more migrations +# you'll amass, the slower it'll run and the greater likelihood for issues). +# +# It's strongly recommended to check this file into your version control system. + +ActiveRecord::Schema.define(:version => 20110507100554) do + + create_table "comments", :force => true do |t| + t.integer "suggestion_id" + t.integer "user_id" + t.text "comment" + t.datetime "created_at" + t.datetime "updated_at" + end + + create_table "events", :force => true do |t| + t.string "name" + t.datetime "time" + t.string "location" + t.boolean "registration_required" + t.string "registration_email" + t.boolean "volunteers_required" + t.string "volunteers_email" + t.text "description" + t.datetime "publish_date" + t.integer "author" + t.datetime "created_at" + t.datetime "updated_at" + end + + create_table "news_items", :force => true do |t| + t.string "title" + t.text "content" + t.datetime "publish_date" + t.integer "author" + t.datetime "created_at" + t.datetime "updated_at" + end + + create_table "slugs", :force => true do |t| + t.string "name" + t.integer "sluggable_id" + t.integer "sequence", :default => 1, :null => false + t.string "sluggable_type", :limit => 40 + t.string "scope" + t.datetime "created_at" + end + + add_index "slugs", ["name", "sluggable_type", "sequence", "scope"], :name => "index_slugs_on_n_s_s_and_s", :unique => true + add_index "slugs", ["sluggable_id"], :name => "index_slugs_on_sluggable_id" + + create_table "sponsors", :force => true do |t| + t.string "name" + t.text "description" + t.string "website" + t.string "alt_text" + t.integer "amount_paid" + t.datetime "start_date" + t.datetime "expiry_date" + t.text "html_override" + t.datetime "created_at" + t.datetime "updated_at" + end + + create_table "statics", :force => true do |t| + t.string "title" + t.text "content" + t.string "slug" + t.datetime "created_at" + t.datetime "updated_at" + end + + create_table "suggestions", :force => true do |t| + t.string "subject" + t.text "message" + t.integer "user_id" + t.datetime "created_at" + t.datetime "updated_at" + end + +end From d71f22d53c979aab7a9c0a697f452764ff38ed43 Mon Sep 17 00:00:00 2001 From: Maxwell Swadling Date: Sat, 7 May 2011 21:33:19 +1000 Subject: [PATCH 11/55] Added root route. Signed-off-by: Maxwell Swadling --- app/controllers/events_controller.rb | 3 +- app/controllers/news_items_controller.rb | 2 +- config/routes.rb | 18 +- public/index.html | 239 ----------------------- 4 files changed, 9 insertions(+), 253 deletions(-) delete mode 100644 public/index.html diff --git a/app/controllers/events_controller.rb b/app/controllers/events_controller.rb index b929904..aa82171 100644 --- a/app/controllers/events_controller.rb +++ b/app/controllers/events_controller.rb @@ -2,7 +2,7 @@ class EventsController < ApplicationController before_filter :public_post, :only => :show def index - @events = Event.all + @events = Event.published respond_to do |format| format.html # index.html.erb @@ -22,5 +22,4 @@ def public_post redirect_to(:action => :index, :notice => "Event not available yet.") end end - end diff --git a/app/controllers/news_items_controller.rb b/app/controllers/news_items_controller.rb index 895e42d..64a5d4c 100644 --- a/app/controllers/news_items_controller.rb +++ b/app/controllers/news_items_controller.rb @@ -2,7 +2,7 @@ class NewsItemsController < ApplicationController before_filter :public_post, :only => :show def index - @news_items = NewsItem.all + @news_items = NewsItem.published respond_to do |format| format.html diff --git a/config/routes.rb b/config/routes.rb index 05db265..2a67534 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,15 +1,11 @@ CSESocWebsite::Application.routes.draw do - resources :comments - - resources :suggestions - - resources :sponsors - - resources :events - - resources :news_items - - resources :statics + root :to => "news_items#index" + + resources :suggestions, :only => [:index, :show, :new, :create] + resources :sponsors, :only => [:index] + resources :events, :only => [:index, :show] + resources :news_items, :only => [:index, :show] + resources :statics, :only => [:show] # The priority is based upon order of creation: # first created -> highest priority. diff --git a/public/index.html b/public/index.html deleted file mode 100644 index 75d5edd..0000000 --- a/public/index.html +++ /dev/null @@ -1,239 +0,0 @@ - - - - Ruby on Rails: Welcome aboard - - - - -
    - - -
    - - - - -
    -

    Getting started

    -

    Here’s how to get rolling:

    - -
      -
    1. -

      Use rails generate to create your models and controllers

      -

      To see all available options, run it without parameters.

      -
    2. - -
    3. -

      Set up a default route and remove or rename this file

      -

      Routes are set up in config/routes.rb.

      -
    4. - -
    5. -

      Create your database

      -

      Run rake db:migrate to create your database. If you're not using SQLite (the default), edit config/database.yml with your username and password.

      -
    6. -
    -
    -
    - - -
    - - From b0ab930a16442bb04d7fd0910370aa6adf48533b Mon Sep 17 00:00:00 2001 From: Maxwell Swadling Date: Sat, 7 May 2011 22:23:26 +1000 Subject: [PATCH 12/55] Added application layout. Signed-off-by: Maxwell Swadling --- app/views/layouts/application.html.erb | 21 +- app/views/news_items/index.html.erb | 8 - app/views/shared/_analytics.html.erb | 13 ++ app/views/shared/_header.html.erb | 12 ++ public/images/header/dropdown.png | Bin 0 -> 2256 bytes public/images/header/header.png | Bin 0 -> 18449 bytes public/images/header/reg_menu_centre.png | Bin 0 -> 217 bytes public/images/header/reg_menu_left.png | Bin 0 -> 319 bytes public/images/header/reg_menu_right.png | Bin 0 -> 322 bytes public/stylesheets/1020_12_10_10.css | 173 ++++++++++++++++ public/stylesheets/reset.css | 53 +++++ public/stylesheets/scaffold.css | 11 -- public/stylesheets/style.css | 241 +++++++++++++++++++++++ public/stylesheets/text.css | 77 ++++++++ 14 files changed, 588 insertions(+), 21 deletions(-) create mode 100644 app/views/shared/_analytics.html.erb create mode 100644 app/views/shared/_header.html.erb create mode 100644 public/images/header/dropdown.png create mode 100644 public/images/header/header.png create mode 100644 public/images/header/reg_menu_centre.png create mode 100644 public/images/header/reg_menu_left.png create mode 100644 public/images/header/reg_menu_right.png create mode 100644 public/stylesheets/1020_12_10_10.css create mode 100644 public/stylesheets/reset.css create mode 100644 public/stylesheets/style.css create mode 100644 public/stylesheets/text.css diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index c0e688b..9c02f50 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -5,10 +5,27 @@ <%= stylesheet_link_tag :all %> <%= javascript_include_tag :defaults %> <%= csrf_meta_tag %> + + <%= render 'shared/analytics' %> - -<%= yield %> +
    + + + +
    + <%= yield %> +
    + + + +
    diff --git a/app/views/news_items/index.html.erb b/app/views/news_items/index.html.erb index 5348d31..8d7c27b 100644 --- a/app/views/news_items/index.html.erb +++ b/app/views/news_items/index.html.erb @@ -6,9 +6,6 @@ Content Publish date Author - - - <% @news_items.each do |news_item| %> @@ -17,13 +14,8 @@ <%= news_item.content %> <%= news_item.publish_date %> <%= news_item.author %> - <%= link_to 'Show', news_item %> - <%= link_to 'Edit', edit_news_item_path(news_item) %> - <%= link_to 'Destroy', news_item, :confirm => 'Are you sure?', :method => :delete %> <% end %>
    - -<%= link_to 'New News item', new_news_item_path %> diff --git a/app/views/shared/_analytics.html.erb b/app/views/shared/_analytics.html.erb new file mode 100644 index 0000000..fd0171c --- /dev/null +++ b/app/views/shared/_analytics.html.erb @@ -0,0 +1,13 @@ + \ No newline at end of file diff --git a/app/views/shared/_header.html.erb b/app/views/shared/_header.html.erb new file mode 100644 index 0000000..0eb71bf --- /dev/null +++ b/app/views/shared/_header.html.erb @@ -0,0 +1,12 @@ +
    + <%= link_to "", root_path, :id => "headerLink" %> + +
    \ No newline at end of file diff --git a/public/images/header/dropdown.png b/public/images/header/dropdown.png new file mode 100644 index 0000000000000000000000000000000000000000..773a20af7378b628a5e9ea8626b390b0e9277920 GIT binary patch literal 2256 zcmeAS@N?(olHy`uVBq!ia0vp^+ZY&_5;@p_tT!QEyMPo+v6E*A2N2Y7q;vrJoCO|{ z#S9GGLLkg|>2BR0prA~NYeY$Kep*R+Vo@rCV@iHfs)Ac)QEGX9QFgI{bFc~9rppU} z>cl|moQqNuOEUBG6hbm{QyC0Q^bL&k4Gs0$x%3$rI5a$6978JRyuG`>Yi_0N@sIC~ zi_<*W5?K!>b&$D`&zAXiwZ{5Y`H{2zyH+N~pDlO)+qdJVqq*Um)juDu zxG!ja|MjxgTGRL9U#%ZKv(C+4dvn{{FDECf@87v&$B(}^Z{9q5Z}9`?y>)+oy}Z2K zzrOtay}kO^qT=PZ=lVfr(d!CZE zZEyI2iMi%)3*}{6H+Tw}c^*>pyrIH5TP1O_O3tJf-$}Ry+h@*<{v?0%!@U>+3W+z8 zM3u5>Y1g8v-#08S+DMwyNwf!daFooL8JYcdFLU3iqeO?>@UR@5IxZ!qrLEoiozFr` zoR}0tHJ<}5-{U=3h3=NZ@&hpGZ@qTU`oW5kne<0=zIz{}esTaZ`#|S6%96fcTAFSB zzdxtuomE|R@$Ns>Y4gsXxu5(}`VKPON6rtm6F8rZ5} N@O1TaS?83{1OSoYx(WaQ literal 0 HcmV?d00001 diff --git a/public/images/header/header.png b/public/images/header/header.png new file mode 100644 index 0000000000000000000000000000000000000000..05994e1adbbd06ed2b7e6a7dc764fe71a7718339 GIT binary patch literal 18449 zcmX{81z1$w`#lcR9S&WB4BaUq4MR!C5YjCmEnPzh0|-2zbR#){q=X<04U&RG$Iwc5 z$AA2MfA7n6k+aX49rwQXTI*hiI9+X3Vgfn>G&D3~bv2Ma8XAT>>SudA9Mo62>JD?% zH!L4TbwfPVM+lxh6!km4r<$n`s{Q@HPjpj}Pn)Pe(!N(Vd2it1_}<^f+X2nr-=E*v z-POm=#?yh{!`mt6P?`=6?FE`TNWm~5_aM(V(-3h7mpjs*vX~;^Onebp5rANfuX^Sq#JQrzOSW@zWk8kj<9Ir(Y7zVudX`oCd7 zDF#yaUXc}QeZo1H>yk>`Mf|+49=7>Ldp6C4@1K<#A^E=>NpNxz{T@D2V0dfDZ4>*JT?@20`6-X zcnN7(sS>Rlq2Ca*CKhBeDS-u_1$``ns@W6@=@M7${^0)fa^@xcwRIV+Y-d&i6RIZJ z04v9uIALtq4&bXpGpT^JpE*~cLDjelX`3ypf{q*@#PC-~-{^8yA8uZ1%YjOsd9s?i zvUna+_Kvq}&Cz9*!T94^dQX^&J;O+m^2NGEqPO`|A6$d-RfgC58{F>pW32)YA5!+` z(tgWmq|#XvJWJt*hBMYDit)WJ)8H?D(XpuGLt z0YI@qLVcM5T~G<5XUx@ZB)-J2IUvx==1fW(EWW+|>SnuD&rH0A&uXP z$@d18@E@aj0(3ct4}~F2kR8K|D%Lv;;ctfGfwgb+vIl!9kTTZMurlf7e5fHN4q8Ua zQpY3V5@|=4S+Q`c#!J4~D+%KsJb3gcL--a+g{}!>SU7 zFQp%&1j@Gd332oUvR1JU(MRpjTQ!C0?e}*oV!6vp8pU|K~7VwSD{9q6>5(6oqDe+!{0T*A}^QU% zFKOXhH$l(A2%S%V)j$i@ioWiIZd?kWcyuOAr|r|P`IITCuvahA@`4n1_TZ(J`?G7F z_x_%dIiy$Se(qvLZlgNk$A8S28k|-;Lm0rP(4psW1FsO}2xXI|jaSwpx+&Grp;slk zc3X+{6x-CkYD8mKm^?%AL*`M+Y3Wy%2ndzXWtt#x3ZF*lqxM$j>Vot;?UvM+ygD-Ag|kpK+x_a}spA}lWy*)L z>axZh$?*b%!570I9J+WY1I92J80WYV(>pXDiRGiO91+i#~VLbPAL19i~f^u#7nUK`i`hv5m!~8ngoVv* zB{a*_1B*`i@16~S_6y8soxxoK2aq$_j7bwCgB3!3NP~(MCEnLJqDL~TEYr_PR1Z`2 z`G&heXScqM4^O)s8zeYIHSEXyzXT2qBF14s@vu%>KAq1ncU=T2_`;hvbZ8^9ndO&P zI@+zWvmfcCJhUr$KZVXr8y`kJyG3ETHtB(8iY_=f?QBbI!j zqy7+#I0AnoWyj*X{Cg&doW~k4$0N)=-Y_lv3X^5fdW)%xcn{7if<=KZK)knCVw%?) z1Oh>#*%R|5$Twh_6|-a;Y(|eCBsfW@x915{d;}d^rA6vA6ED|(a*k01;J&SZ8CUMo z$Q2RGgQ+WaHDE#9WhV?-qY|irb`Rpa1KvJMy|i0d-?|Y^{f#W0risYfxTnEuQvZEl z2Fo$Lu+%K1NmFCW+%9aLxQu|^f>^sbs0@9xV6riZXC$NVvN&6;?UFz^p0O~?XC4iP z;Jj#OT&G*n_JK2SLM(|qTfxCm#qPmy38NkYoF5e)io~%77l)z?ShLTo?W1cOA!gTU zRvE!Rh+Ab}Py4~Y)nC%1!Y<yhjcGx-8nq=FrWoJ* zK4zaCnJRW4Dwk^MbkKJOZ@_Y0RdV{wCNZfXu4?*|9~h$Ll8hFdu2jq_ERSjgV%p?W#-_@l}FvPhuvT8#n$8dL$8QuK5C-6T1A4dB6`M%I(3p zJhDu2`Igz)na99Hfn(=}=VuS7Nlwc2LgmbfA9AU23hdK3-$=H$q-u)->X#~r(xjkj z4)+?n_X(wCoNCvI_;>kEU!ddtvWsHYm9O;^3;fE`G7810?u#i2Q?2B8I2Xx@A;S@{ zvW+8ZBMb@lB>st>)sK($u&>Eq15`h5Ls2 z=Vbs^OOotjde!d4|Nm`lZ8v7z%&+>Y-mRo>eSLrbYbS)JK$Pm?|?b z)NiIEo6-L4HvZuK^}(|1J&&&ZgiLI&-Z5%tpaqV^M^ayvy?PcH{!-lK2s3-IDC0GQ9~j2>y|GneBl}NQEIb!a?ewO zL_fDu6n#J^Yq-}VY3RO7x2P#{_lQc%^NgqHb8Ud)Odx3X9jKVZlb=2JBZg7jkU1bw z4^*toeY;M{kgO{IQ{!jvq)>Xo&?->a1Y}E`dd!lIY?i^k zQ!81W5;_MQENQ$Se@-$O&cM;iHdKNCDd6A}Z|Uo0r7BO*^1UbKDOkB4v=VsQ4rM5?mfPZN-O)Bz#{v7|Y$tE+%uS z3#=~5r99BGQ7nqz>=Xi)UNh)_9IwfqtU1to|873hR+;5)vx?h?N?<2;ScFzCkX^&5 zM(b@syPs3nB~Hi+!LwiP@A}W|Db&|X-v;P{EPDu&tQ2>-InA*c*nz0SubOUXiHzoy zSjRK;C!xuogfXui3g2)NvBB~R7io?hD{SK9+SmUP{klyQ<3F}y=?AfDfkoD7KB2ZH z8g3lYs4c`)HvE$DbsAKQ1OUipw2Uo_oh`CD61fQ+FvlTg8jIXYA)8X&=UnXC6Qh#A zHfs7zf%|!Th=)}484V}!B0_s*&!jrvp;$ZSc?1CH4THH59ek0jCB^Mj_v9T;txYz2z_*^;OznebeVi4%(Ztt1rq`A?~3F`o1?SawyiN3m{AHasZA@S=jL*KUycl`fV z?CM){9W$aD_i@uQ#EyAMt+&TC^+qZm#@B}`P%haD-3**gZeoeY_5#GQ$D1=ahqv`K z$k)!3+-Oevo;OJYgZ!eW*y4TsGMclV1Wen~yPmjE0Jtefbqf_njD;{Wux|%Uwh(pz zd2M32t14g3UfHh9YGeb~;gq$;wv{vVjfH6&B2*2(k#9p*F$1WdqKCWB$y2z6niJVu z`^i}U!~1osWwe3QEc+bAkK9i!HjFCGX-R6#yMw_cZ~DC{1dKi^#Zx*Qb%shGFW{Dx zt9Bh}qY{jqR*u$=krw(~odf;I?ET0oV$Y<}Mg+{0{>A-*`*)?~hf@dOYR*UoSAWjc z^3WG0S^MA7$85|A6#RW|)R%>Q%%)!j?bsgyJ1IN(zpz@UP$y3aT*LQ7=(rv#ijHHg z7*inmO?q2NTq4Gzn!_8og!vOIkJGZQTWu8U7Vr4QHq*|Pya_`qL4atC1&Dmfc1(_1 z02PSJ8_lvmFR4sOq){0fnjlTR1d4XN*qgk_OwSF_By=%6NzN|#=1I%a`N&n)SrD{i zu)_H8oG`t1)F_+#voM>+B<$Ck`TT=@Hbef|idl&#Gh^p>fh~;;+dr8{4Q^EiKL~U# z+M`*b^uu{k3qm@e1$F)y7zaC~O6iI-KN8B1oX6#by~6#&1$06$SnoP)C(A)za-4G%+eD%9d(5AI1qPzT4)2X1u@ZYbpjKpn{5 zn#Qe{t1Vt)eyL*9xu&W4;+|;dy#s++v+7lwWt%%D&#bZlpv`wsn_V-jKbouY z&s>AhLr{Tw@2i*IP3>(*ruO#s$A@!FoEF6rp*jaWY)<)VC$^@Q4VQf1G1#!HsV6v1~uxyyLGd3OgIUkVPPSz}?NwJFa0(P4Wt>$Pj723jRFkYS2*`RW*gtcXonz0`2g|GihohA9^S6)0u zC3w^Y^J(=0vO|!4sUMm=^7m*$umelaDkJ<(33gB3Ds~geEZLkV7rhu-Mb8^P%P9YJN zgspNd%B8TM#!+_j&UG7Gvm|wpy-)z7&tnF)*i74XQd9 zWJ~J2XUxVLN!vjoGp`gL6sYmknlPY!KZr1_`7Z=wfjVT`&Z`Zr3K~D9q;#`*gbw`< zBn(~TGyRrshD);6b621t;pJgj3_ClyqIBU0)||bS!2Uq1*E~qa{I|TVT@Ca&k`h&! zz410uUzqJ1*@BTo4%nQkmdA z>j6@lO^T6f_kd)%3Ya!|N&#dXko~)VW(b~%``1(A@vOwqlgCR+4)P8~^EpO0uCHG^%&0z*Lbwao-It zobwZBVVQCM2W~zJ99fDKnAasOHBjFW^c9Mjw*bXM)qa>W=rHp#S+cI@Z4~J>Uy-Nu zZtRQD1HE5Oj+GBHR)03__}CBBWYJ@wVFz;guT!RwgEn^!uTX6c)76Tpx{*2=$uMA{TGZwy0`9si62j&*8d!n2#28(+v zb_;}$Z}fVwpWf|bzgx^oODi(^_LJ!QgUipfB!-X8p*#Fxx!EE=oBpohzJE5C0Pm7r z)WBGc8}h1LDB{4=#(vdz@{JpA3!nu8+fvH+^ne`W*cx^K5!kKFW773Y$f`2y)^L&x zbBkOZ%?LhKm69_)(gQ%t4h$>n2DM?n-ENdwk$j}hJen?H6*K~oy$;!Dd&@{wI?o}r z?J*Lw6ZUM(dY|$|mDk63JaZ9FremEaEbE?1bqxMD0J` zl%AeT9GZM*TIE-Lp;Y_zIn5;^8F0k|Bx>EhA0@z{g>~HZUv=D`NL(I{oQCqFBG&y! zA+Ah&Gj5^n{M1tLeBjdK`Xb61+r{6oc_Mp8&J$Z4Y{3Dfwv|;%`JGh?@dBQ{ z5L5%RI0;=UfM7OdRaN!BX~KSGtZ@>U;zX%x|&BAt%kTjv@S9Xry)b=q|I3_Q! zGeyEm^tF83_U*?{o#SJkvb;B0Tf3|z7f@1 zr&yA9N9(+p55sEq3m)8vWd|PAHM|XT_}UBz4n;6j>^cF3XO?i;UfWPIiFa?25 z=urJ!?=XJc<^Sp(>aI&a)4977fAr$o)|j+JbY98Q8@=VRtmY)OTF&r_VqD+NFON=? z{n2r!&Ok=S`fM~SFL~I==N z(L|h?pf|0bHpZ6OfCn-hC+K?FB!-ATw7E*U2n77*dKSco-$61;oiYMq-S%_^NPwCcxurld zA*0dpK_#i$>=W%-uhq`t%dywFk9XBKA?G#WU+2YT@8Zc9M0bv}5$FPlfZK9+OM zdt&a)N-uM2^PswI=CsX6(uL&fa*$8+0!=$uQO^Ra6Xlk!Lr8*pagM5jP zI?sO&+VvVyNbE#kF8Qk0zI$|)@;g|Y=%N-ms8!a?X@Slsq;E}Te_)XqLRPKbpT4KN zyDUHJ-OS30TuhOTptzf!p05jZP3r4fBzadv4A#10s9(h%Ph8{q5W&AvRj7-{GnL2P zRqrOx1WvRx9>WDce*rX$*nxf>Kfm&tz2!B%f97Ibm3#AKrD4}-kJUoRPr;Pr zIoRxQmqep#;&~E3@xlJH4M$QQm>xim#L>FFUV zfm7JmB-8m@R3Z>}*mv2qJlJl?M6EkT{cOK4N26Dmj_(w->D&i_)0Jb2Pbo79E4!_^? z)_67_<4m_$;nE@5Z>tD)s&K&_HRj3}RzDHMZKbdH0vilgAgiw<<~cZjwQZF_zu!Z) z&^(lg)o0nWU}UoC4VxLZg44o+To@1{WQt(_s@<`&a=a=Lv*aZ++iK4{xOA`;wJW{AFU;;(GJWYQEmpzfZ^*1qi359NFF%v`91{{XluS zj=vqM2#Xb20y~^!{{MLaBA49;yaz%FL-r`Y>)313 zn%K_O1HJj!(R};2uT6mEFXKK~)k{9=f?L>s=@@ow1So-2tSiOFduG(6NlV%zPT=Km zso^r0IBJ167jFQ8WP}8NV;)FOaOn3jI^p9#yyD7Ey--tC5RKjP3c(Zm5Hfa~OA3pn z;CX#ec$r{i-&H`(qaUr%Vx?Tg;=?W{VAO=S>1Sor8{>u9YGDX-dXHcaHRlz#f^f!0BQy9{9&&YtErDM8BeVt9*)wfNKz z&CkOXJ+$>D`TMt>T#zCFNumTQ8H}8r@AhL}T-LazKKBAtZIYCk>1wG9_9s|<<#oGE zX@Y!C^zXF))!PNNexvH`I3jD|1@ClUb02$8$i$?D4JB zcjn}6P{Hr4)cyoL7|08y?8_Jv(e$l8W%@c@7CUrEUE=afo`KL%fQaJ4#c(UjU)FBB zr*wa%F(b8~w8;0%?UI+5j*HQ-{!GXD8udN7iU?kMp?DKPH0-o@^C_tv%JD+;(h?g7 zY=Oo*bmYYv<^L&+r)v;^FEH_DscnV;Bj0M_Zey>QJ>F`Ajegph6GUGJq|M$dQhgjPj7=Q!gj`4my2$jxnw^FL@u)Oq)cj>#ye zq4c%qQ`(QVKM^}3cR?2$UhidLT(O8Iw8noWg~U7e7c4AM7db6@Z6UO*C5L~Tx~PQq zNALm32TSBq`W3oWbw|mcfXgZaKb&dwV8T%%Z8z)C_Me>-;>qxq4!D%lSXw zNTj-AzXYKvq2@S z!Fkb-J8i^t9OXp75j@P;S6ZL^3_t%hzS?M-zn5?mQ%;~LvpJyq4KvE> z=P_#Z?B9h(I-&vAI!B(8gJQR<%g*laSmx{4R__=7U#}pnr>)9l_CS1h^0LwO<$5{$ zyJ{5wrr@_( zk1tvg73M+)2~8o{I0M0^qwKy z<%9ctKIIRqS{1P6_l@1UPTzWO`{iu&fZG#E+0{mWrsNJu;9A)K8=<~>Pvm0c)*AU& zYCio;zp*5uc0Q%FcFUmH2&RujIVD&5BjaEGQJYtucpR3&Ow=n+3<<_HTZbd#W17^$ z#}6szop;XL*jqlXD}{`MqRyFrijHTeC{Ziomo!oEWX<|iJ(dO&|G!mc@G_`)Gu=$e zcG|BLPqp^>vZmL4@tYH=oc0(OUIaGxzh!0V%o_UKMtI{k6XGafJ|Lt;r;F00$0Z+4 zvR_t=ti7!Aa=DvX3R{zGatp$;#`m;h5Q=e=@)zfp}l~fZ%g~^RP>GqI+xB-p*U0%E$k`ou`Dp7HY!N52Q?+Y_%NMuC2M# zc$BJGNbPnz<}|n-rS15gmZTw#ub6u4$C67zvh+)wILXT=)?$alWBAnM)^iGkv0Gw% zNwm&&g#&|s`=hjmR|&>_7Piy>v#-Un%&ISaVY+r0SgMPphoknt?QTc6*98iN5ba&OT8UhH>d?Jy6aw1R*5L zSy7h+kFHkOR0a(g{3PnKt}TFO7eW6I8kTxnm_+#2pz`ZjM zeX%4=$aI7_3bhg{HG^>mFnJ(vmSg7-Xel%cn>F62}3gQ zofX-z{^)kK-{%WjINs^C&`2o7VA9#W;NprQqH6u1v5*$yb#B@1bsxUKN!OQL$ zL872dW~4UqzyGv?l{epjAFLQjE6K$C!$|48n1Kh7c8Cq^R!=yTjj#0|nnh9H>~{XY zlzzcu+>~txJ&Z(Z3fPg~V~y4hHO5Qb8uzcF6j%o8q$)h`hXqy%rZUJ+hFIFPrhnV3L znPmsTNMdV#U8X;@y`7{K3usPu)GZhZ(BBv;Hj#)$ICW;>DjI!w4#mMPHIfQhPI0j_ zB*lo;V-bC(1aD1dwTK~*@JFkh&fdfTPLZ0CCkV0iW@NsbqG+Y6NG0v1LmwSDw5cq9 zgKH*K9=Xr!UKJn=dpE8%J4BO#0QFExF--bFvz!5~7$E-RYJ9w)UCne?y4i!CeYqRh&>+d>SAy4gAsqM1@HVDyf4VCQlmoMACaovNwgI=YD~qn_a)NE`hQZ za0~!QXVEDkE#QlW!U-pgf_Sy-1W<0pu4b*yV8oyqjIQ%soAC?GWAlo}n;abavTN`e zW<>Dt2iUFaZJb(Z2jhRRFx7Ny7(9in`(LUQ2))KeX&%mfV#%wXfPc{N-eIjq z+KFj4Nkr;fk@;W!&pZ~XsF+Vl-+p>=zI0b`wlcSmAU^D9dTm`pR7(XDS5mgWv+*)(wEYn{aMRr`74^+|mg+94q zB6W~j;5^FT{*;Asf=b}V$|B`W&CwER5cr8IrPDquDfx8e2#{hu*5_(-Pd$P89U~F) zZKc%;UtA7&Dq5!mW}-^DgqzMCJqaAkfuGag=5b;PJCPE%2)(t4PWmG3NnBQ6gISH< ziL*lZHcYvM-_vKvSk3?eeIpL|8!xjZ)_?C>u z{WB^YM_&pA6X3n@F4(Q2?xL%p+#H&e;l+xeT?LvnMCL_EWX*)OJOD{=ZG9{qyvl`T z9(RK=GdR;H4!d=a-jK$Xz?U+f#q2_lGW7MxY)(c;ao;Xz)5UfPc^!HH$1&Q0a~%oc z?XDv@lv6VtAvnEEF>32gEH;br(EyRr8&dxAvTg}06SpHOD|-S>))4V`ElGq7s88v$ zXi5WYavpojdOI|9sH>4WI(4iyuH`un_*cKgzrR|{BQH@Z9i>}2fS zEckWLOmkZU1tlsbRNx59GYIXmp@$oGW(kbtpS73V&x3}}(dt7)Ii!XQumlfT4k}oZ zv@KNpOV%HFkiXZqzea7^;Nq5a&^4Ml@|-$PX?VSe{V2P4m{YFi@M}1t zP7$^&R4S%x8CdbBhPfEVElHUU*obd^g98htJnNykB)LZxP%~?e$$q%Y{<GO|POnx+KX!ejX_a_=9^7xw#X?#3-7N zh>pvHAQNsm6b6mSau)M5b!BFyW-R4B0FvrpB`rg7w5Rx?qq%-5%=RLGw4{OP$N;>} zt-qQd9W^};R|mFz*&OrVCOn9JgTS@EF_@m|k&stV$1WzDsAufduR3rvtGHq~*?vV^ zj^rOw4LrC+%dqt^#-C=me3r5S_O(@e^0$4=`yjLyj=62HKrKNma`ghJidJCb>MPkS z7KD}&KlB3pgDYK^V`P>5pK3TA`!i8x?{2p8j)LY<-l)??EWvR~Mxo>p|;sSONXLWm8=8^Qz@ZL*lf=CjT$@lQ< zt^!u`;;<1Y>*AgP*w;qwE+>NHhnGOl%+RpOv4YP2Jy!b+_E!N!-RhHFPo{=)3GXp_ zvx+!cxQ*+XG}0U(+27s*CfRNLd}Ybh?vFDUu}o+bVE0`O6jS9=5qBrLU-X3dyl{CG zx+T+mutezM@UTK(p;u~~`Kc=nTIbTdzI?x=Ek z!L!R?lychUi+pBOJ7F5y4M=Cs^z_tfOYt65pU)`Z2b)wWDp%kVU2L$lu8ex|Li!wHP(h2aR_^30G6uUEt>0pdDa3( z&?=mrcz*UWhdNucqg@zYn1J1&7AB;<`Q}P(*sUgDOuF?!AveXR|AY#l%XsN%e1;cz zV7KJ4`NX*o5-+4X6PAx_!H8F|1%O$LT|^jgDy}bc0aytv1Gnaji912Lb>veK>u zk7)W+@A!zUf}6+HkxMhn-?&-&q)wZfv&B6*&xBm@-rKx7!nB?!X<6v+R;f7;+0cqyTzT_~n@q zF;v1148P@zC*8uynGd9F_d>8I-+<`@_Pzj{n}wO|!52}5fp=KpIMzG!^mEyJ&^&`= z8&c3ihio{(uE?|FEB&FD_m;H>vYI@WjN~Atw!UDdRQ+zZi`zuhQwOn~ZZJd6k#@}6 z9Xvs_>*K|OEELx=OncsbS?Acx&aRo)k1|SXuLc)vMY|qup{kIn`S~da!J*oRqZd1Q zj9vxFq&J*q7KX8MuI=K1jcUaYIb@dz$7kI7p-ELmtytJ?B0Dl_Gpj1U&;3oP=#X?3j0w{YVrg^Fgu(^_ zKm6M33h9T04R8a-gS$XcQ)3k;4USoi!8We<&o4%IE!z(=V=%q5!|W8ns+GHgberNZ zNByATyB62XT4K_-VTjQQ^rv#9Ax%rb5oyIPd+ov3!U=?GuRO@96=e zGg;C{ty??NDDgmXlppD(LXT$ZJB&m9iTi`R*>GEQi3?^CggPk_7aS`1=eB|LF3~^W z2z~6yB9nc8>cto*?A5c(2>cIgytZsYPrr=8zAaaI2e4cH%;x$As#dE;ngk00?*A@4 zL-^S#>#Y)dZ}DomP1|LHPMiTCNoaG4cWY(LWQN1+Q_bx1bUnz)SBqffclJgp^lZia?EZ9X?M;^I9p$d=u}-j zRz-vg#D;EbqQZ}4GG9;}SGguRkIQ2DDN7ie+0qTXbCgSF=8}Z*KK{;qtnIRMZNy=J zhCS17Y}5I~r(SL6@f>HgPX@A4e+ME&;s7Kyz?dnSHiJblZj@yM18irV#c|z?lOB9k zZ0IFgHvBvx>#I_D*I)QNP~QmL(l=e7?QxbYFAk`zgz4i%Zc0JiL~hFfJsD zQ~r#@|GOT^4LlcNFnWDSnwJHY!*DmCKGSr(IS$dY6sF%HnB4xKqcsoRK(Z92W%r&YC~CO03egq@r?oD9M$A@bY1*! zJonnS`+B8Cb|iQq%0I3kGcrrPYKB@Nk#=Y~El4k{uswwp9^RD;w#iF8u^cMy82Sc zIs~i)vxZl}ch=m`*Qior{_^DRDP&JHtJ$eV!)a8leHi0B0s8gTpQ81As5-|Rrz{g> zOmVSVi1fps6O8r;jK*@3711@C@KZ%x07i~z-`=66e87|I7Us=Y*&)~TIqSH(G6XO0 zInx5S4&t}2F&@ip{GT707|1sX;U;~1o9R~h#`vTw<^#nE%oh!7br zO*Oea%h>^PF%agf2YoJAFiUWrr?X|BCj!>?!X>i+r}?|mp%G!L%sVexGeOwDaU2N+b8@)D}lVa4Z7E$`i)})%lOBLyM**ohtqfsw= zLzxTkbB)(Tu>!Mtg^tZ83=u64bn$A12QZ9~uQ91RXVtKFLl;O0V&Ce`a(uzJa6H2_~j#V-ei=W{41ju>DP@ z+e@W|O?f^OWa#M8iyR{j(CZHmserl^+#`j36H&f==XbJYq4-U4ghzijh};+EqgD69U3f^TrqMMbe~nZjG}dB^*lktq?x zDk2O9eYev-4qq!BJ_Zg^VWxpLy#ZY7W2;g2Y;TEFUp$Rb+77ce7QtNW&YxchJ@shr zya#ac0+*HTJ-uV8$&(X?oPbkeCJ>60Pj#GKM7%H&6tStibgz%@#R@IYf%aKA&9qyu zCON;9J1vG|grXi`;{Xo-CLp>W@JLWAtK$PY1HJQ16s-h54c3~BH!nsCoC5V{q&Udq zWOHI~ZgSaxS?DiU#<=w9;!N<(sTrvg3`v$utf2R4t#O}8N*s`H$T%V>o*l&x0alJY zF+AzjuWtsTs4GqZo~cl2zVve9ItUe@&9Iu0!_xEjm(0uEC?u{rfQuFA6hD*`D0FC| zhw!KmRu`{-JCVBb%};RNCzY)y1kk1vIn|ANgu8wdSyQj}Q~pgMis_%D*E7VSGxlRl z$`at~w;^1g%B~=cf2=^8uj_xydKDeZvWudj;rjjizW^|e`d}52>essSg<%QMp*A4d zxIhTAU+vzm#Ud~}6H$!duyOX_woCb&Yqy!|Gt)+b{X$IN*ets=>T@NM?R2Q9 z-x^_bLy%?m)JquQ0@H$7J$BuMcXCwZ_l&q=@yZxDO&_|q^LFYwEO*fMlvm z({s(MrLWBqdwhWM!lT#S;9%zL6nNq`pQcn3dO|0|?(~&err0K{brSR)Wy+Xs8hYei zD1E}VISbGSWKYFD7nUU1u`NcM8RJJ*X8=l%8#3a3UC-cIfWzi%q=E2aZ1$6VmU00U zHs$?!MK3-5(Z9%oJiz9!EE*&6qqL`xA;M;^zeA=DWeKKAah^k&)LEN^ETb33GTDP0 z{69u?T6F_)F#n?S_qYd4pyhz<<4tJ)zW^f!-1)eT*f|+Ff-Ca=H(+c15MY*wED(`p zSd`msm^L8MWBUWkd({w&%$tsBiAxcGa@9ygN(VBXtVr>A_yk+~&9DcYJy=BXZP<-6 zxQfK>A?v6jP(EZ0B`W;hh^_P6vFp%u?>ev*i`s4;)b@EScVc&7rHITGk(t=JIvTT1 z#En=wrr_f%0UR1~d0lt0cL3*w{Ph;1?Z-BrxJV%8JkAU#5m_J=6x#r^*j)~Lf zaC*Oo>DjeCm!5SZmCKin{v{vgF8PPIKL45@CcBb_kxicBo)V3-sejbJ32#EXNze* zdSU(#M`A+Z9|SFURSgI_mtZGW9ZVqlRRCg_;)<^1lk+Z ze2v7)MrF$ut*UQ%g2{PUUZ(L_qSj+DK`w{(QB`+}NGt4jXn;NCW{Yn0%y^fH$orVq zvoU5y{>2;nICAnBUc%TEaVwSs@H9*)se+{zSOg4J)f^7jUQkO@vP$XH_Qv`RmbpCO zI~j{VNv43TRvPG2%txd#c7yl`6DmgsePL_uXGaej%&m)hKKp&LAthej+?a^eEbZ&^ZhfctfA`w_?YVd5(Aj@;xFl8cQXi zSc;0-*uzk;@8lFLk*ptqlp~wGeTBQh7)j(#J05J{lQOyIf}?wUUiTSJS3#=KBO+IZt_fkjMy zh2`{w#G^|C6=ynP>#aC8SJSZTXEt9;Ree-M#$ykN&AjnX!^;2VNI0yjgGJ<9OaKg& zryGuG!~-c~{w4al$UHvJa?)2s-ob?I#@M|7h}{RnrAma9~?K$$okj{hnm(gYLUhNx;PeJCA4h{(IxgGobGb(!u{M9{o; z4^b@St;Q{wz5Dj_@pnJ-@pnH{u5?K{w`xkgL#tA;Y-uW#EltI;r8%TnQPyqT!rBd+ zShHahYc_1=x0!#r(p3(J!{Goj1AETE3Zn;#dlwh6L!P+0OfROVr zO<}lX!HxtFORU`ov-;#n+@5cOTQHlq?O^nezejI)@Z%ix91e#gKGHDL_gWE|jQJY2 z#Z2|DscJ&zXh#lC0Uq;w#O7lz1YIz{t{ZY8zubQg5xE_>5_5;yhs|q_#I|X~UZC5N z`z+1x0XPz3Ip&lcT5`wXaO6H2SPjdcF`vr%n6Fn)RgIP=$PqhVU_Oys$x3PQ3eZ$l zr$+piBObP5{+ok<6I69fxJJ^V_l=)nPJWL3=gk?z2gRNKz~OK>91h2SgEX=tOdR=7 zxFX9Ahr{7;I2?cpZcbPzIQ$X4iNoP=I2?}uDrs0gnxFG~APzpCF?>+CJW>va!{Kl^ z8~~)v7(OTixD?2dxXzJh{7W2#+#L>w!{KoJH%SASF?`TM;Ldy=%YT{cW(*&+F24ui za5x+ehofMVS*~El@IfyF*OA)tHx7R`0)1x;AM{Cn55(bcI2;Z~!AC-K=C$n8?{KWX zTgUtu(0_<=z!fuw4_cky193PU4u`{WV30#_18CW&UmDO2D}>q_E8JZ>Z-;i!u^3Ce sG8OoJ#_&NC@^?56hr{7;I1V)aA3CSSjdh(B!3HFG!UJo86icy_X9x!n)NrJ90QsB+9+AZi z4BSE>%y{W;-5;Q!Scz*yiE~kEVo7FxoY$gX<^bUYP zldEFcROZ#SSLQZ*{Z=SZS7v5*Rx@)B!3HFG!UJo86icy_X9x!n)NrJ90QsB+9+AZi z4BSE>%y{W;-5;Q!Scz*yiE~kEVo7FxoEaloG4bkTN3LcE9@qOki3N{! z_WbwU)2ed$*R1fsKNNFFv&P+~X| zrCodf@w@Y#tGuq4?opd1`F^6arKx?^{RIryzq~$M{k8t)XYunqiW_ooOD>x-W7$id z;&V%;NF1LS#P!ZE;`&u@%`>0RY|>ew^{}EPN_$n;%#RE+qzeBoXsam#I+4NC)z4*} HQ$iB}eQ$LI literal 0 HcmV?d00001 diff --git a/public/images/header/reg_menu_right.png b/public/images/header/reg_menu_right.png new file mode 100644 index 0000000000000000000000000000000000000000..7c78040c4d61992f83bbeb564dd261479f75c29e GIT binary patch literal 322 zcmeAS@N?(olHy`uVBq!ia0vp^tU#>B!3HFG!UJo86icy_X9x!n)NrJ90QsB+9+AZi z4BSE>%y{W;-5;Q!Scz*yiE~kEVo7FxoEaloG4X1@Ay>15h|B$VZT(`q z6I7?&GkL!Gvh|G{noSCamKZP!2eVD$`I9!o{7>PeExFQ{O&;!5Z)upU@=mIa(fF|U zqe4TsX{iT`1E!@`^Xf-kpL%+s5QFD)l@Ir99?E;viT|H&Utjyo=Dfx8d1trf8g7=A zKR$6;rbbuO6rqJWr?c;W4+(XRy?#WZF!uV%Gai}~pIa)j> Global +--------------------------------------------------------------------------------*/ +.grid_1, +.grid_2, +.grid_3, +.grid_4, +.grid_5, +.grid_6, +.grid_7, +.grid_8, +.grid_9, +.grid_10, +.grid_11, +.grid_12 { + display: inline; + float: left; + margin-left: 10px; + margin-right: 10px; +} + +.push_1, .pull_1, +.push_2, .pull_2, +.push_3, .pull_3, +.push_4, .pull_4, +.push_5, .pull_5, +.push_6, .pull_6, +.push_7, .pull_7, +.push_8, .pull_8, +.push_9, .pull_9, +.push_10, .pull_10, +.push_11, .pull_11 { + position: relative; +} + +/* =Grid >> Children (Alpha ~ First, Omega ~ Last) +--------------------------------------------------------------------------------*/ +.alpha {margin-left: 0;} +.omega {margin-right: 0;} + +/* =Grid >> 12 Columns +--------------------------------------------------------------------------------*/ +.container_12 .grid_1 {width: 65px;} +.container_12 .grid_2 {width: 150px;} +.container_12 .grid_3 {width: 235px;} +.container_12 .grid_4 {width: 320px;} +.container_12 .grid_5 {width: 405px;} +.container_12 .grid_6 {width: 490px;} +.container_12 .grid_7 {width: 575px;} +.container_12 .grid_8 {width: 660px;} +.container_12 .grid_9 {width: 745px;} +.container_12 .grid_10 {width: 830px;} +.container_12 .grid_11 {width: 915px;} +.container_12 .grid_12 {width: 1000px;} + +/* =Prefix Extra Space >> 12 Columns +--------------------------------------------------------------------------------*/ +.container_12 .prefix_1 {padding-left: 85px;} +.container_12 .prefix_2 {padding-left: 170px;} +.container_12 .prefix_3 {padding-left: 255px;} +.container_12 .prefix_4 {padding-left: 340px;} +.container_12 .prefix_5 {padding-left: 425px;} +.container_12 .prefix_6 {padding-left: 510px;} +.container_12 .prefix_7 {padding-left: 595px;} +.container_12 .prefix_8 {padding-left: 680px;} +.container_12 .prefix_9 {padding-left: 765px;} +.container_12 .prefix_10 {padding-left: 850px;} +.container_12 .prefix_11 {padding-left: 935px;} + +/* =Suffix Extra Space >> 12 Columns +--------------------------------------------------------------------------------*/ +.container_12 .suffix_1 {padding-right: 85px;} +.container_12 .suffix_2 {padding-right: 170px;} +.container_12 .suffix_3 {padding-right: 255px;} +.container_12 .suffix_4 {padding-right: 340px;} +.container_12 .suffix_5 {padding-right: 425px;} +.container_12 .suffix_6 {padding-right: 510px;} +.container_12 .suffix_7 {padding-right: 595px;} +.container_12 .suffix_8 {padding-right: 680px;} +.container_12 .suffix_9 {padding-right: 765px;} +.container_12 .suffix_10 {padding-right: 850px;} +.container_12 .suffix_11 {padding-right: 935px;} + +/* `Push Space >> 12 Columns +--------------------------------------------------------------------------------*/ +.container_12 .push_1 {left: 85px;} +.container_12 .push_2 {left: 170px;} +.container_12 .push_3 {left: 255px;} +.container_12 .push_4 {left: 340px;} +.container_12 .push_5 {left: 425px;} +.container_12 .push_6 {left: 510px;} +.container_12 .push_7 {left: 595px;} +.container_12 .push_8 {left: 680px;} +.container_12 .push_9 {left: 765px;} +.container_12 .push_10 {left: 850px;} +.container_12 .push_11 {left: 935px;} + +/* `Pull Space >> 12 Columns +--------------------------------------------------------------------------------*/ +.container_12 .pull_1 {left: -85px;} +.container_12 .pull_2 {left: -170px;} +.container_12 .pull_3 {left: -255px;} +.container_12 .pull_4 {left: -340px;} +.container_12 .pull_5 {left: -425px;} +.container_12 .pull_6 {left: -510px;} +.container_12 .pull_7 {left: -595px;} +.container_12 .pull_8 {left: -680px;} +.container_12 .pull_9 {left: -765px;} +.container_12 .pull_10 {left: -850px;} +.container_12 .pull_11 {left: -935px;} + +/* `Clear Floated Elements +----------------------------------------------------------------------------------------------------*/ +/* http://sonspring.com/journal/clearing-floats */ +.clear { + clear: both; + display: block; + overflow: hidden; + visibility: hidden; + width: 0; + height: 0; +} + +/* http://www.yuiblog.com/blog/2010/09/27/clearfix-reloaded-overflowhidden-demystified */ +.clearfix:before, +.clearfix:after { + content: "\0020"; + display: block; + overflow: hidden; + visibility: hidden; + width: 0; + height: 0; +} +.clearfix:after { + clear: both; +} +/* +The following zoom:1 rule is specifically for IE6 + IE7. +Move to separate stylesheet if invalid CSS is a problem. +*/ +.clearfix { + zoom: 1; +} diff --git a/public/stylesheets/reset.css b/public/stylesheets/reset.css new file mode 100644 index 0000000..13f8e0a --- /dev/null +++ b/public/stylesheets/reset.css @@ -0,0 +1,53 @@ +/* http://meyerweb.com/eric/tools/css/reset/ */ +/* v1.0 | 20080212 */ + +html, body, div, span, applet, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +a, abbr, acronym, address, big, cite, code, +del, dfn, em, font, img, ins, kbd, q, s, samp, +small, strike, strong, sub, sup, tt, var, +b, u, i, center, +dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td { + margin: 0; + padding: 0; + border: 0; + outline: 0; + font-size: 100%; + vertical-align: baseline; + background: transparent; +} +body { + line-height: 1; +} +ol, ul { + list-style: none; +} +blockquote, q { + quotes: none; +} +blockquote:before, blockquote:after, +q:before, q:after { + content: ''; + content: none; +} + +/* remember to define focus styles! */ +:focus { + outline: 0; +} + +/* remember to highlight inserts somehow! */ +ins { + text-decoration: none; +} +del { + text-decoration: line-through; +} + +/* tables still need 'cellspacing="0"' in the markup */ +table { + border-collapse: collapse; + border-spacing: 0; +} \ No newline at end of file diff --git a/public/stylesheets/scaffold.css b/public/stylesheets/scaffold.css index 1ae7000..31ada1a 100644 --- a/public/stylesheets/scaffold.css +++ b/public/stylesheets/scaffold.css @@ -1,10 +1,3 @@ -body { background-color: #fff; color: #333; } - -body, p, ol, ul, td { - font-family: verdana, arial, helvetica, sans-serif; - font-size: 13px; - line-height: 18px; -} pre { background-color: #eee; @@ -12,10 +5,6 @@ pre { font-size: 11px; } -a { color: #000; } -a:visited { color: #666; } -a:hover { color: #fff; background-color:#000; } - div.field, div.actions { margin-bottom: 10px; } diff --git a/public/stylesheets/style.css b/public/stylesheets/style.css new file mode 100644 index 0000000..3f4a04d --- /dev/null +++ b/public/stylesheets/style.css @@ -0,0 +1,241 @@ +h1,h2,h3 { + font-weight:normal; +} +h1 { + text-transform:uppercase; + font-size:24px; + letter-spacing:-2px; + color:#88a5b4; +} +h6 { + border-bottom:1px solid #EEE; + padding-bottom:10px; +} +#header { + display:block; + background:none repeat scroll 0 0 #F2F2F2; + border: 1px solid #C2C2C2; + margin-bottom:10px; +} +#headerLink { + background:url('/images/header/header.png') no-repeat; + height:100px; + width:700px; + display:block; + margin:20px 0; +} +#headerLink { + text-indent:-9999em; +} +#nav_region { + clear:both; + height:38px; + background: ; + position:relative; + margin-bottom:20px; + background:url('/images/header/reg_menu_centre.png') repeat-x; +} +#nav_region a { + text-decoration:none; +} +#nav_region a:hover { + color:#cd282f; +} +#nav_region_centre { + height:38px; + padding:0 10px; + float:left; +} +#nav_region_centre li { + list-style:none; +} +#nav_region_left { + background:url('/images/header/reg_menu_left.png') no-repeat; + float:left; + width:5px; + height:38px; + position:relative; +} +#nav_region_right { + background:url('/images/header/reg_menu_right.png') no-repeat; + float:right; + width:5px; + height:38px; + position:relative; + top:0; +} +#nav_region_centre .nav_tier1{ + margin:0; + padding:0; + list-style:none; +} +#nav_region_centre .nav_tier1>li{ + display:inline; + margin:0; + float:left; + margin-left:10px; + position:relative; +} +#nav_region_centre .nav_tier1>li a { + color:#333; + font-size:18px; + padding-top:5px; + padding-left:10px; + padding-right:10px; + display:block; +} +#nav_region_centre .nav_tier1>li a:hover { + color:#cd282f; +} +li.first { + padding-top:10px; +} +.nav_tier1 ul li { + padding-left:10px; +} +ul.nav_tier1 { + list-style: none outside none; + margin: 0; + padding: 0; + display:inline; +} +ul.nav_tier1 li { + position: relative; + margin:0; +} +li ul { + display: none; + left: 0; + position: absolute; + top: 37px; + background:url('/images/header/dropdown.png'); + width:182px; + -moz-box-shadow:0 1px 0px #999; + -webkit-box-shadow:0 1px 1px #999; + -o-box-shadow:0 1px 1px #999; + box-shadow:0 1px 0px #999; + -moz-border-radius:6px; + -webkit-border-radius:6px; + -o-border-radius:6px; +} +ul li a { + -moz-border-bottom-colors: none; + -moz-border-image: none; + -moz-border-left-colors: none; + -moz-border-right-colors: none; + -moz-border-top-colors: none; + color: #777777; + display: block; + padding: 5px; + font-size:18px; + text-decoration: none; +} +* html ul li { + float: left; + height: 1%; +} +* html ul li a { + height: 1%; +} +ul li a:hover { +} +li ul li a { + padding: 2px 5px; +} +li:hover ul, li.over ul { + display: block; +} +#nav_region_centre .nav_tier1>li span { + +} +#nav_region_centre .nav_tier2 { + overflow:visible !important; + position:absolute; + display:none; + top:0; + left:0; +} +#nav_region_centre .nav_tier2>li{ + margin:0; +} +#nav_region_centre .nav_tier2:hover{ + display:block; +} +#content { + clear:both; +} +.timestamp { + color:#999; +} +ul.pagenumbers { + text-align: center; +} +.pagenumbers li{ + list-style:none; + display:inline-block; +} +.pagenumbers .disabled { + padding: 5px; + font-size:18px; +} +.pagenumbers .current { + padding: 5px; + font-weight:bold; + font-size:18px; +} +.pagenumbers a:hover { + color:#cd282f; +} +#calendar { + border:1px solid #c2c2c2; +} +#sponsorsTable { + border:1px solid #c2c2c2; +} +#sponsorsColumnTable td { + border:1px solid #c2c2c2; + text-align:center; + vertical-align:middle; + padding: 5px; +} +#sponsorsPageTable { + width: 100%; +} +#sponsorsPageTable tr { + border-bottom:1px solid #c2c2c2; + border-top:1px solid #c2c2c2; +} +#sponsorsPageTable td { + padding-left:10px; + padding-right:10px; + padding-top:20px; + padding-bottom:20px; +} +#sponsorsPageTable td.sponsorImage { + text-align:center; + vertical-align:middle; +} +#sponsorsPageTable td.sponsorDescription .sponsorName { + font-weight:bold; +} +#footerText { + text-align:center; + padding-top:20px; +} +.news-title { + color:#2f586d; +} +.news a { + color:#2f58cd; +} +.news a:hover { + color:#cf582d; +} + +.news .itemTitle { + width: 100%; + border-top: 1px solid #c2c2c2; + border-bottom: 1px solid #c2c2c2; + padding-top: 5px; + padding-bottom: 5px; +} diff --git a/public/stylesheets/text.css b/public/stylesheets/text.css new file mode 100644 index 0000000..dd03ad2 --- /dev/null +++ b/public/stylesheets/text.css @@ -0,0 +1,77 @@ +/* `Basic HTML +----------------------------------------------------------------------------------------------------*/ + +body { + font: 13px/1.5 'Helvetica Neue', Arial, 'Liberation Sans', FreeSans, sans-serif; +} + +a:focus { + outline: 1px dotted; +} + +hr { + border: 0 #ccc solid; + border-top-width: 1px; + clear: both; + height: 0; +} + +/* `Headings +----------------------------------------------------------------------------------------------------*/ + +h1 { + font-size: 25px; +} + +h2 { + font-size: 23px; +} + +h3 { + font-size: 21px; +} + +h4 { + font-size: 19px; +} + +h5 { + font-size: 17px; +} + +h6 { + font-size: 15px; +} + +/* `Spacing +----------------------------------------------------------------------------------------------------*/ + +ol { + list-style: decimal; +} + +ul { + list-style: disc; +} + +li { + margin-left: 30px; +} + +p, +dl, +hr, +h1, +h2, +h3, +h4, +h5, +h6, +ol, +ul, +pre, +table, +address, +fieldset { + margin-bottom: 20px; +} \ No newline at end of file From ab6ac3aa205b3d16d4b3a425a7c13435e89c6de5 Mon Sep 17 00:00:00 2001 From: Maxwell Swadling Date: Sat, 7 May 2011 22:42:44 +1000 Subject: [PATCH 13/55] Added full nav bar. Signed-off-by: Maxwell Swadling --- app/helpers/application_helper.rb | 8 +++++++ app/views/shared/_header.html.erb | 37 +++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index de6be79..018db74 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1,2 +1,10 @@ module ApplicationHelper + # Link's to static content that may not exist + def static_content_path(name) + begin + return Static.find(name) + rescue + return root_path + end + end end diff --git a/app/views/shared/_header.html.erb b/app/views/shared/_header.html.erb index 0eb71bf..19d7a96 100644 --- a/app/views/shared/_header.html.erb +++ b/app/views/shared/_header.html.erb @@ -4,7 +4,44 @@ From e4f4a16cc3d05c3e65532c2945f77e92345184ae Mon Sep 17 00:00:00 2001 From: Maxwell Swadling Date: Sat, 7 May 2011 23:18:39 +1000 Subject: [PATCH 14/55] Added 3 column layout. Signed-off-by: Maxwell Swadling --- app/controllers/application_controller.rb | 6 ++++ app/helpers/application_helper.rb | 8 +++++ app/views/layouts/application.html.erb | 5 ++- app/views/news_items/index.html.erb | 40 +++++++++++----------- app/views/shared/_calendar.html.erb | 5 +++ app/views/shared/_sponsors.html.erb | 7 ++++ app/views/shared/_three_columns.html.erb | 13 +++++++ public/icon.png | Bin 0 -> 903 bytes 8 files changed, 63 insertions(+), 21 deletions(-) create mode 100644 app/views/shared/_calendar.html.erb create mode 100644 app/views/shared/_sponsors.html.erb create mode 100644 app/views/shared/_three_columns.html.erb create mode 100644 public/icon.png diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index e8065d9..31e600e 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,3 +1,9 @@ class ApplicationController < ActionController::Base protect_from_forgery + before_filter :load_sponsors + + private + def load_sponsors + @sponsors = Sponsor.visible + end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 018db74..de2034a 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1,4 +1,11 @@ module ApplicationHelper + + # A page's title + def page_title(y) + y = "#{y} - " unless y.empty? + "#{y}CSESoc - UNSW Computer Science and Engineering Society" + end + # Link's to static content that may not exist def static_content_path(name) begin @@ -7,4 +14,5 @@ def static_content_path(name) return root_path end end + end diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 9c02f50..7d67305 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -1,11 +1,14 @@ - CSESocWebsite + <%= page_title(yield :title) %> <%= stylesheet_link_tag :all %> <%= javascript_include_tag :defaults %> + <%= csrf_meta_tag %> + <%= yield :head %> + <%= render 'shared/analytics' %> diff --git a/app/views/news_items/index.html.erb b/app/views/news_items/index.html.erb index 8d7c27b..2fdaad6 100644 --- a/app/views/news_items/index.html.erb +++ b/app/views/news_items/index.html.erb @@ -1,21 +1,21 @@ -

    Listing news_items

    +<%= render :layout => 'shared/three_columns' do %> +

    Listing news_items

    + + + + + + + -
    TitleContentPublish dateAuthor
    - - - - - - - -<% @news_items.each do |news_item| %> - - - - - - -<% end %> -
    TitleContentPublish dateAuthor
    <%= news_item.title %><%= news_item.content %><%= news_item.publish_date %><%= news_item.author %>
    - -
    + <% @news_items.each do |news_item| %> + + <%= news_item.title %> + <%= news_item.content %> + <%= news_item.publish_date %> + <%= news_item.author %> + + <% end %> + +
    +<% end %> \ No newline at end of file diff --git a/app/views/shared/_calendar.html.erb b/app/views/shared/_calendar.html.erb new file mode 100644 index 0000000..65301c3 --- /dev/null +++ b/app/views/shared/_calendar.html.erb @@ -0,0 +1,5 @@ +

    Calendar

    + +
    + +
    \ No newline at end of file diff --git a/app/views/shared/_sponsors.html.erb b/app/views/shared/_sponsors.html.erb new file mode 100644 index 0000000..8455718 --- /dev/null +++ b/app/views/shared/_sponsors.html.erb @@ -0,0 +1,7 @@ +

    Our sponsors

    + + +<% @sponsors.each do |sponsor| %> + +<% end %> +
    <%= sponsor.name %>
    \ No newline at end of file diff --git a/app/views/shared/_three_columns.html.erb b/app/views/shared/_three_columns.html.erb new file mode 100644 index 0000000..03e4af0 --- /dev/null +++ b/app/views/shared/_three_columns.html.erb @@ -0,0 +1,13 @@ +
    + <%= render 'shared/calendar' %> +
    + +
    + <%= yield %> +
    + +
    + <%= render 'shared/sponsors' %> +
    + + \ No newline at end of file diff --git a/public/icon.png b/public/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..a763b022027d24691faec9f9bc6eca62a6d5c6bc GIT binary patch literal 903 zcmV;219<$2P)Px#24YJ`L;wH)0002_L%V+f000SaNLh0L01f;A01f;BSu>O500007bV*G`2ipY! z1~dSB)*&1K00Rd}L_t(I%RQ5AXq9Cg#(&p+&hwm?ZLV#`aXD*djnOtY1}zA(7nFz$ z3$cxstPk}e+diyFf~3eOf`Wt+KlB0>f(nL(N%mpAX@N@8sMw066FN<|b?ltY^Y%Rd zK0If;Kiu43uK#skFMq8c8XFh9g_$$S1Ylr7!VJI+0p^@2EEWM&pH7BhXydU=*T6|h z&Lo4{4ImH#gd_k8i$$J#VszZp#-|fM;N(ov30(9^B9)PRf-ocuBk*Fe zM1Jua*GeT`|KJd>KK(dNE9$6J0{~*Hx(8l1KoVv~uCPFTDoHg6Lo6QS?*1OWJ~OFg!ZWQO95qQ+Dp%Cp*kHS!9Hg&ft-e3|8!zttm<|2i0E}$D zk5t09*{gZp{OGVQ&E@dCc;Emll`CXguCp)Sd4=461tv~S+QjLf>1=Pa?GN5-8}I02 z#~b@>U0W-A4<2P^cHVq1jxd}g%9ScRN4E0CiPKDF&r__FDJ&Mbv$vBy`#nV+WqWJP_Nr0=oZEk)6*cWBf=76hSU zF=wCbewLnF+xb0vL2a3pw)?#U960{1npZSfXoi^~)fNY&-ode;W~NoDRh7zB>uhgh zb*6<=zf6-~C{mwJE&-R?ZiY#y63g@qt$H< dGD5*~{{tnnSNA-%t_J`B002ovPDHLkV1nCjqt5^U literal 0 HcmV?d00001 From 9467b07bb72e804c9f604f9506066db0ded36927 Mon Sep 17 00:00:00 2001 From: Maxwell Swadling Date: Sun, 8 May 2011 01:31:45 +1000 Subject: [PATCH 15/55] Added markdown to news posts and Capistrano. Signed-off-by: Maxwell Swadling --- Gemfile | 7 +++--- Gemfile.lock | 17 ++++++++++++++ app/views/news_items/index.html.erb | 35 ++++++++++++++++------------- 3 files changed, 39 insertions(+), 20 deletions(-) diff --git a/Gemfile b/Gemfile index 48371ed..bc95f61 100644 --- a/Gemfile +++ b/Gemfile @@ -2,19 +2,18 @@ source 'http://rubygems.org' gem 'rails', '3.0.7' -# Bundle edge Rails instead: -# gem 'rails', :git => 'git://github.com/rails/rails.git' - gem 'sqlite3' gem 'friendly_id' gem 'paperclip' +gem 'rdiscount' +gem 'capistrano' # Use unicorn as the web server # gem 'unicorn' # Deploy with Capistrano -# gem 'capistrano' + # To use debugger (ruby-debug for Ruby 1.8.7+, ruby-debug19 for Ruby 1.9.2+) # gem 'ruby-debug' diff --git a/Gemfile.lock b/Gemfile.lock index 9653f91..8f24286 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -31,10 +31,17 @@ GEM arel (2.0.9) babosa (0.3.3) builder (2.1.2) + capistrano (2.5.21) + highline + net-scp (>= 1.0.0) + net-sftp (>= 2.0.0) + net-ssh (>= 2.0.14) + net-ssh-gateway (>= 1.0.0) erubis (2.6.6) abstract (>= 1.0.0) friendly_id (3.2.1.1) babosa (~> 0.3.0) + highline (1.6.1) i18n (0.5.0) mail (2.2.19) activesupport (>= 2.3.6) @@ -42,6 +49,13 @@ GEM mime-types (~> 1.16) treetop (~> 1.4.8) mime-types (1.16) + net-scp (1.0.4) + net-ssh (>= 1.99.1) + net-sftp (2.0.5) + net-ssh (>= 2.0.9) + net-ssh (2.1.4) + net-ssh-gateway (1.0.1) + net-ssh (>= 1.99.1) paperclip (2.3.11) activerecord (>= 2.3.0) activesupport (>= 2.3.2) @@ -65,6 +79,7 @@ GEM rake (>= 0.8.7) thor (~> 0.14.4) rake (0.8.7) + rdiscount (1.6.8) sqlite3 (1.3.3) thor (0.14.6) treetop (1.4.9) @@ -75,7 +90,9 @@ PLATFORMS ruby DEPENDENCIES + capistrano friendly_id paperclip rails (= 3.0.7) + rdiscount sqlite3 diff --git a/app/views/news_items/index.html.erb b/app/views/news_items/index.html.erb index 2fdaad6..441ed5e 100644 --- a/app/views/news_items/index.html.erb +++ b/app/views/news_items/index.html.erb @@ -1,21 +1,24 @@ <%= render :layout => 'shared/three_columns' do %> -

    Listing news_items

    - - - - - - - +

    News

    <% @news_items.each do |news_item| %> - - - - - - +
    +
    +
    + <%= news_item.publish_date.day %> + <%= news_item.publish_date.month %> + <%= news_item.publish_date.year %> + @ <%= news_item.publish_date.strftime("%H:%M") %> + by <%#= news_item.author.name %> +
    +
    +
    +

    <%= news_item.title %>

    +
    + <%=h RDiscount.new(news_item.content).to_html %> +
    +
    +
    <% end %> -
    TitleContentPublish dateAuthor
    <%= news_item.title %><%= news_item.content %><%= news_item.publish_date %><%= news_item.author %>
    -
    + <% end %> \ No newline at end of file From 5b50719588009206bd208265499375ab1391c589 Mon Sep 17 00:00:00 2001 From: Maxwell Swadling Date: Sun, 8 May 2011 01:32:21 +1000 Subject: [PATCH 16/55] Fixed formatting. Signed-off-by: Maxwell Swadling --- app/views/events/show.html.erb | 2 +- app/views/layouts/application.html.erb | 1 + app/views/news_items/_news_item.html.erb | 17 ++++++++++ app/views/news_items/index.html.erb | 18 +---------- app/views/news_items/show.html.erb | 22 +------------ app/views/shared/_header.html.erb | 2 +- app/views/shared/_sponsors.html.erb | 6 ++-- app/views/sponsors/_sponsor.html.erb | 9 ++++++ app/views/sponsors/index.html.erb | 40 ++++-------------------- public/stylesheets/style.css | 3 ++ 10 files changed, 43 insertions(+), 77 deletions(-) create mode 100644 app/views/news_items/_news_item.html.erb create mode 100644 app/views/sponsors/_sponsor.html.erb diff --git a/app/views/events/show.html.erb b/app/views/events/show.html.erb index 6005414..7949248 100644 --- a/app/views/events/show.html.erb +++ b/app/views/events/show.html.erb @@ -1,4 +1,4 @@ -

    <%= notice %>

    +

    Name: diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 7d67305..3d56e86 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -19,6 +19,7 @@

    +

    <%= notice %>

    <%= yield %>
    diff --git a/app/views/news_items/_news_item.html.erb b/app/views/news_items/_news_item.html.erb new file mode 100644 index 0000000..81a53c7 --- /dev/null +++ b/app/views/news_items/_news_item.html.erb @@ -0,0 +1,17 @@ +
    +
    +
    + <%= news_item.publish_date.day %> + <%= news_item.publish_date.month %> + <%= news_item.publish_date.year %> + @ <%= news_item.publish_date.strftime("%H:%M") %> + by <%#= news_item.author.name %> +
    +
    +
    +

    <%= link_to_unless defined?(no_link), news_item.title, news_item %>

    +
    + <%= RDiscount.new(news_item.content).to_html.html_safe %> +
    +
    +
    \ No newline at end of file diff --git a/app/views/news_items/index.html.erb b/app/views/news_items/index.html.erb index 441ed5e..1173b2d 100644 --- a/app/views/news_items/index.html.erb +++ b/app/views/news_items/index.html.erb @@ -2,23 +2,7 @@

    News

    <% @news_items.each do |news_item| %> -
    -
    -
    - <%= news_item.publish_date.day %> - <%= news_item.publish_date.month %> - <%= news_item.publish_date.year %> - @ <%= news_item.publish_date.strftime("%H:%M") %> - by <%#= news_item.author.name %> -
    -
    -
    -

    <%= news_item.title %>

    -
    - <%=h RDiscount.new(news_item.content).to_html %> -
    -
    -
    + <%= render news_item %> <% end %> <% end %> \ No newline at end of file diff --git a/app/views/news_items/show.html.erb b/app/views/news_items/show.html.erb index 6edc662..d2316ec 100644 --- a/app/views/news_items/show.html.erb +++ b/app/views/news_items/show.html.erb @@ -1,25 +1,5 @@ -

    <%= notice %>

    -

    - Title: - <%= @news_item.title %> -

    -

    - Content: - <%= @news_item.content %> -

    +<%= render @news_item, :no_link => true %> -

    - Publish date: - <%= @news_item.publish_date %> -

    - -

    - Author: - <%= @news_item.author %> -

    - - -<%= link_to 'Edit', edit_news_item_path(@news_item) %> | <%= link_to 'Back', news_items_path %> diff --git a/app/views/shared/_header.html.erb b/app/views/shared/_header.html.erb index 19d7a96..77e7c7b 100644 --- a/app/views/shared/_header.html.erb +++ b/app/views/shared/_header.html.erb @@ -7,7 +7,7 @@
  • <%= link_to "Home", root_path %>
  • -
  • <%= link_to "About", "google.com" %> +
  • <%= link_to "About", static_content_path("about") %>
    • <%= link_to "History", static_content_path("history") %>
    • <%= link_to "Constitution", static_content_path("constitution") %>
    • diff --git a/app/views/shared/_sponsors.html.erb b/app/views/shared/_sponsors.html.erb index 8455718..2afef86 100644 --- a/app/views/shared/_sponsors.html.erb +++ b/app/views/shared/_sponsors.html.erb @@ -1,7 +1,7 @@

      Our sponsors

      -<% @sponsors.each do |sponsor| %> - -<% end %> + <% @sponsors.each do |sponsor| %> + + <% end %>
      <%= sponsor.name %>
      <%= sponsor.name %>
      \ No newline at end of file diff --git a/app/views/sponsors/_sponsor.html.erb b/app/views/sponsors/_sponsor.html.erb new file mode 100644 index 0000000..7ff79c1 --- /dev/null +++ b/app/views/sponsors/_sponsor.html.erb @@ -0,0 +1,9 @@ + + + <%= link_to image_tag(sponsor.image_path.url, :alt => sponsor.name), sponsor.website %> + + + <%= link_to sponsor.name, sponsor.website %>
      + <%= sponsor.description %> + + diff --git a/app/views/sponsors/index.html.erb b/app/views/sponsors/index.html.erb index ca16f63..9ff0500 100644 --- a/app/views/sponsors/index.html.erb +++ b/app/views/sponsors/index.html.erb @@ -1,37 +1,9 @@ -

      Listing sponsors

      +

      Our Sponsors

      - - - - - - - - - - - - - - +
      NameDescriptionWebsiteAlt textAmount paidStart dateExpiry dateHtml override
      + + <% @sponsors.each do |sponsor| %> + <%= render 'sponsor' %> + <% end %> -<% @sponsors.each do |sponsor| %> - - - - - - - - - - - - - -<% end %>
      <%= sponsor.name %><%= sponsor.description %><%= sponsor.website %><%= sponsor.alt_text %><%= sponsor.amount_paid %><%= sponsor.start_date %><%= sponsor.expiry_date %><%= sponsor.html_override %><%= link_to 'Show', sponsor %><%= link_to 'Edit', edit_sponsor_path(sponsor) %><%= link_to 'Destroy', sponsor, :confirm => 'Are you sure?', :method => :delete %>
      - -
      - -<%= link_to 'New Sponsor', new_sponsor_path %> diff --git a/public/stylesheets/style.css b/public/stylesheets/style.css index 3f4a04d..898c38d 100644 --- a/public/stylesheets/style.css +++ b/public/stylesheets/style.css @@ -225,6 +225,9 @@ ul.pagenumbers { .news-title { color:#2f586d; } +.news-title h2 a { + color:#2f586d; +} .news a { color:#2f58cd; } From 117424be3196381e35921241cd0e24c93936225a Mon Sep 17 00:00:00 2001 From: Maxwell Swadling Date: Sun, 8 May 2011 01:32:38 +1000 Subject: [PATCH 17/55] Added static content seeds. Signed-off-by: Maxwell Swadling --- app/helpers/application_helper.rb | 6 +----- app/models/static.rb | 4 ++-- db/migrate/20110507094645_create_statics.rb | 2 +- db/schema.rb | 6 +++++- db/seeds.rb | 18 ++++++++++++++++++ 5 files changed, 27 insertions(+), 9 deletions(-) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index de2034a..d41bbc2 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -8,11 +8,7 @@ def page_title(y) # Link's to static content that may not exist def static_content_path(name) - begin - return Static.find(name) - rescue - return root_path - end + Static.find(name) end end diff --git a/app/models/static.rb b/app/models/static.rb index ac9c895..6003782 100644 --- a/app/models/static.rb +++ b/app/models/static.rb @@ -1,7 +1,7 @@ class Static < ActiveRecord::Base - has_friendly_id :slug, :use_slug => true + has_friendly_id :slug_name, :use_slug => true validates :title, :presence => true validates :content, :presence => true - validates :slug, :presence => true + validates :slug_name, :presence => true end diff --git a/db/migrate/20110507094645_create_statics.rb b/db/migrate/20110507094645_create_statics.rb index e289506..b87950f 100644 --- a/db/migrate/20110507094645_create_statics.rb +++ b/db/migrate/20110507094645_create_statics.rb @@ -3,7 +3,7 @@ def self.up create_table :statics do |t| t.string :title t.text :content - t.string :slug + t.string :slug_name t.timestamps end diff --git a/db/schema.rb b/db/schema.rb index ff8bc1e..5d1b79e 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -65,6 +65,10 @@ t.datetime "start_date" t.datetime "expiry_date" t.text "html_override" + t.string "image_file_name" + t.string "image_content_type" + t.integer "image_file_size" + t.datetime "image_updated_at" t.datetime "created_at" t.datetime "updated_at" end @@ -72,7 +76,7 @@ create_table "statics", :force => true do |t| t.string "title" t.text "content" - t.string "slug" + t.string "slug_name" t.datetime "created_at" t.datetime "updated_at" end diff --git a/db/seeds.rb b/db/seeds.rb index 664d8c7..070d0df 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -5,3 +5,21 @@ # # cities = City.create([{ :name => 'Chicago' }, { :name => 'Copenhagen' }]) # Mayor.create(:name => 'Daley', :city => cities.first) + +Static.create :title => "About CSESoc", :content => "About CSESoc.", :slug_name => "about" +Static.create :title => "A Brief History of CSESoc", :content => "History of CSESoc.", :slug_name => "history" +Static.create :title => "Constitution", :content => "CSESoc Constitution.", :slug_name => "constitution" +Static.create :title => "CSESoc Exec & Heads", :content => "Exec & Heads", :slug_name => "execheads" +Static.create :title => "Working Groups", :content => "Working groups.", :slug_name => "workinggroups" +Static.create :title => "Beta", :content => "Beta.", :slug_name => "workinggroupsbeta" +Static.create :title => "Publicity", :content => "Publicity.", :slug_name => "workinggroupspublicity" +Static.create :title => "Social", :content => "Social.", :slug_name => "workinggroupssocial" +Static.create :title => "Sysadmin", :content => "Sysadmin.", :slug_name => "workinggroupssysadmin" +Static.create :title => "Tech", :content => "Tech.", :slug_name => "workinggroupstech" +Static.create :title => "Volunteer!", :content => "Volunteer.", :slug_name => "workinggroupsvolunteer" +Static.create :title => "Students", :content => "Students.", :slug_name => "students" +Static.create :title => "Murder", :content => "Murder.", :slug_name => "murder" +Static.create :title => "Bling", :content => "Bling.", :slug_name => "bling" +Static.create :title => "Games", :content => "Games.", :slug_name => "games" +Static.create :title => "IRC", :content => "Irc.", :slug_name => "irc" +Static.create :title => "Contact Us", :content => "Contact us.", :slug_name => "contact" \ No newline at end of file From 6f65e3f3713f0832f2c1032222774180090381da Mon Sep 17 00:00:00 2001 From: Maxwell Swadling Date: Sun, 8 May 2011 01:37:35 +1000 Subject: [PATCH 18/55] Static pages are now markdown. Signed-off-by: Maxwell Swadling --- app/views/statics/show.html.erb | 21 ++------------------- app/views/suggestions/show.html.erb | 2 +- 2 files changed, 3 insertions(+), 20 deletions(-) diff --git a/app/views/statics/show.html.erb b/app/views/statics/show.html.erb index cd07600..829a64e 100644 --- a/app/views/statics/show.html.erb +++ b/app/views/statics/show.html.erb @@ -1,20 +1,3 @@ -

      <%= notice %>

      +

      <%= @static.title %>

      -

      - Title: - <%= @static.title %> -

      - -

      - Content: - <%= @static.content %> -

      - -

      - Slug: - <%= @static.slug %> -

      - - -<%= link_to 'Edit', edit_static_path(@static) %> | -<%= link_to 'Back', statics_path %> +<%= RDiscount.new(@static.content).to_html.html_safe %> diff --git a/app/views/suggestions/show.html.erb b/app/views/suggestions/show.html.erb index 4412dcd..a2a802b 100644 --- a/app/views/suggestions/show.html.erb +++ b/app/views/suggestions/show.html.erb @@ -1,4 +1,4 @@ -

      <%= notice %>

      +

      Subject: From 23496fe79686086629ac4568b2c7554e4f8b6e37 Mon Sep 17 00:00:00 2001 From: Ritwik Roy Date: Sun, 8 May 2011 01:44:13 +1000 Subject: [PATCH 19/55] Added favicon.ico as a conversion from icon.png --- public/favicon.ico | Bin 0 -> 1150 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/public/favicon.ico b/public/favicon.ico index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..e6555d048f9cde0fa391615fdf51619aa1966580 100644 GIT binary patch literal 1150 zcmbtTTT4_?6h1;fz$d8}vztwM%ZFZi$%qOXP4kkP%)^*7<7LLlFv|i_%x+ZRWMHKk zrDH=E(#grB7&QmVrVKU75V8_0L%)*jZJz1QCB+uvI2+lmrGztB*H zMud{TOi{vUW*}o%1X+muD@p?G{XOhiu_cbIxH)@fEZAs{qJ1$aNZ%-PUW;Gbqx0e? zAH~Eu9hq@+e1?r*xn`FI!p`DYpiZg51d_@`2g7N;{Ab3oSkK*^Q zAGq#10<&=?inHVOd*$m`UUd3e#+0Z-!z%+JrG;ld%)}+l!I+=B#v6PBJlVQ+FM=l`MM|!v|4zmb&Qqx{4@i;e*S=_+A@msV&run z#zunZYHLKduSIarlxJb;%SV)$HdA~Iv_42YV{*Pdovmo5J09I{z`5f&@LW5I>XKx% zxoc%T{HhnH%F?O7(P}NUzUQYJB;TLkXNa>K{*GqqpAA*cL<|P~gzH9OMigegO{0=} z%e7Q{sOpC2jB(h!1;Kz1(^Hdp+T{V)rM4>yE!> zW4QFXFg*&+{qe-*CdS8x;J#dmob6$_dj23DcM_kou_zB_n9gNX&R(yH9uYR^ku#S&$_V4UCv1TbuH@mFJ0?C zYf2=%#M^u8;(`{JYKUW0E^0lr{t6ErgEfA;>9o8F&WrrB6sNsk=A*t%l|SPVmR4V> UF~3LZ2^+mle)CR60shDS0ED(;H2?qr literal 0 HcmV?d00001 From 15fbf2a8b0c5884673fc0df5509967281628dcb7 Mon Sep 17 00:00:00 2001 From: Maxwell Swadling Date: Sun, 8 May 2011 01:54:40 +1000 Subject: [PATCH 20/55] Fixed up formatting for sponsors. Signed-off-by: Maxwell Swadling --- Gemfile | 3 --- app/models/sponsor.rb | 2 +- app/views/shared/_sponsors.html.erb | 8 ++++++-- app/views/sponsors/_sponsor.html.erb | 2 +- app/views/sponsors/index.html.erb | 2 +- db/seeds.rb | 4 +++- 6 files changed, 12 insertions(+), 9 deletions(-) diff --git a/Gemfile b/Gemfile index bc95f61..73d1183 100644 --- a/Gemfile +++ b/Gemfile @@ -12,9 +12,6 @@ gem 'capistrano' # Use unicorn as the web server # gem 'unicorn' -# Deploy with Capistrano - - # To use debugger (ruby-debug for Ruby 1.8.7+, ruby-debug19 for Ruby 1.9.2+) # gem 'ruby-debug' # gem 'ruby-debug19', :require => 'ruby-debug' diff --git a/app/models/sponsor.rb b/app/models/sponsor.rb index 1fb9fcc..fe9e966 100644 --- a/app/models/sponsor.rb +++ b/app/models/sponsor.rb @@ -1,5 +1,5 @@ class Sponsor < ActiveRecord::Base - has_friendly_id :title, :use_slug => true + has_friendly_id :name, :use_slug => true has_attached_file :image, :styles => { :medium => "300x300>", :thumb => "100x100>" } validates :name, :presence => true diff --git a/app/views/shared/_sponsors.html.erb b/app/views/shared/_sponsors.html.erb index 2afef86..7904b47 100644 --- a/app/views/shared/_sponsors.html.erb +++ b/app/views/shared/_sponsors.html.erb @@ -2,6 +2,10 @@ <% @sponsors.each do |sponsor| %> - + <% end %> -
      <%= sponsor.name %>
      +
      + <%= link_to image_tag(sponsor.image_path.url, :alt => sponsor.alt_text, :border => 0), sponsor.website rescue sponsor.name %> +
      +
      \ No newline at end of file + diff --git a/app/views/sponsors/_sponsor.html.erb b/app/views/sponsors/_sponsor.html.erb index 7ff79c1..e543aa4 100644 --- a/app/views/sponsors/_sponsor.html.erb +++ b/app/views/sponsors/_sponsor.html.erb @@ -1,6 +1,6 @@ - <%= link_to image_tag(sponsor.image_path.url, :alt => sponsor.name), sponsor.website %> + <%= link_to image_tag(sponsor.image_path.url, :alt => sponsor.name), sponsor.website rescue nil %> <%= link_to sponsor.name, sponsor.website %>
      diff --git a/app/views/sponsors/index.html.erb b/app/views/sponsors/index.html.erb index 9ff0500..58d302d 100644 --- a/app/views/sponsors/index.html.erb +++ b/app/views/sponsors/index.html.erb @@ -3,7 +3,7 @@ <% @sponsors.each do |sponsor| %> - <%= render 'sponsor' %> + <%= render 'sponsor', :sponsor => sponsor %> <% end %>
      diff --git a/db/seeds.rb b/db/seeds.rb index 070d0df..f9ab7a2 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -22,4 +22,6 @@ Static.create :title => "Bling", :content => "Bling.", :slug_name => "bling" Static.create :title => "Games", :content => "Games.", :slug_name => "games" Static.create :title => "IRC", :content => "Irc.", :slug_name => "irc" -Static.create :title => "Contact Us", :content => "Contact us.", :slug_name => "contact" \ No newline at end of file +Static.create :title => "Contact Us", :content => "Contact us.", :slug_name => "contact" + +Sponsor.create :name => "UNSW CSE", :description => "UNSW Computer Science and Engineering", :website => "http://www.cse.unsw.edu.au", :alt_text => "CSE", :amount_paid => 1, :start_date => DateTime.parse("2011, 1, 1"), :expiry_date => DateTime.parse("2012, 1, 1") \ No newline at end of file From be28090444689119d53a7d0c3e05e20a55884e61 Mon Sep 17 00:00:00 2001 From: Maxwell Swadling Date: Sun, 8 May 2011 02:37:49 +1000 Subject: [PATCH 21/55] Added Devise for authentication. Signed-off-by: Maxwell Swadling --- app/models/user.rb | 6 + app/views/devise/sessions/new.html.erb | 14 ++ app/views/shared/_header.html.erb | 12 +- config/initializers/devise.rb | 194 ++++++++++++++++++ config/locales/devise.en.yml | 50 +++++ config/routes.rb | 2 + .../20110507160439_devise_create_users.rb | 18 ++ db/schema.rb | 17 +- db/seeds.rb | 2 +- public/stylesheets/style.css | 31 +++ 10 files changed, 343 insertions(+), 3 deletions(-) create mode 100644 app/models/user.rb create mode 100644 app/views/devise/sessions/new.html.erb create mode 100644 config/initializers/devise.rb create mode 100644 config/locales/devise.en.yml create mode 100644 db/migrate/20110507160439_devise_create_users.rb diff --git a/app/models/user.rb b/app/models/user.rb new file mode 100644 index 0000000..aa83282 --- /dev/null +++ b/app/models/user.rb @@ -0,0 +1,6 @@ +class User < ActiveRecord::Base + devise :token_authenticatable, :rememberable, :trackable + + # Setup accessible (or protected) attributes for your model + attr_accessible :remember_me +end diff --git a/app/views/devise/sessions/new.html.erb b/app/views/devise/sessions/new.html.erb new file mode 100644 index 0000000..96e2025 --- /dev/null +++ b/app/views/devise/sessions/new.html.erb @@ -0,0 +1,14 @@ +

      Sign in

      + +<%= form_for(resource, :as => resource_name, :url => session_path(resource_name)) do |f| %> +

      <%= f.label :cse_username %>
      + <%= f.text_field :cse_username %>

      + + <% if devise_mapping.rememberable? -%> +

      <%= f.check_box :remember_me %> <%= f.label :remember_me %>

      + <% end -%> + +

      <%= f.submit "Sign in" %>

      +<% end %> + +<%= render :partial => "devise/shared/links" %> \ No newline at end of file diff --git a/app/views/shared/_header.html.erb b/app/views/shared/_header.html.erb index 77e7c7b..242fe93 100644 --- a/app/views/shared/_header.html.erb +++ b/app/views/shared/_header.html.erb @@ -44,6 +44,16 @@
    - + \ No newline at end of file diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb new file mode 100644 index 0000000..f955e82 --- /dev/null +++ b/config/initializers/devise.rb @@ -0,0 +1,194 @@ +# Use this hook to configure devise mailer, warden hooks and so forth. The first +# four configuration values can also be set straight in your models. +Devise.setup do |config| + # ==> Mailer Configuration + # Configure the e-mail address which will be shown in DeviseMailer. + config.mailer_sender = "noreply@csesoc.unsw.edu.au" + + # Configure the class responsible to send e-mails. + # config.mailer = "Devise::Mailer" + + # ==> ORM configuration + # Load and configure the ORM. Supports :active_record (default) and + # :mongoid (bson_ext recommended) by default. Other ORMs may be + # available as additional gems. + require 'devise/orm/active_record' + + # ==> Configuration for any authentication mechanism + # Configure which keys are used when authenticating a user. The default is + # just :email. You can configure it to use [:username, :subdomain], so for + # authenticating a user, both parameters are required. Remember that those + # parameters are used only when authenticating and not when retrieving from + # session. If you need permissions, you should implement that in a before filter. + # You can also supply a hash where the value is a boolean determining whether + # or not authentication should be aborted when the value is not present. + # config.authentication_keys = [ :email ] + + # Configure parameters from the request object used for authentication. Each entry + # given should be a request method and it will automatically be passed to the + # find_for_authentication method and considered in your model lookup. For instance, + # if you set :request_keys to [:subdomain], :subdomain will be used on authentication. + # The same considerations mentioned for authentication_keys also apply to request_keys. + # config.request_keys = [] + + # Configure which authentication keys should be case-insensitive. + # These keys will be downcased upon creating or modifying a user and when used + # to authenticate or find a user. Default is :email. + config.case_insensitive_keys = [ :cse_username ] + + # Tell if authentication through request.params is enabled. True by default. + # config.params_authenticatable = true + + # Tell if authentication through HTTP Basic Auth is enabled. False by default. + # config.http_authenticatable = false + + # If http headers should be returned for AJAX requests. True by default. + # config.http_authenticatable_on_xhr = true + + # The realm used in Http Basic Authentication. "Application" by default. + # config.http_authentication_realm = "Application" + + # ==> Configuration for :database_authenticatable + # For bcrypt, this is the cost for hashing the password and defaults to 10. If + # using other encryptors, it sets how many times you want the password re-encrypted. + config.stretches = 10 + + # Setup a pepper to generate the encrypted password. + # config.pepper = "65647b631fcdc276cc29076634a4d4b7c3ce8c06e4f3fa8411812745b3eb2a62bd8c1e9ac9525755ecfa4636cb51e0ba8ee083a58dcd5b43f9f25561d8f90deb" + + # ==> Configuration for :confirmable + # The time you want to give your user to confirm his account. During this time + # he will be able to access your application without confirming. Default is 0.days + # When confirm_within is zero, the user won't be able to sign in without confirming. + # You can use this to let your user access some features of your application + # without confirming the account, but blocking it after a certain period + # (ie 2 days). + # config.confirm_within = 2.days + + # Defines which key will be used when confirming an account + # config.confirmation_keys = [ :email ] + + # ==> Configuration for :rememberable + # The time the user will be remembered without asking for credentials again. + # config.remember_for = 2.weeks + + # If true, a valid remember token can be re-used between multiple browsers. + # config.remember_across_browsers = true + + # If true, extends the user's remember period when remembered via cookie. + # config.extend_remember_period = false + + # If true, uses the password salt as remember token. This should be turned + # to false if you are not using database authenticatable. + config.use_salt_as_remember_token = true + + # Options to be passed to the created cookie. For instance, you can set + # :secure => true in order to force SSL only cookies. + # config.cookie_options = {} + + # ==> Configuration for :validatable + # Range for password length. Default is 6..128. + # config.password_length = 6..128 + + # Regex to use to validate the email address + # config.email_regexp = /\A([\w\.%\+\-]+)@([\w\-]+\.)+([\w]{2,})\z/i + + # ==> Configuration for :timeoutable + # The time you want to timeout the user session without activity. After this + # time the user will be asked for credentials again. Default is 30 minutes. + # config.timeout_in = 30.minutes + + # ==> Configuration for :lockable + # Defines which strategy will be used to lock an account. + # :failed_attempts = Locks an account after a number of failed attempts to sign in. + # :none = No lock strategy. You should handle locking by yourself. + # config.lock_strategy = :failed_attempts + + # Defines which key will be used when locking and unlocking an account + # config.unlock_keys = [ :email ] + + # Defines which strategy will be used to unlock an account. + # :email = Sends an unlock link to the user email + # :time = Re-enables login after a certain amount of time (see :unlock_in below) + # :both = Enables both strategies + # :none = No unlock strategy. You should handle unlocking by yourself. + # config.unlock_strategy = :both + + # Number of authentication tries before locking an account if lock_strategy + # is failed attempts. + # config.maximum_attempts = 20 + + # Time interval to unlock the account if :time is enabled as unlock_strategy. + # config.unlock_in = 1.hour + + # ==> Configuration for :recoverable + # + # Defines which key will be used when recovering the password for an account + # config.reset_password_keys = [ :email ] + + # Time interval you can reset your password with a reset password key. + # Don't put a too small interval or your users won't have the time to + # change their passwords. + config.reset_password_within = 2.hours + + # ==> Configuration for :encryptable + # Allow you to use another encryption algorithm besides bcrypt (default). You can use + # :sha1, :sha512 or encryptors from others authentication tools as :clearance_sha1, + # :authlogic_sha512 (then you should set stretches above to 20 for default behavior) + # and :restful_authentication_sha1 (then you should set stretches to 10, and copy + # REST_AUTH_SITE_KEY to pepper) + # config.encryptor = :sha512 + + # ==> Configuration for :token_authenticatable + # Defines name of the authentication token params key + # config.token_authentication_key = :auth_token + + # If true, authentication through token does not store user in session and needs + # to be supplied on each request. Useful if you are using the token as API token. + # config.stateless_token = false + + # ==> Scopes configuration + # Turn scoped views on. Before rendering "sessions/new", it will first check for + # "users/sessions/new". It's turned off by default because it's slower if you + # are using only default views. + # config.scoped_views = false + + # Configure the default scope given to Warden. By default it's the first + # devise role declared in your routes (usually :user). + # config.default_scope = :user + + # Configure sign_out behavior. + # Sign_out action can be scoped (i.e. /users/sign_out affects only :user scope). + # The default is true, which means any logout action will sign out all active scopes. + # config.sign_out_all_scopes = true + + # ==> Navigation configuration + # Lists the formats that should be treated as navigational. Formats like + # :html, should redirect to the sign in page when the user does not have + # access, but formats like :xml or :json, should return 401. + # + # If you have any extra navigational formats, like :iphone or :mobile, you + # should add them to the navigational formats lists. + # + # The :"*/*" and "*/*" formats below is required to match Internet + # Explorer requests. + # config.navigational_formats = [:"*/*", "*/*", :html] + + # The default HTTP method used to sign out a resource. Default is :get. + # config.sign_out_via = :get + + # ==> OmniAuth + # Add a new OmniAuth provider. Check the wiki for more information on setting + # up on your models and hooks. + # config.omniauth :github, 'APP_ID', 'APP_SECRET', :scope => 'user,public_repo' + + # ==> Warden configuration + # If you want to use other strategies, that are not supported by Devise, or + # change the failure app, you can configure them inside the config.warden block. + # + # config.warden do |manager| + # manager.failure_app = AnotherApp + # manager.intercept_401 = false + # manager.default_strategies(:scope => :user).unshift :some_external_strategy + # end +end diff --git a/config/locales/devise.en.yml b/config/locales/devise.en.yml new file mode 100644 index 0000000..25022e1 --- /dev/null +++ b/config/locales/devise.en.yml @@ -0,0 +1,50 @@ +# Additional translations at http://github.com/plataformatec/devise/wiki/I18n + +en: + errors: + messages: + expired: "has expired, please request a new one" + not_found: "not found" + already_confirmed: "was already confirmed, please try signing in" + not_locked: "was not locked" + not_saved: + one: "1 error prohibited this %{resource} from being saved:" + other: "%{count} errors prohibited this %{resource} from being saved:" + + devise: + failure: + already_authenticated: 'You are already signed in.' + unauthenticated: 'You need to sign in or sign up before continuing.' + unconfirmed: 'You have to confirm your account before continuing.' + locked: 'Your account is locked.' + invalid: 'Invalid email or password.' + invalid_token: 'Invalid authentication token.' + timeout: 'Your session expired, please sign in again to continue.' + inactive: 'Your account was not activated yet.' + sessions: + signed_in: 'Signed in successfully.' + signed_out: 'Signed out successfully.' + passwords: + send_instructions: 'You will receive an email with instructions about how to reset your password in a few minutes.' + updated: 'Your password was changed successfully. You are now signed in.' + confirmations: + send_instructions: 'You will receive an email with instructions about how to confirm your account in a few minutes.' + confirmed: 'Your account was successfully confirmed. You are now signed in.' + registrations: + signed_up: 'Welcome! You have signed up successfully.' + inactive_signed_up: 'You have signed up successfully. However, we could not sign you in because your account is %{reason}.' + updated: 'You updated your account successfully.' + destroyed: 'Bye! Your account was successfully cancelled. We hope to see you again soon.' + unlocks: + send_instructions: 'You will receive an email with instructions about how to unlock your account in a few minutes.' + unlocked: 'Your account was successfully unlocked. You are now signed in.' + omniauth_callbacks: + success: 'Successfully authorized from %{kind} account.' + failure: 'Could not authorize you from %{kind} because "%{reason}".' + mailer: + confirmation_instructions: + subject: 'Confirmation instructions' + reset_password_instructions: + subject: 'Reset password instructions' + unlock_instructions: + subject: 'Unlock Instructions' diff --git a/config/routes.rb b/config/routes.rb index 2a67534..b113523 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,6 +1,8 @@ CSESocWebsite::Application.routes.draw do root :to => "news_items#index" + devise_for :users + resources :suggestions, :only => [:index, :show, :new, :create] resources :sponsors, :only => [:index] resources :events, :only => [:index, :show] diff --git a/db/migrate/20110507160439_devise_create_users.rb b/db/migrate/20110507160439_devise_create_users.rb new file mode 100644 index 0000000..430d7ba --- /dev/null +++ b/db/migrate/20110507160439_devise_create_users.rb @@ -0,0 +1,18 @@ +class DeviseCreateUsers < ActiveRecord::Migration + def self.up + create_table(:users) do |t| + t.token_authenticatable + t.rememberable + t.trackable + t.string :cse_username + + t.timestamps + end + + add_index :users, :authentication_token, :unique => true + end + + def self.down + drop_table :users + end +end diff --git a/db/schema.rb b/db/schema.rb index 5d1b79e..27dcae2 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20110507100554) do +ActiveRecord::Schema.define(:version => 20110507160439) do create_table "comments", :force => true do |t| t.integer "suggestion_id" @@ -89,4 +89,19 @@ t.datetime "updated_at" end + create_table "users", :force => true do |t| + t.string "authentication_token" + t.datetime "remember_created_at" + t.integer "sign_in_count", :default => 0 + t.datetime "current_sign_in_at" + t.datetime "last_sign_in_at" + t.string "current_sign_in_ip" + t.string "last_sign_in_ip" + t.string "cse_username" + t.datetime "created_at" + t.datetime "updated_at" + end + + add_index "users", ["authentication_token"], :name => "index_users_on_authentication_token", :unique => true + end diff --git a/db/seeds.rb b/db/seeds.rb index f9ab7a2..e789380 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -24,4 +24,4 @@ Static.create :title => "IRC", :content => "Irc.", :slug_name => "irc" Static.create :title => "Contact Us", :content => "Contact us.", :slug_name => "contact" -Sponsor.create :name => "UNSW CSE", :description => "UNSW Computer Science and Engineering", :website => "http://www.cse.unsw.edu.au", :alt_text => "CSE", :amount_paid => 1, :start_date => DateTime.parse("2011, 1, 1"), :expiry_date => DateTime.parse("2012, 1, 1") \ No newline at end of file +Sponsor.create :name => "UNSW CSE", :description => "UNSW Computer Science and Engineering", :website => "http://www.cse.unsw.edu.au", :alt_text => "CSE", :amount_paid => 1, :start_date => DateTime.parse("2011, 1, 1"), :expiry_date => DateTime.parse("2012, 1, 1") diff --git a/public/stylesheets/style.css b/public/stylesheets/style.css index 898c38d..4f90a84 100644 --- a/public/stylesheets/style.css +++ b/public/stylesheets/style.css @@ -64,6 +64,37 @@ h6 { position:relative; top:0; } +#nav_region_mid_right { + float:right; + height:38px; + padding:0 10px; + float:right; +} +#nav_region_mid_right .nav_tier1{ + margin:0; + padding:0; + list-style:none; +} +#nav_region_mid_right .nav_tier1>li{ + display:inline; + margin:0; + float:right; + margin-left:10px; + position:relative; +} +#nav_region_mid_right .nav_tier1>li a { + color:#333; + font-size:18px; + padding-top:5px; + padding-left:10px; + padding-right:10px; + display:block; +} +#nav_region_mid_right .nav_tier1>li a:hover { + color:#cd282f; +} + + #nav_region_centre .nav_tier1{ margin:0; padding:0; From 6fa9d4ea7463e150b4001f1f693c201b8fc27d40 Mon Sep 17 00:00:00 2001 From: Maxwell Swadling Date: Sun, 8 May 2011 02:41:27 +1000 Subject: [PATCH 22/55] Added Rails Admin. Signed-off-by: Maxwell Swadling --- Gemfile | 2 + Gemfile.lock | 18 ++++++ config/locales/rails_admin.en.yml | 60 +++++++++++++++++++ .../20110507163905_create_histories_table.rb | 18 ++++++ ...name_histories_to_rails_admin_histories.rb | 9 +++ db/schema.rb | 15 ++++- 6 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 config/locales/rails_admin.en.yml create mode 100644 db/migrate/20110507163905_create_histories_table.rb create mode 100644 db/migrate/20110507163906_rename_histories_to_rails_admin_histories.rb diff --git a/Gemfile b/Gemfile index 73d1183..539a6bd 100644 --- a/Gemfile +++ b/Gemfile @@ -8,6 +8,8 @@ gem 'friendly_id' gem 'paperclip' gem 'rdiscount' gem 'capistrano' +gem 'devise' +gem 'rails_admin', :git => 'git://github.com/sferik/rails_admin.git' # Use unicorn as the web server # gem 'unicorn' diff --git a/Gemfile.lock b/Gemfile.lock index 8f24286..0e4f268 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,3 +1,11 @@ +GIT + remote: git://github.com/sferik/rails_admin.git + revision: 319a23d4df85ec82cc023fb30c2ae60f5ceca214 + specs: + rails_admin (0.0.1) + builder (~> 2.1.0) + rails (~> 3.0.6) + GEM remote: http://rubygems.org/ specs: @@ -30,6 +38,7 @@ GEM activesupport (3.0.7) arel (2.0.9) babosa (0.3.3) + bcrypt-ruby (2.1.4) builder (2.1.2) capistrano (2.5.21) highline @@ -37,6 +46,10 @@ GEM net-sftp (>= 2.0.0) net-ssh (>= 2.0.14) net-ssh-gateway (>= 1.0.0) + devise (1.3.4) + bcrypt-ruby (~> 2.1.2) + orm_adapter (~> 0.0.3) + warden (~> 1.0.3) erubis (2.6.6) abstract (>= 1.0.0) friendly_id (3.2.1.1) @@ -56,6 +69,7 @@ GEM net-ssh (2.1.4) net-ssh-gateway (1.0.1) net-ssh (>= 1.99.1) + orm_adapter (0.0.4) paperclip (2.3.11) activerecord (>= 2.3.0) activesupport (>= 2.3.2) @@ -85,14 +99,18 @@ GEM treetop (1.4.9) polyglot (>= 0.3.1) tzinfo (0.3.27) + warden (1.0.4) + rack (>= 1.0) PLATFORMS ruby DEPENDENCIES capistrano + devise friendly_id paperclip rails (= 3.0.7) + rails_admin! rdiscount sqlite3 diff --git a/config/locales/rails_admin.en.yml b/config/locales/rails_admin.en.yml new file mode 100644 index 0000000..6a00c7f --- /dev/null +++ b/config/locales/rails_admin.en.yml @@ -0,0 +1,60 @@ +# Sample localization file for English. Add more files in this directory for other locales. +# See http://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. + +en: + home: + name: "home" + admin: + dashboard: + pagename: "Site administration" + name: "Dashboard" + model_name: "Model name" + last_used: "Last used" + records: "Records" + modify: "Modify" + add_new: "Add new" + show: "Show" + ago: "ago" + history: + name: "History" + page_name: "History for %{name}" + no_activity: "No Activity" + credentials: + log_out: "Log out" + list: + edit_action: "Edit" + delete_action: "Delete" + select_action: "Select" + delete_selected: "Delete selected" + add_new: "Add new" + search: "Search" + select: "Select %{name} to edit" + show_all: "Show all" + new: + basic_info: "Basic info" + required: "Required" + optional: "Optional" + one_char: "character." + many_chars: "characters or fewer." + chosen: "Chosen %{name}" + select_choice: "Select your choice(s) and click" + chose_all: "Choose all" + clear_all: "Clear all" + save: "Save" + save_and_add_another: "Save and add another" + save_and_edit: "Save and edit" + cancel: "Cancel" + delete: + flash_confirmation: "%{name} was successfully destroyed" + confirmation: "Yes, I'm sure" + flash: + successful: "%{name} was successfully %{action}" + error: "%{name} failed to be %{action}" + noaction: "No actions were taken" + actions: + create: "create" + created: "created" + update: "update" + updated: "updated" + delete: "delete" + deleted: "deleted" \ No newline at end of file diff --git a/db/migrate/20110507163905_create_histories_table.rb b/db/migrate/20110507163905_create_histories_table.rb new file mode 100644 index 0000000..7884b5d --- /dev/null +++ b/db/migrate/20110507163905_create_histories_table.rb @@ -0,0 +1,18 @@ +class CreateHistoriesTable < ActiveRecord::Migration + def self.up + create_table :histories do |t| + t.string :message # title, name, or object_id + t.string :username + t.integer :item + t.string :table + t.integer :month, :limit => 2 + t.integer :year, :limit => 5 + t.timestamps + end + add_index(:histories, [:item, :table, :month, :year]) + end + + def self.down + drop_table :histories + end +end diff --git a/db/migrate/20110507163906_rename_histories_to_rails_admin_histories.rb b/db/migrate/20110507163906_rename_histories_to_rails_admin_histories.rb new file mode 100644 index 0000000..b5a87f3 --- /dev/null +++ b/db/migrate/20110507163906_rename_histories_to_rails_admin_histories.rb @@ -0,0 +1,9 @@ +class RenameHistoriesToRailsAdminHistories < ActiveRecord::Migration + def self.up + rename_table :histories, :rails_admin_histories + end + + def self.down + rename_table :rails_admin_histories, :histories + end +end diff --git a/db/schema.rb b/db/schema.rb index 27dcae2..1e2bb99 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20110507160439) do +ActiveRecord::Schema.define(:version => 20110507163906) do create_table "comments", :force => true do |t| t.integer "suggestion_id" @@ -44,6 +44,19 @@ t.datetime "updated_at" end + create_table "rails_admin_histories", :force => true do |t| + t.string "message" + t.string "username" + t.integer "item" + t.string "table" + t.integer "month", :limit => 2 + t.integer "year", :limit => 5 + t.datetime "created_at" + t.datetime "updated_at" + end + + add_index "rails_admin_histories", ["item", "table", "month", "year"], :name => "index_histories_on_item_and_table_and_month_and_year" + create_table "slugs", :force => true do |t| t.string "name" t.integer "sluggable_id" From 091e95342a542fba65e5ecdbe244738c94356faf Mon Sep 17 00:00:00 2001 From: Maxwell Swadling Date: Sat, 7 May 2011 23:31:45 +0800 Subject: [PATCH 23/55] Added markdown to news posts and Capistrano. Signed-off-by: Maxwell Swadling --- Gemfile | 7 +++--- Gemfile.lock | 17 ++++++++++++++ app/views/news_items/index.html.erb | 35 ++++++++++++++++------------- 3 files changed, 39 insertions(+), 20 deletions(-) diff --git a/Gemfile b/Gemfile index 48371ed..bc95f61 100644 --- a/Gemfile +++ b/Gemfile @@ -2,19 +2,18 @@ source 'http://rubygems.org' gem 'rails', '3.0.7' -# Bundle edge Rails instead: -# gem 'rails', :git => 'git://github.com/rails/rails.git' - gem 'sqlite3' gem 'friendly_id' gem 'paperclip' +gem 'rdiscount' +gem 'capistrano' # Use unicorn as the web server # gem 'unicorn' # Deploy with Capistrano -# gem 'capistrano' + # To use debugger (ruby-debug for Ruby 1.8.7+, ruby-debug19 for Ruby 1.9.2+) # gem 'ruby-debug' diff --git a/Gemfile.lock b/Gemfile.lock index 9653f91..8f24286 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -31,10 +31,17 @@ GEM arel (2.0.9) babosa (0.3.3) builder (2.1.2) + capistrano (2.5.21) + highline + net-scp (>= 1.0.0) + net-sftp (>= 2.0.0) + net-ssh (>= 2.0.14) + net-ssh-gateway (>= 1.0.0) erubis (2.6.6) abstract (>= 1.0.0) friendly_id (3.2.1.1) babosa (~> 0.3.0) + highline (1.6.1) i18n (0.5.0) mail (2.2.19) activesupport (>= 2.3.6) @@ -42,6 +49,13 @@ GEM mime-types (~> 1.16) treetop (~> 1.4.8) mime-types (1.16) + net-scp (1.0.4) + net-ssh (>= 1.99.1) + net-sftp (2.0.5) + net-ssh (>= 2.0.9) + net-ssh (2.1.4) + net-ssh-gateway (1.0.1) + net-ssh (>= 1.99.1) paperclip (2.3.11) activerecord (>= 2.3.0) activesupport (>= 2.3.2) @@ -65,6 +79,7 @@ GEM rake (>= 0.8.7) thor (~> 0.14.4) rake (0.8.7) + rdiscount (1.6.8) sqlite3 (1.3.3) thor (0.14.6) treetop (1.4.9) @@ -75,7 +90,9 @@ PLATFORMS ruby DEPENDENCIES + capistrano friendly_id paperclip rails (= 3.0.7) + rdiscount sqlite3 diff --git a/app/views/news_items/index.html.erb b/app/views/news_items/index.html.erb index 2fdaad6..441ed5e 100644 --- a/app/views/news_items/index.html.erb +++ b/app/views/news_items/index.html.erb @@ -1,21 +1,24 @@ <%= render :layout => 'shared/three_columns' do %> -

    Listing news_items

    - - - - - - - +

    News

    <% @news_items.each do |news_item| %> - - - - - - +
    +
    +
    + <%= news_item.publish_date.day %> + <%= news_item.publish_date.month %> + <%= news_item.publish_date.year %> + @ <%= news_item.publish_date.strftime("%H:%M") %> + by <%#= news_item.author.name %> +
    +
    +
    +

    <%= news_item.title %>

    +
    + <%=h RDiscount.new(news_item.content).to_html %> +
    +
    +
    <% end %> -
    TitleContentPublish dateAuthor
    <%= news_item.title %><%= news_item.content %><%= news_item.publish_date %><%= news_item.author %>
    -
    + <% end %> \ No newline at end of file From 369b52ec9a060676665ea1030f88b40953e9e875 Mon Sep 17 00:00:00 2001 From: Maxwell Swadling Date: Sat, 7 May 2011 23:32:21 +0800 Subject: [PATCH 24/55] Fixed formatting. Signed-off-by: Maxwell Swadling --- app/views/events/show.html.erb | 2 +- app/views/layouts/application.html.erb | 1 + app/views/news_items/_news_item.html.erb | 17 ++++++++++ app/views/news_items/index.html.erb | 18 +---------- app/views/news_items/show.html.erb | 22 +------------ app/views/shared/_header.html.erb | 2 +- app/views/shared/_sponsors.html.erb | 6 ++-- app/views/sponsors/_sponsor.html.erb | 9 ++++++ app/views/sponsors/index.html.erb | 40 ++++-------------------- public/stylesheets/style.css | 3 ++ 10 files changed, 43 insertions(+), 77 deletions(-) create mode 100644 app/views/news_items/_news_item.html.erb create mode 100644 app/views/sponsors/_sponsor.html.erb diff --git a/app/views/events/show.html.erb b/app/views/events/show.html.erb index 6005414..7949248 100644 --- a/app/views/events/show.html.erb +++ b/app/views/events/show.html.erb @@ -1,4 +1,4 @@ -

    <%= notice %>

    +

    Name: diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 7d67305..3d56e86 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -19,6 +19,7 @@

    +

    <%= notice %>

    <%= yield %>
    diff --git a/app/views/news_items/_news_item.html.erb b/app/views/news_items/_news_item.html.erb new file mode 100644 index 0000000..81a53c7 --- /dev/null +++ b/app/views/news_items/_news_item.html.erb @@ -0,0 +1,17 @@ +
    +
    +
    + <%= news_item.publish_date.day %> + <%= news_item.publish_date.month %> + <%= news_item.publish_date.year %> + @ <%= news_item.publish_date.strftime("%H:%M") %> + by <%#= news_item.author.name %> +
    +
    +
    +

    <%= link_to_unless defined?(no_link), news_item.title, news_item %>

    +
    + <%= RDiscount.new(news_item.content).to_html.html_safe %> +
    +
    +
    \ No newline at end of file diff --git a/app/views/news_items/index.html.erb b/app/views/news_items/index.html.erb index 441ed5e..1173b2d 100644 --- a/app/views/news_items/index.html.erb +++ b/app/views/news_items/index.html.erb @@ -2,23 +2,7 @@

    News

    <% @news_items.each do |news_item| %> -
    -
    -
    - <%= news_item.publish_date.day %> - <%= news_item.publish_date.month %> - <%= news_item.publish_date.year %> - @ <%= news_item.publish_date.strftime("%H:%M") %> - by <%#= news_item.author.name %> -
    -
    -
    -

    <%= news_item.title %>

    -
    - <%=h RDiscount.new(news_item.content).to_html %> -
    -
    -
    + <%= render news_item %> <% end %> <% end %> \ No newline at end of file diff --git a/app/views/news_items/show.html.erb b/app/views/news_items/show.html.erb index 6edc662..d2316ec 100644 --- a/app/views/news_items/show.html.erb +++ b/app/views/news_items/show.html.erb @@ -1,25 +1,5 @@ -

    <%= notice %>

    -

    - Title: - <%= @news_item.title %> -

    -

    - Content: - <%= @news_item.content %> -

    +<%= render @news_item, :no_link => true %> -

    - Publish date: - <%= @news_item.publish_date %> -

    - -

    - Author: - <%= @news_item.author %> -

    - - -<%= link_to 'Edit', edit_news_item_path(@news_item) %> | <%= link_to 'Back', news_items_path %> diff --git a/app/views/shared/_header.html.erb b/app/views/shared/_header.html.erb index 19d7a96..77e7c7b 100644 --- a/app/views/shared/_header.html.erb +++ b/app/views/shared/_header.html.erb @@ -7,7 +7,7 @@
  • <%= link_to "Home", root_path %>
  • -
  • <%= link_to "About", "google.com" %> +
  • <%= link_to "About", static_content_path("about") %>
    • <%= link_to "History", static_content_path("history") %>
    • <%= link_to "Constitution", static_content_path("constitution") %>
    • diff --git a/app/views/shared/_sponsors.html.erb b/app/views/shared/_sponsors.html.erb index 8455718..2afef86 100644 --- a/app/views/shared/_sponsors.html.erb +++ b/app/views/shared/_sponsors.html.erb @@ -1,7 +1,7 @@

      Our sponsors

      -<% @sponsors.each do |sponsor| %> - -<% end %> + <% @sponsors.each do |sponsor| %> + + <% end %>
      <%= sponsor.name %>
      <%= sponsor.name %>
      \ No newline at end of file diff --git a/app/views/sponsors/_sponsor.html.erb b/app/views/sponsors/_sponsor.html.erb new file mode 100644 index 0000000..7ff79c1 --- /dev/null +++ b/app/views/sponsors/_sponsor.html.erb @@ -0,0 +1,9 @@ + + + <%= link_to image_tag(sponsor.image_path.url, :alt => sponsor.name), sponsor.website %> + + + <%= link_to sponsor.name, sponsor.website %>
      + <%= sponsor.description %> + + diff --git a/app/views/sponsors/index.html.erb b/app/views/sponsors/index.html.erb index ca16f63..9ff0500 100644 --- a/app/views/sponsors/index.html.erb +++ b/app/views/sponsors/index.html.erb @@ -1,37 +1,9 @@ -

      Listing sponsors

      +

      Our Sponsors

      - - - - - - - - - - - - - - +
      NameDescriptionWebsiteAlt textAmount paidStart dateExpiry dateHtml override
      + + <% @sponsors.each do |sponsor| %> + <%= render 'sponsor' %> + <% end %> -<% @sponsors.each do |sponsor| %> - - - - - - - - - - - - - -<% end %>
      <%= sponsor.name %><%= sponsor.description %><%= sponsor.website %><%= sponsor.alt_text %><%= sponsor.amount_paid %><%= sponsor.start_date %><%= sponsor.expiry_date %><%= sponsor.html_override %><%= link_to 'Show', sponsor %><%= link_to 'Edit', edit_sponsor_path(sponsor) %><%= link_to 'Destroy', sponsor, :confirm => 'Are you sure?', :method => :delete %>
      - -
      - -<%= link_to 'New Sponsor', new_sponsor_path %> diff --git a/public/stylesheets/style.css b/public/stylesheets/style.css index 3f4a04d..898c38d 100644 --- a/public/stylesheets/style.css +++ b/public/stylesheets/style.css @@ -225,6 +225,9 @@ ul.pagenumbers { .news-title { color:#2f586d; } +.news-title h2 a { + color:#2f586d; +} .news a { color:#2f58cd; } From e8898293953be29a5d718d0e34e5317ed980cc4d Mon Sep 17 00:00:00 2001 From: Maxwell Swadling Date: Sat, 7 May 2011 23:32:38 +0800 Subject: [PATCH 25/55] Added static content seeds. Signed-off-by: Maxwell Swadling --- app/helpers/application_helper.rb | 6 +----- app/models/static.rb | 4 ++-- db/migrate/20110507094645_create_statics.rb | 2 +- db/schema.rb | 6 +++++- db/seeds.rb | 18 ++++++++++++++++++ 5 files changed, 27 insertions(+), 9 deletions(-) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index de2034a..d41bbc2 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -8,11 +8,7 @@ def page_title(y) # Link's to static content that may not exist def static_content_path(name) - begin - return Static.find(name) - rescue - return root_path - end + Static.find(name) end end diff --git a/app/models/static.rb b/app/models/static.rb index ac9c895..6003782 100644 --- a/app/models/static.rb +++ b/app/models/static.rb @@ -1,7 +1,7 @@ class Static < ActiveRecord::Base - has_friendly_id :slug, :use_slug => true + has_friendly_id :slug_name, :use_slug => true validates :title, :presence => true validates :content, :presence => true - validates :slug, :presence => true + validates :slug_name, :presence => true end diff --git a/db/migrate/20110507094645_create_statics.rb b/db/migrate/20110507094645_create_statics.rb index e289506..b87950f 100644 --- a/db/migrate/20110507094645_create_statics.rb +++ b/db/migrate/20110507094645_create_statics.rb @@ -3,7 +3,7 @@ def self.up create_table :statics do |t| t.string :title t.text :content - t.string :slug + t.string :slug_name t.timestamps end diff --git a/db/schema.rb b/db/schema.rb index ff8bc1e..5d1b79e 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -65,6 +65,10 @@ t.datetime "start_date" t.datetime "expiry_date" t.text "html_override" + t.string "image_file_name" + t.string "image_content_type" + t.integer "image_file_size" + t.datetime "image_updated_at" t.datetime "created_at" t.datetime "updated_at" end @@ -72,7 +76,7 @@ create_table "statics", :force => true do |t| t.string "title" t.text "content" - t.string "slug" + t.string "slug_name" t.datetime "created_at" t.datetime "updated_at" end diff --git a/db/seeds.rb b/db/seeds.rb index 664d8c7..070d0df 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -5,3 +5,21 @@ # # cities = City.create([{ :name => 'Chicago' }, { :name => 'Copenhagen' }]) # Mayor.create(:name => 'Daley', :city => cities.first) + +Static.create :title => "About CSESoc", :content => "About CSESoc.", :slug_name => "about" +Static.create :title => "A Brief History of CSESoc", :content => "History of CSESoc.", :slug_name => "history" +Static.create :title => "Constitution", :content => "CSESoc Constitution.", :slug_name => "constitution" +Static.create :title => "CSESoc Exec & Heads", :content => "Exec & Heads", :slug_name => "execheads" +Static.create :title => "Working Groups", :content => "Working groups.", :slug_name => "workinggroups" +Static.create :title => "Beta", :content => "Beta.", :slug_name => "workinggroupsbeta" +Static.create :title => "Publicity", :content => "Publicity.", :slug_name => "workinggroupspublicity" +Static.create :title => "Social", :content => "Social.", :slug_name => "workinggroupssocial" +Static.create :title => "Sysadmin", :content => "Sysadmin.", :slug_name => "workinggroupssysadmin" +Static.create :title => "Tech", :content => "Tech.", :slug_name => "workinggroupstech" +Static.create :title => "Volunteer!", :content => "Volunteer.", :slug_name => "workinggroupsvolunteer" +Static.create :title => "Students", :content => "Students.", :slug_name => "students" +Static.create :title => "Murder", :content => "Murder.", :slug_name => "murder" +Static.create :title => "Bling", :content => "Bling.", :slug_name => "bling" +Static.create :title => "Games", :content => "Games.", :slug_name => "games" +Static.create :title => "IRC", :content => "Irc.", :slug_name => "irc" +Static.create :title => "Contact Us", :content => "Contact us.", :slug_name => "contact" \ No newline at end of file From 734a29b1064e4b50595212455ba894929a72139c Mon Sep 17 00:00:00 2001 From: Maxwell Swadling Date: Sat, 7 May 2011 23:37:35 +0800 Subject: [PATCH 26/55] Static pages are now markdown. Signed-off-by: Maxwell Swadling --- app/views/statics/show.html.erb | 21 ++------------------- app/views/suggestions/show.html.erb | 2 +- 2 files changed, 3 insertions(+), 20 deletions(-) diff --git a/app/views/statics/show.html.erb b/app/views/statics/show.html.erb index cd07600..829a64e 100644 --- a/app/views/statics/show.html.erb +++ b/app/views/statics/show.html.erb @@ -1,20 +1,3 @@ -

      <%= notice %>

      +

      <%= @static.title %>

      -

      - Title: - <%= @static.title %> -

      - -

      - Content: - <%= @static.content %> -

      - -

      - Slug: - <%= @static.slug %> -

      - - -<%= link_to 'Edit', edit_static_path(@static) %> | -<%= link_to 'Back', statics_path %> +<%= RDiscount.new(@static.content).to_html.html_safe %> diff --git a/app/views/suggestions/show.html.erb b/app/views/suggestions/show.html.erb index 4412dcd..a2a802b 100644 --- a/app/views/suggestions/show.html.erb +++ b/app/views/suggestions/show.html.erb @@ -1,4 +1,4 @@ -

      <%= notice %>

      +

      Subject: From a7ef3a5d05d1ca56cf3ec70d79f0f636f1a6d79b Mon Sep 17 00:00:00 2001 From: Maxwell Swadling Date: Sat, 7 May 2011 23:54:40 +0800 Subject: [PATCH 27/55] Fixed up formatting for sponsors. Signed-off-by: Maxwell Swadling --- Gemfile | 3 --- app/models/sponsor.rb | 2 +- app/views/shared/_sponsors.html.erb | 8 ++++++-- app/views/sponsors/_sponsor.html.erb | 2 +- app/views/sponsors/index.html.erb | 2 +- db/seeds.rb | 4 +++- 6 files changed, 12 insertions(+), 9 deletions(-) diff --git a/Gemfile b/Gemfile index bc95f61..73d1183 100644 --- a/Gemfile +++ b/Gemfile @@ -12,9 +12,6 @@ gem 'capistrano' # Use unicorn as the web server # gem 'unicorn' -# Deploy with Capistrano - - # To use debugger (ruby-debug for Ruby 1.8.7+, ruby-debug19 for Ruby 1.9.2+) # gem 'ruby-debug' # gem 'ruby-debug19', :require => 'ruby-debug' diff --git a/app/models/sponsor.rb b/app/models/sponsor.rb index 1fb9fcc..fe9e966 100644 --- a/app/models/sponsor.rb +++ b/app/models/sponsor.rb @@ -1,5 +1,5 @@ class Sponsor < ActiveRecord::Base - has_friendly_id :title, :use_slug => true + has_friendly_id :name, :use_slug => true has_attached_file :image, :styles => { :medium => "300x300>", :thumb => "100x100>" } validates :name, :presence => true diff --git a/app/views/shared/_sponsors.html.erb b/app/views/shared/_sponsors.html.erb index 2afef86..7904b47 100644 --- a/app/views/shared/_sponsors.html.erb +++ b/app/views/shared/_sponsors.html.erb @@ -2,6 +2,10 @@ <% @sponsors.each do |sponsor| %> - + <% end %> -
      <%= sponsor.name %>
      +
      + <%= link_to image_tag(sponsor.image_path.url, :alt => sponsor.alt_text, :border => 0), sponsor.website rescue sponsor.name %> +
      +
      \ No newline at end of file + diff --git a/app/views/sponsors/_sponsor.html.erb b/app/views/sponsors/_sponsor.html.erb index 7ff79c1..e543aa4 100644 --- a/app/views/sponsors/_sponsor.html.erb +++ b/app/views/sponsors/_sponsor.html.erb @@ -1,6 +1,6 @@ - <%= link_to image_tag(sponsor.image_path.url, :alt => sponsor.name), sponsor.website %> + <%= link_to image_tag(sponsor.image_path.url, :alt => sponsor.name), sponsor.website rescue nil %> <%= link_to sponsor.name, sponsor.website %>
      diff --git a/app/views/sponsors/index.html.erb b/app/views/sponsors/index.html.erb index 9ff0500..58d302d 100644 --- a/app/views/sponsors/index.html.erb +++ b/app/views/sponsors/index.html.erb @@ -3,7 +3,7 @@ <% @sponsors.each do |sponsor| %> - <%= render 'sponsor' %> + <%= render 'sponsor', :sponsor => sponsor %> <% end %>
      diff --git a/db/seeds.rb b/db/seeds.rb index 070d0df..f9ab7a2 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -22,4 +22,6 @@ Static.create :title => "Bling", :content => "Bling.", :slug_name => "bling" Static.create :title => "Games", :content => "Games.", :slug_name => "games" Static.create :title => "IRC", :content => "Irc.", :slug_name => "irc" -Static.create :title => "Contact Us", :content => "Contact us.", :slug_name => "contact" \ No newline at end of file +Static.create :title => "Contact Us", :content => "Contact us.", :slug_name => "contact" + +Sponsor.create :name => "UNSW CSE", :description => "UNSW Computer Science and Engineering", :website => "http://www.cse.unsw.edu.au", :alt_text => "CSE", :amount_paid => 1, :start_date => DateTime.parse("2011, 1, 1"), :expiry_date => DateTime.parse("2012, 1, 1") \ No newline at end of file From b88d29c4f196fc6f196c7c7449d9adb265c89943 Mon Sep 17 00:00:00 2001 From: Maxwell Swadling Date: Sun, 8 May 2011 00:37:49 +0800 Subject: [PATCH 28/55] Added Devise for authentication. Signed-off-by: Maxwell Swadling --- app/models/user.rb | 6 + app/views/devise/sessions/new.html.erb | 14 ++ app/views/shared/_header.html.erb | 12 +- config/initializers/devise.rb | 194 ++++++++++++++++++ config/locales/devise.en.yml | 50 +++++ config/routes.rb | 2 + .../20110507160439_devise_create_users.rb | 18 ++ db/schema.rb | 17 +- db/seeds.rb | 2 +- public/stylesheets/style.css | 31 +++ 10 files changed, 343 insertions(+), 3 deletions(-) create mode 100644 app/models/user.rb create mode 100644 app/views/devise/sessions/new.html.erb create mode 100644 config/initializers/devise.rb create mode 100644 config/locales/devise.en.yml create mode 100644 db/migrate/20110507160439_devise_create_users.rb diff --git a/app/models/user.rb b/app/models/user.rb new file mode 100644 index 0000000..aa83282 --- /dev/null +++ b/app/models/user.rb @@ -0,0 +1,6 @@ +class User < ActiveRecord::Base + devise :token_authenticatable, :rememberable, :trackable + + # Setup accessible (or protected) attributes for your model + attr_accessible :remember_me +end diff --git a/app/views/devise/sessions/new.html.erb b/app/views/devise/sessions/new.html.erb new file mode 100644 index 0000000..96e2025 --- /dev/null +++ b/app/views/devise/sessions/new.html.erb @@ -0,0 +1,14 @@ +

      Sign in

      + +<%= form_for(resource, :as => resource_name, :url => session_path(resource_name)) do |f| %> +

      <%= f.label :cse_username %>
      + <%= f.text_field :cse_username %>

      + + <% if devise_mapping.rememberable? -%> +

      <%= f.check_box :remember_me %> <%= f.label :remember_me %>

      + <% end -%> + +

      <%= f.submit "Sign in" %>

      +<% end %> + +<%= render :partial => "devise/shared/links" %> \ No newline at end of file diff --git a/app/views/shared/_header.html.erb b/app/views/shared/_header.html.erb index 77e7c7b..242fe93 100644 --- a/app/views/shared/_header.html.erb +++ b/app/views/shared/_header.html.erb @@ -44,6 +44,16 @@
    - + \ No newline at end of file diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb new file mode 100644 index 0000000..f955e82 --- /dev/null +++ b/config/initializers/devise.rb @@ -0,0 +1,194 @@ +# Use this hook to configure devise mailer, warden hooks and so forth. The first +# four configuration values can also be set straight in your models. +Devise.setup do |config| + # ==> Mailer Configuration + # Configure the e-mail address which will be shown in DeviseMailer. + config.mailer_sender = "noreply@csesoc.unsw.edu.au" + + # Configure the class responsible to send e-mails. + # config.mailer = "Devise::Mailer" + + # ==> ORM configuration + # Load and configure the ORM. Supports :active_record (default) and + # :mongoid (bson_ext recommended) by default. Other ORMs may be + # available as additional gems. + require 'devise/orm/active_record' + + # ==> Configuration for any authentication mechanism + # Configure which keys are used when authenticating a user. The default is + # just :email. You can configure it to use [:username, :subdomain], so for + # authenticating a user, both parameters are required. Remember that those + # parameters are used only when authenticating and not when retrieving from + # session. If you need permissions, you should implement that in a before filter. + # You can also supply a hash where the value is a boolean determining whether + # or not authentication should be aborted when the value is not present. + # config.authentication_keys = [ :email ] + + # Configure parameters from the request object used for authentication. Each entry + # given should be a request method and it will automatically be passed to the + # find_for_authentication method and considered in your model lookup. For instance, + # if you set :request_keys to [:subdomain], :subdomain will be used on authentication. + # The same considerations mentioned for authentication_keys also apply to request_keys. + # config.request_keys = [] + + # Configure which authentication keys should be case-insensitive. + # These keys will be downcased upon creating or modifying a user and when used + # to authenticate or find a user. Default is :email. + config.case_insensitive_keys = [ :cse_username ] + + # Tell if authentication through request.params is enabled. True by default. + # config.params_authenticatable = true + + # Tell if authentication through HTTP Basic Auth is enabled. False by default. + # config.http_authenticatable = false + + # If http headers should be returned for AJAX requests. True by default. + # config.http_authenticatable_on_xhr = true + + # The realm used in Http Basic Authentication. "Application" by default. + # config.http_authentication_realm = "Application" + + # ==> Configuration for :database_authenticatable + # For bcrypt, this is the cost for hashing the password and defaults to 10. If + # using other encryptors, it sets how many times you want the password re-encrypted. + config.stretches = 10 + + # Setup a pepper to generate the encrypted password. + # config.pepper = "65647b631fcdc276cc29076634a4d4b7c3ce8c06e4f3fa8411812745b3eb2a62bd8c1e9ac9525755ecfa4636cb51e0ba8ee083a58dcd5b43f9f25561d8f90deb" + + # ==> Configuration for :confirmable + # The time you want to give your user to confirm his account. During this time + # he will be able to access your application without confirming. Default is 0.days + # When confirm_within is zero, the user won't be able to sign in without confirming. + # You can use this to let your user access some features of your application + # without confirming the account, but blocking it after a certain period + # (ie 2 days). + # config.confirm_within = 2.days + + # Defines which key will be used when confirming an account + # config.confirmation_keys = [ :email ] + + # ==> Configuration for :rememberable + # The time the user will be remembered without asking for credentials again. + # config.remember_for = 2.weeks + + # If true, a valid remember token can be re-used between multiple browsers. + # config.remember_across_browsers = true + + # If true, extends the user's remember period when remembered via cookie. + # config.extend_remember_period = false + + # If true, uses the password salt as remember token. This should be turned + # to false if you are not using database authenticatable. + config.use_salt_as_remember_token = true + + # Options to be passed to the created cookie. For instance, you can set + # :secure => true in order to force SSL only cookies. + # config.cookie_options = {} + + # ==> Configuration for :validatable + # Range for password length. Default is 6..128. + # config.password_length = 6..128 + + # Regex to use to validate the email address + # config.email_regexp = /\A([\w\.%\+\-]+)@([\w\-]+\.)+([\w]{2,})\z/i + + # ==> Configuration for :timeoutable + # The time you want to timeout the user session without activity. After this + # time the user will be asked for credentials again. Default is 30 minutes. + # config.timeout_in = 30.minutes + + # ==> Configuration for :lockable + # Defines which strategy will be used to lock an account. + # :failed_attempts = Locks an account after a number of failed attempts to sign in. + # :none = No lock strategy. You should handle locking by yourself. + # config.lock_strategy = :failed_attempts + + # Defines which key will be used when locking and unlocking an account + # config.unlock_keys = [ :email ] + + # Defines which strategy will be used to unlock an account. + # :email = Sends an unlock link to the user email + # :time = Re-enables login after a certain amount of time (see :unlock_in below) + # :both = Enables both strategies + # :none = No unlock strategy. You should handle unlocking by yourself. + # config.unlock_strategy = :both + + # Number of authentication tries before locking an account if lock_strategy + # is failed attempts. + # config.maximum_attempts = 20 + + # Time interval to unlock the account if :time is enabled as unlock_strategy. + # config.unlock_in = 1.hour + + # ==> Configuration for :recoverable + # + # Defines which key will be used when recovering the password for an account + # config.reset_password_keys = [ :email ] + + # Time interval you can reset your password with a reset password key. + # Don't put a too small interval or your users won't have the time to + # change their passwords. + config.reset_password_within = 2.hours + + # ==> Configuration for :encryptable + # Allow you to use another encryption algorithm besides bcrypt (default). You can use + # :sha1, :sha512 or encryptors from others authentication tools as :clearance_sha1, + # :authlogic_sha512 (then you should set stretches above to 20 for default behavior) + # and :restful_authentication_sha1 (then you should set stretches to 10, and copy + # REST_AUTH_SITE_KEY to pepper) + # config.encryptor = :sha512 + + # ==> Configuration for :token_authenticatable + # Defines name of the authentication token params key + # config.token_authentication_key = :auth_token + + # If true, authentication through token does not store user in session and needs + # to be supplied on each request. Useful if you are using the token as API token. + # config.stateless_token = false + + # ==> Scopes configuration + # Turn scoped views on. Before rendering "sessions/new", it will first check for + # "users/sessions/new". It's turned off by default because it's slower if you + # are using only default views. + # config.scoped_views = false + + # Configure the default scope given to Warden. By default it's the first + # devise role declared in your routes (usually :user). + # config.default_scope = :user + + # Configure sign_out behavior. + # Sign_out action can be scoped (i.e. /users/sign_out affects only :user scope). + # The default is true, which means any logout action will sign out all active scopes. + # config.sign_out_all_scopes = true + + # ==> Navigation configuration + # Lists the formats that should be treated as navigational. Formats like + # :html, should redirect to the sign in page when the user does not have + # access, but formats like :xml or :json, should return 401. + # + # If you have any extra navigational formats, like :iphone or :mobile, you + # should add them to the navigational formats lists. + # + # The :"*/*" and "*/*" formats below is required to match Internet + # Explorer requests. + # config.navigational_formats = [:"*/*", "*/*", :html] + + # The default HTTP method used to sign out a resource. Default is :get. + # config.sign_out_via = :get + + # ==> OmniAuth + # Add a new OmniAuth provider. Check the wiki for more information on setting + # up on your models and hooks. + # config.omniauth :github, 'APP_ID', 'APP_SECRET', :scope => 'user,public_repo' + + # ==> Warden configuration + # If you want to use other strategies, that are not supported by Devise, or + # change the failure app, you can configure them inside the config.warden block. + # + # config.warden do |manager| + # manager.failure_app = AnotherApp + # manager.intercept_401 = false + # manager.default_strategies(:scope => :user).unshift :some_external_strategy + # end +end diff --git a/config/locales/devise.en.yml b/config/locales/devise.en.yml new file mode 100644 index 0000000..25022e1 --- /dev/null +++ b/config/locales/devise.en.yml @@ -0,0 +1,50 @@ +# Additional translations at http://github.com/plataformatec/devise/wiki/I18n + +en: + errors: + messages: + expired: "has expired, please request a new one" + not_found: "not found" + already_confirmed: "was already confirmed, please try signing in" + not_locked: "was not locked" + not_saved: + one: "1 error prohibited this %{resource} from being saved:" + other: "%{count} errors prohibited this %{resource} from being saved:" + + devise: + failure: + already_authenticated: 'You are already signed in.' + unauthenticated: 'You need to sign in or sign up before continuing.' + unconfirmed: 'You have to confirm your account before continuing.' + locked: 'Your account is locked.' + invalid: 'Invalid email or password.' + invalid_token: 'Invalid authentication token.' + timeout: 'Your session expired, please sign in again to continue.' + inactive: 'Your account was not activated yet.' + sessions: + signed_in: 'Signed in successfully.' + signed_out: 'Signed out successfully.' + passwords: + send_instructions: 'You will receive an email with instructions about how to reset your password in a few minutes.' + updated: 'Your password was changed successfully. You are now signed in.' + confirmations: + send_instructions: 'You will receive an email with instructions about how to confirm your account in a few minutes.' + confirmed: 'Your account was successfully confirmed. You are now signed in.' + registrations: + signed_up: 'Welcome! You have signed up successfully.' + inactive_signed_up: 'You have signed up successfully. However, we could not sign you in because your account is %{reason}.' + updated: 'You updated your account successfully.' + destroyed: 'Bye! Your account was successfully cancelled. We hope to see you again soon.' + unlocks: + send_instructions: 'You will receive an email with instructions about how to unlock your account in a few minutes.' + unlocked: 'Your account was successfully unlocked. You are now signed in.' + omniauth_callbacks: + success: 'Successfully authorized from %{kind} account.' + failure: 'Could not authorize you from %{kind} because "%{reason}".' + mailer: + confirmation_instructions: + subject: 'Confirmation instructions' + reset_password_instructions: + subject: 'Reset password instructions' + unlock_instructions: + subject: 'Unlock Instructions' diff --git a/config/routes.rb b/config/routes.rb index 2a67534..b113523 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,6 +1,8 @@ CSESocWebsite::Application.routes.draw do root :to => "news_items#index" + devise_for :users + resources :suggestions, :only => [:index, :show, :new, :create] resources :sponsors, :only => [:index] resources :events, :only => [:index, :show] diff --git a/db/migrate/20110507160439_devise_create_users.rb b/db/migrate/20110507160439_devise_create_users.rb new file mode 100644 index 0000000..430d7ba --- /dev/null +++ b/db/migrate/20110507160439_devise_create_users.rb @@ -0,0 +1,18 @@ +class DeviseCreateUsers < ActiveRecord::Migration + def self.up + create_table(:users) do |t| + t.token_authenticatable + t.rememberable + t.trackable + t.string :cse_username + + t.timestamps + end + + add_index :users, :authentication_token, :unique => true + end + + def self.down + drop_table :users + end +end diff --git a/db/schema.rb b/db/schema.rb index 5d1b79e..27dcae2 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20110507100554) do +ActiveRecord::Schema.define(:version => 20110507160439) do create_table "comments", :force => true do |t| t.integer "suggestion_id" @@ -89,4 +89,19 @@ t.datetime "updated_at" end + create_table "users", :force => true do |t| + t.string "authentication_token" + t.datetime "remember_created_at" + t.integer "sign_in_count", :default => 0 + t.datetime "current_sign_in_at" + t.datetime "last_sign_in_at" + t.string "current_sign_in_ip" + t.string "last_sign_in_ip" + t.string "cse_username" + t.datetime "created_at" + t.datetime "updated_at" + end + + add_index "users", ["authentication_token"], :name => "index_users_on_authentication_token", :unique => true + end diff --git a/db/seeds.rb b/db/seeds.rb index f9ab7a2..e789380 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -24,4 +24,4 @@ Static.create :title => "IRC", :content => "Irc.", :slug_name => "irc" Static.create :title => "Contact Us", :content => "Contact us.", :slug_name => "contact" -Sponsor.create :name => "UNSW CSE", :description => "UNSW Computer Science and Engineering", :website => "http://www.cse.unsw.edu.au", :alt_text => "CSE", :amount_paid => 1, :start_date => DateTime.parse("2011, 1, 1"), :expiry_date => DateTime.parse("2012, 1, 1") \ No newline at end of file +Sponsor.create :name => "UNSW CSE", :description => "UNSW Computer Science and Engineering", :website => "http://www.cse.unsw.edu.au", :alt_text => "CSE", :amount_paid => 1, :start_date => DateTime.parse("2011, 1, 1"), :expiry_date => DateTime.parse("2012, 1, 1") diff --git a/public/stylesheets/style.css b/public/stylesheets/style.css index 898c38d..4f90a84 100644 --- a/public/stylesheets/style.css +++ b/public/stylesheets/style.css @@ -64,6 +64,37 @@ h6 { position:relative; top:0; } +#nav_region_mid_right { + float:right; + height:38px; + padding:0 10px; + float:right; +} +#nav_region_mid_right .nav_tier1{ + margin:0; + padding:0; + list-style:none; +} +#nav_region_mid_right .nav_tier1>li{ + display:inline; + margin:0; + float:right; + margin-left:10px; + position:relative; +} +#nav_region_mid_right .nav_tier1>li a { + color:#333; + font-size:18px; + padding-top:5px; + padding-left:10px; + padding-right:10px; + display:block; +} +#nav_region_mid_right .nav_tier1>li a:hover { + color:#cd282f; +} + + #nav_region_centre .nav_tier1{ margin:0; padding:0; From 2b159654d8359a77f66d3412f102716fb9854554 Mon Sep 17 00:00:00 2001 From: Maxwell Swadling Date: Sun, 8 May 2011 00:41:27 +0800 Subject: [PATCH 29/55] Added Rails Admin. Signed-off-by: Maxwell Swadling --- Gemfile | 2 + Gemfile.lock | 18 ++++++ config/locales/rails_admin.en.yml | 60 +++++++++++++++++++ .../20110507163905_create_histories_table.rb | 18 ++++++ ...name_histories_to_rails_admin_histories.rb | 9 +++ db/schema.rb | 15 ++++- 6 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 config/locales/rails_admin.en.yml create mode 100644 db/migrate/20110507163905_create_histories_table.rb create mode 100644 db/migrate/20110507163906_rename_histories_to_rails_admin_histories.rb diff --git a/Gemfile b/Gemfile index 73d1183..539a6bd 100644 --- a/Gemfile +++ b/Gemfile @@ -8,6 +8,8 @@ gem 'friendly_id' gem 'paperclip' gem 'rdiscount' gem 'capistrano' +gem 'devise' +gem 'rails_admin', :git => 'git://github.com/sferik/rails_admin.git' # Use unicorn as the web server # gem 'unicorn' diff --git a/Gemfile.lock b/Gemfile.lock index 8f24286..0e4f268 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,3 +1,11 @@ +GIT + remote: git://github.com/sferik/rails_admin.git + revision: 319a23d4df85ec82cc023fb30c2ae60f5ceca214 + specs: + rails_admin (0.0.1) + builder (~> 2.1.0) + rails (~> 3.0.6) + GEM remote: http://rubygems.org/ specs: @@ -30,6 +38,7 @@ GEM activesupport (3.0.7) arel (2.0.9) babosa (0.3.3) + bcrypt-ruby (2.1.4) builder (2.1.2) capistrano (2.5.21) highline @@ -37,6 +46,10 @@ GEM net-sftp (>= 2.0.0) net-ssh (>= 2.0.14) net-ssh-gateway (>= 1.0.0) + devise (1.3.4) + bcrypt-ruby (~> 2.1.2) + orm_adapter (~> 0.0.3) + warden (~> 1.0.3) erubis (2.6.6) abstract (>= 1.0.0) friendly_id (3.2.1.1) @@ -56,6 +69,7 @@ GEM net-ssh (2.1.4) net-ssh-gateway (1.0.1) net-ssh (>= 1.99.1) + orm_adapter (0.0.4) paperclip (2.3.11) activerecord (>= 2.3.0) activesupport (>= 2.3.2) @@ -85,14 +99,18 @@ GEM treetop (1.4.9) polyglot (>= 0.3.1) tzinfo (0.3.27) + warden (1.0.4) + rack (>= 1.0) PLATFORMS ruby DEPENDENCIES capistrano + devise friendly_id paperclip rails (= 3.0.7) + rails_admin! rdiscount sqlite3 diff --git a/config/locales/rails_admin.en.yml b/config/locales/rails_admin.en.yml new file mode 100644 index 0000000..6a00c7f --- /dev/null +++ b/config/locales/rails_admin.en.yml @@ -0,0 +1,60 @@ +# Sample localization file for English. Add more files in this directory for other locales. +# See http://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. + +en: + home: + name: "home" + admin: + dashboard: + pagename: "Site administration" + name: "Dashboard" + model_name: "Model name" + last_used: "Last used" + records: "Records" + modify: "Modify" + add_new: "Add new" + show: "Show" + ago: "ago" + history: + name: "History" + page_name: "History for %{name}" + no_activity: "No Activity" + credentials: + log_out: "Log out" + list: + edit_action: "Edit" + delete_action: "Delete" + select_action: "Select" + delete_selected: "Delete selected" + add_new: "Add new" + search: "Search" + select: "Select %{name} to edit" + show_all: "Show all" + new: + basic_info: "Basic info" + required: "Required" + optional: "Optional" + one_char: "character." + many_chars: "characters or fewer." + chosen: "Chosen %{name}" + select_choice: "Select your choice(s) and click" + chose_all: "Choose all" + clear_all: "Clear all" + save: "Save" + save_and_add_another: "Save and add another" + save_and_edit: "Save and edit" + cancel: "Cancel" + delete: + flash_confirmation: "%{name} was successfully destroyed" + confirmation: "Yes, I'm sure" + flash: + successful: "%{name} was successfully %{action}" + error: "%{name} failed to be %{action}" + noaction: "No actions were taken" + actions: + create: "create" + created: "created" + update: "update" + updated: "updated" + delete: "delete" + deleted: "deleted" \ No newline at end of file diff --git a/db/migrate/20110507163905_create_histories_table.rb b/db/migrate/20110507163905_create_histories_table.rb new file mode 100644 index 0000000..7884b5d --- /dev/null +++ b/db/migrate/20110507163905_create_histories_table.rb @@ -0,0 +1,18 @@ +class CreateHistoriesTable < ActiveRecord::Migration + def self.up + create_table :histories do |t| + t.string :message # title, name, or object_id + t.string :username + t.integer :item + t.string :table + t.integer :month, :limit => 2 + t.integer :year, :limit => 5 + t.timestamps + end + add_index(:histories, [:item, :table, :month, :year]) + end + + def self.down + drop_table :histories + end +end diff --git a/db/migrate/20110507163906_rename_histories_to_rails_admin_histories.rb b/db/migrate/20110507163906_rename_histories_to_rails_admin_histories.rb new file mode 100644 index 0000000..b5a87f3 --- /dev/null +++ b/db/migrate/20110507163906_rename_histories_to_rails_admin_histories.rb @@ -0,0 +1,9 @@ +class RenameHistoriesToRailsAdminHistories < ActiveRecord::Migration + def self.up + rename_table :histories, :rails_admin_histories + end + + def self.down + rename_table :rails_admin_histories, :histories + end +end diff --git a/db/schema.rb b/db/schema.rb index 27dcae2..1e2bb99 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20110507160439) do +ActiveRecord::Schema.define(:version => 20110507163906) do create_table "comments", :force => true do |t| t.integer "suggestion_id" @@ -44,6 +44,19 @@ t.datetime "updated_at" end + create_table "rails_admin_histories", :force => true do |t| + t.string "message" + t.string "username" + t.integer "item" + t.string "table" + t.integer "month", :limit => 2 + t.integer "year", :limit => 5 + t.datetime "created_at" + t.datetime "updated_at" + end + + add_index "rails_admin_histories", ["item", "table", "month", "year"], :name => "index_histories_on_item_and_table_and_month_and_year" + create_table "slugs", :force => true do |t| t.string "name" t.integer "sluggable_id" From c359d75f87f5d1ab29c228e6a69e09d6dd0e5993 Mon Sep 17 00:00:00 2001 From: Maxwell Swadling Date: Sun, 8 May 2011 01:38:23 +0800 Subject: [PATCH 30/55] Added remote auth. Signed-off-by: Maxwell Swadling --- app/views/devise/sessions/new.html.erb | 14 -------------- app/views/shared/_header.html.erb | 1 + config/initializers/devise.rb | 2 +- config/routes.rb | 5 ++++- 4 files changed, 6 insertions(+), 16 deletions(-) delete mode 100644 app/views/devise/sessions/new.html.erb diff --git a/app/views/devise/sessions/new.html.erb b/app/views/devise/sessions/new.html.erb deleted file mode 100644 index 96e2025..0000000 --- a/app/views/devise/sessions/new.html.erb +++ /dev/null @@ -1,14 +0,0 @@ -

    Sign in

    - -<%= form_for(resource, :as => resource_name, :url => session_path(resource_name)) do |f| %> -

    <%= f.label :cse_username %>
    - <%= f.text_field :cse_username %>

    - - <% if devise_mapping.rememberable? -%> -

    <%= f.check_box :remember_me %> <%= f.label :remember_me %>

    - <% end -%> - -

    <%= f.submit "Sign in" %>

    -<% end %> - -<%= render :partial => "devise/shared/links" %> \ No newline at end of file diff --git a/app/views/shared/_header.html.erb b/app/views/shared/_header.html.erb index 242fe93..3cd4246 100644 --- a/app/views/shared/_header.html.erb +++ b/app/views/shared/_header.html.erb @@ -44,6 +44,7 @@ + diff --git a/public/stylesheets/scaffold.css b/public/stylesheets/scaffold.css index 31ada1a..14de1f0 100644 --- a/public/stylesheets/scaffold.css +++ b/public/stylesheets/scaffold.css @@ -43,3 +43,12 @@ div.field, div.actions { font-size: 12px; list-style: square; } + +.bar-text { + color: #88A5B4; + font-size: 18px; + padding-top: 5px; + padding-left: 10px; + padding-right: 10px; + display:block ; +} From 9bde9f2510e84c5aeb984800b0af1460819653ff Mon Sep 17 00:00:00 2001 From: Maxwell Swadling Date: Sun, 8 May 2011 17:30:35 +0800 Subject: [PATCH 33/55] All users must have an unique cse_username. Signed-off-by: Maxwell Swadling --- app/models/user.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/models/user.rb b/app/models/user.rb index aa83282..c1f49e5 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,6 +1,7 @@ class User < ActiveRecord::Base devise :token_authenticatable, :rememberable, :trackable + validates :cse_username, :presence => true, :uniqueness => true # Setup accessible (or protected) attributes for your model attr_accessible :remember_me end From 1cd40c09140232fe52e569fb71d9aa58b729cd95 Mon Sep 17 00:00:00 2001 From: Maxwell Swadling Date: Sun, 8 May 2011 18:05:18 +0800 Subject: [PATCH 34/55] Added roles. Signed-off-by: Maxwell Swadling --- Gemfile | 1 + Gemfile.lock | 2 ++ app/controllers/application_controller.rb | 4 ++++ app/models/ability.rb | 16 ++++++++++++++ app/models/event.rb | 10 +++------ app/models/news_item.rb | 2 +- app/models/user.rb | 21 +++++++++++++++++++ config/initializers/rails_admin.rb | 1 + .../20110507094824_create_news_items.rb | 2 +- db/migrate/20110507094950_create_events.rb | 2 +- .../20110508094121_add_role_to_users.rb | 9 ++++++++ db/schema.rb | 7 ++++--- 12 files changed, 64 insertions(+), 13 deletions(-) create mode 100644 app/models/ability.rb create mode 100644 config/initializers/rails_admin.rb create mode 100644 db/migrate/20110508094121_add_role_to_users.rb diff --git a/Gemfile b/Gemfile index 539a6bd..746a7cb 100644 --- a/Gemfile +++ b/Gemfile @@ -8,6 +8,7 @@ gem 'friendly_id' gem 'paperclip' gem 'rdiscount' gem 'capistrano' +gem "cancan" gem 'devise' gem 'rails_admin', :git => 'git://github.com/sferik/rails_admin.git' diff --git a/Gemfile.lock b/Gemfile.lock index 0e4f268..f5c5444 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -40,6 +40,7 @@ GEM babosa (0.3.3) bcrypt-ruby (2.1.4) builder (2.1.2) + cancan (1.6.4) capistrano (2.5.21) highline net-scp (>= 1.0.0) @@ -106,6 +107,7 @@ PLATFORMS ruby DEPENDENCIES + cancan capistrano devise friendly_id diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 31e600e..2ba25fa 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -2,6 +2,10 @@ class ApplicationController < ActionController::Base protect_from_forgery before_filter :load_sponsors + rescue_from CanCan::AccessDenied do |exception| + redirect_to root_url, :notice => exception.message + end + private def load_sponsors @sponsors = Sponsor.visible diff --git a/app/models/ability.rb b/app/models/ability.rb new file mode 100644 index 0000000..e21aba8 --- /dev/null +++ b/app/models/ability.rb @@ -0,0 +1,16 @@ +class Ability + include CanCan::Ability + + def initialize(user) + can :read, :all + + # Rails Admin + if user && user.admin? + can :access, :rails_admin + if user.role? :superadmin + can :manage, :all + end + end + + end +end diff --git a/app/models/event.rb b/app/models/event.rb index 276f2b1..a29de72 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -1,5 +1,5 @@ class Event < ActiveRecord::Base - belongs_to :author, :class_name => "User" + belongs_to :author, :class_name => "User", :foreign_key => "author_id" has_friendly_id :name, :use_slug => true @@ -10,12 +10,8 @@ class Event < ActiveRecord::Base validates :publish_date, :presence => true validates :author, :presence => true - validates :registration_required, :presence => true - validates :registration_email, - :format => {:with => /^([\w\.%\+\-]+)@([\w\-]+\.)+([\w]{2,})$/i} - validates :volunteers_required, :presence => true - validates :volunteers_email, - :format => {:with => /^([\w\.%\+\-]+)@([\w\-]+\.)+([\w]{2,})$/i} + validates_format_of :registration_email, :with => /^([\w\.%\+\-]+)@([\w\-]+\.)+([\w]{2,})$/i, :unless => lambda {|event| event.registration_email.blank?} + validates_format_of :volunteers_email, :with => /^([\w\.%\+\-]+)@([\w\-]+\.)+([\w]{2,})$/i, :unless => lambda {|event| event.volunteers_email.blank?} scope :published, lambda { where "events.publish_date <= ?", Time.now} diff --git a/app/models/news_item.rb b/app/models/news_item.rb index ed6e961..ed6f9c0 100644 --- a/app/models/news_item.rb +++ b/app/models/news_item.rb @@ -1,5 +1,5 @@ class NewsItem < ActiveRecord::Base - belongs_to :author, :class_name => "User" + belongs_to :author, :class_name => "User", :foreign_key => "author_id" has_friendly_id :title, :use_slug => true diff --git a/app/models/user.rb b/app/models/user.rb index c1f49e5..e04023d 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,7 +1,28 @@ class User < ActiveRecord::Base + ROLES = ['admin', 'superadmin'] + + has_many :events + has_many :comments + has_many :news_items + has_many :suggestions + devise :token_authenticatable, :rememberable, :trackable validates :cse_username, :presence => true, :uniqueness => true # Setup accessible (or protected) attributes for your model attr_accessible :remember_me + + # Determine email from CSE Username + def email + "#{cse_username}@cse.unsw.edu.au" + end + + # Checks if a user is an admin + def admin? + ROLES.include?(self.role) + end + + def role?(role) + self.role == role.to_s + end end diff --git a/config/initializers/rails_admin.rb b/config/initializers/rails_admin.rb new file mode 100644 index 0000000..32c68f5 --- /dev/null +++ b/config/initializers/rails_admin.rb @@ -0,0 +1 @@ +RailsAdmin.authorize_with :cancan \ No newline at end of file diff --git a/db/migrate/20110507094824_create_news_items.rb b/db/migrate/20110507094824_create_news_items.rb index e426db9..a174337 100644 --- a/db/migrate/20110507094824_create_news_items.rb +++ b/db/migrate/20110507094824_create_news_items.rb @@ -4,7 +4,7 @@ def self.up t.string :title t.text :content t.datetime :publish_date - t.integer :author + t.integer :author_id t.timestamps end diff --git a/db/migrate/20110507094950_create_events.rb b/db/migrate/20110507094950_create_events.rb index 9e72ad8..acc3969 100644 --- a/db/migrate/20110507094950_create_events.rb +++ b/db/migrate/20110507094950_create_events.rb @@ -10,7 +10,7 @@ def self.up t.string :volunteers_email t.text :description t.datetime :publish_date - t.integer :author + t.integer :author_id t.timestamps end diff --git a/db/migrate/20110508094121_add_role_to_users.rb b/db/migrate/20110508094121_add_role_to_users.rb new file mode 100644 index 0000000..9bdeb79 --- /dev/null +++ b/db/migrate/20110508094121_add_role_to_users.rb @@ -0,0 +1,9 @@ +class AddRoleToUsers < ActiveRecord::Migration + def self.up + add_column :users, :role, :string + end + + def self.down + remove_column :users, :role + end +end diff --git a/db/schema.rb b/db/schema.rb index 1e2bb99..f311e99 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20110507163906) do +ActiveRecord::Schema.define(:version => 20110508094121) do create_table "comments", :force => true do |t| t.integer "suggestion_id" @@ -30,7 +30,7 @@ t.string "volunteers_email" t.text "description" t.datetime "publish_date" - t.integer "author" + t.integer "author_id" t.datetime "created_at" t.datetime "updated_at" end @@ -39,7 +39,7 @@ t.string "title" t.text "content" t.datetime "publish_date" - t.integer "author" + t.integer "author_id" t.datetime "created_at" t.datetime "updated_at" end @@ -113,6 +113,7 @@ t.string "cse_username" t.datetime "created_at" t.datetime "updated_at" + t.string "role" end add_index "users", ["authentication_token"], :name => "index_users_on_authentication_token", :unique => true From e7f79df1dcf744d3e51c2cfe293d407f3a27f024 Mon Sep 17 00:00:00 2001 From: Maxwell Swadling Date: Sun, 8 May 2011 21:51:28 +0800 Subject: [PATCH 35/55] Updated Readme. Signed-off-by: Maxwell Swadling --- README | 299 ----------------------- README.md | 108 ++++++++ app/views/news_items/_news_item.html.erb | 2 +- 3 files changed, 109 insertions(+), 300 deletions(-) delete mode 100644 README create mode 100644 README.md diff --git a/README b/README deleted file mode 100644 index ee95e44..0000000 --- a/README +++ /dev/null @@ -1,299 +0,0 @@ -== CSESoc Website - -The UNSW CSESoc Website. - -== Auth Module - -Run this Ruby Sinatra script on the server. If a user gets to it - then they have been CSE authed and we can get their cse_username - from available params. - -This Sinatra app must have remote MySQL access to the database and - an ActiveRecord model and Schema for Users. - - u = User.where(:cse_username => params[:cse_username]) - if u.any? - u = u.first - else - u = User.new - u.cse_username = params[:cse_username] - end - token = User.authentication_token - u.authentication_token = token - u.save - redirect_to("http://csesoc.unsw.edu.au/callback?auth_token=#{token}") - -== License - - UNSW CSESoc Website - Copyright (C) 2011 UNSW CSESoc - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . - -== Welcome to Rails - -Rails is a web-application framework that includes everything needed to create -database-backed web applications according to the Model-View-Control pattern. - -This pattern splits the view (also called the presentation) into "dumb" -templates that are primarily responsible for inserting pre-built data in between -HTML tags. The model contains the "smart" domain objects (such as Account, -Product, Person, Post) that holds all the business logic and knows how to -persist themselves to a database. The controller handles the incoming requests -(such as Save New Account, Update Product, Show Post) by manipulating the model -and directing data to the view. - -In Rails, the model is handled by what's called an object-relational mapping -layer entitled Active Record. This layer allows you to present the data from -database rows as objects and embellish these data objects with business logic -methods. You can read more about Active Record in -link:files/vendor/rails/activerecord/README.html. - -The controller and view are handled by the Action Pack, which handles both -layers by its two parts: Action View and Action Controller. These two layers -are bundled in a single package due to their heavy interdependence. This is -unlike the relationship between the Active Record and Action Pack that is much -more separate. Each of these packages can be used independently outside of -Rails. You can read more about Action Pack in -link:files/vendor/rails/actionpack/README.html. - - -== Getting Started - -1. At the command prompt, create a new Rails application: - rails new myapp (where myapp is the application name) - -2. Change directory to myapp and start the web server: - cd myapp; rails server (run with --help for options) - -3. Go to http://localhost:3000/ and you'll see: - "Welcome aboard: You're riding Ruby on Rails!" - -4. Follow the guidelines to start developing your application. You can find -the following resources handy: - -* The Getting Started Guide: http://guides.rubyonrails.org/getting_started.html -* Ruby on Rails Tutorial Book: http://www.railstutorial.org/ - - -== Debugging Rails - -Sometimes your application goes wrong. Fortunately there are a lot of tools that -will help you debug it and get it back on the rails. - -First area to check is the application log files. Have "tail -f" commands -running on the server.log and development.log. Rails will automatically display -debugging and runtime information to these files. Debugging info will also be -shown in the browser on requests from 127.0.0.1. - -You can also log your own messages directly into the log file from your code -using the Ruby logger class from inside your controllers. Example: - - class WeblogController < ActionController::Base - def destroy - @weblog = Weblog.find(params[:id]) - @weblog.destroy - logger.info("#{Time.now} Destroyed Weblog ID ##{@weblog.id}!") - end - end - -The result will be a message in your log file along the lines of: - - Mon Oct 08 14:22:29 +1000 2007 Destroyed Weblog ID #1! - -More information on how to use the logger is at http://www.ruby-doc.org/core/ - -Also, Ruby documentation can be found at http://www.ruby-lang.org/. There are -several books available online as well: - -* Programming Ruby: http://www.ruby-doc.org/docs/ProgrammingRuby/ (Pickaxe) -* Learn to Program: http://pine.fm/LearnToProgram/ (a beginners guide) - -These two books will bring you up to speed on the Ruby language and also on -programming in general. - - -== Debugger - -Debugger support is available through the debugger command when you start your -Mongrel or WEBrick server with --debugger. This means that you can break out of -execution at any point in the code, investigate and change the model, and then, -resume execution! You need to install ruby-debug to run the server in debugging -mode. With gems, use sudo gem install ruby-debug. Example: - - class WeblogController < ActionController::Base - def index - @posts = Post.find(:all) - debugger - end - end - -So the controller will accept the action, run the first line, then present you -with a IRB prompt in the server window. Here you can do things like: - - >> @posts.inspect - => "[#nil, "body"=>nil, "id"=>"1"}>, - #"Rails", "body"=>"Only ten..", "id"=>"2"}>]" - >> @posts.first.title = "hello from a debugger" - => "hello from a debugger" - -...and even better, you can examine how your runtime objects actually work: - - >> f = @posts.first - => #nil, "body"=>nil, "id"=>"1"}> - >> f. - Display all 152 possibilities? (y or n) - -Finally, when you're ready to resume execution, you can enter "cont". - - -== Console - -The console is a Ruby shell, which allows you to interact with your -application's domain model. Here you'll have all parts of the application -configured, just like it is when the application is running. You can inspect -domain models, change values, and save to the database. Starting the script -without arguments will launch it in the development environment. - -To start the console, run rails console from the application -directory. - -Options: - -* Passing the -s, --sandbox argument will rollback any modifications - made to the database. -* Passing an environment name as an argument will load the corresponding - environment. Example: rails console production. - -To reload your controllers and models after launching the console run -reload! - -More information about irb can be found at: -link:http://www.rubycentral.com/pickaxe/irb.html - - -== dbconsole - -You can go to the command line of your database directly through rails -dbconsole. You would be connected to the database with the credentials -defined in database.yml. Starting the script without arguments will connect you -to the development database. Passing an argument will connect you to a different -database, like rails dbconsole production. Currently works for MySQL, -PostgreSQL and SQLite 3. - -== Description of Contents - -The default directory structure of a generated Ruby on Rails application: - - |-- app - | |-- controllers - | |-- helpers - | |-- mailers - | |-- models - | `-- views - | `-- layouts - |-- config - | |-- environments - | |-- initializers - | `-- locales - |-- db - |-- doc - |-- lib - | `-- tasks - |-- log - |-- public - | |-- images - | |-- javascripts - | `-- stylesheets - |-- script - |-- test - | |-- fixtures - | |-- functional - | |-- integration - | |-- performance - | `-- unit - |-- tmp - | |-- cache - | |-- pids - | |-- sessions - | `-- sockets - `-- vendor - `-- plugins - -app - Holds all the code that's specific to this particular application. - -app/controllers - Holds controllers that should be named like weblogs_controller.rb for - automated URL mapping. All controllers should descend from - ApplicationController which itself descends from ActionController::Base. - -app/models - Holds models that should be named like post.rb. Models descend from - ActiveRecord::Base by default. - -app/views - Holds the template files for the view that should be named like - weblogs/index.html.erb for the WeblogsController#index action. All views use - eRuby syntax by default. - -app/views/layouts - Holds the template files for layouts to be used with views. This models the - common header/footer method of wrapping views. In your views, define a layout - using the layout :default and create a file named default.html.erb. - Inside default.html.erb, call <% yield %> to render the view using this - layout. - -app/helpers - Holds view helpers that should be named like weblogs_helper.rb. These are - generated for you automatically when using generators for controllers. - Helpers can be used to wrap functionality for your views into methods. - -config - Configuration files for the Rails environment, the routing map, the database, - and other dependencies. - -db - Contains the database schema in schema.rb. db/migrate contains all the - sequence of Migrations for your schema. - -doc - This directory is where your application documentation will be stored when - generated using rake doc:app - -lib - Application specific libraries. Basically, any kind of custom code that - doesn't belong under controllers, models, or helpers. This directory is in - the load path. - -public - The directory available for the web server. Contains subdirectories for - images, stylesheets, and javascripts. Also contains the dispatchers and the - default HTML files. This should be set as the DOCUMENT_ROOT of your web - server. - -script - Helper scripts for automation and generation. - -test - Unit and functional tests along with fixtures. When using the rails generate - command, template test files will be generated for you and placed in this - directory. - -vendor - External libraries that the application depends on. Also includes the plugins - subdirectory. If the app has frozen rails, those gems also go here, under - vendor/rails/. This directory is in the load path. diff --git a/README.md b/README.md new file mode 100644 index 0000000..12a3978 --- /dev/null +++ b/README.md @@ -0,0 +1,108 @@ +UNSW CSESoc Website +=================== + +Auth Module +----------- + +A script similar to this needs to run on the CSESoc account on +a CSE Server to authenticate users. + + # Find the user if it exists + u = User.where(:cse_username => params[:cse_username]) + if u.any? + u = u.first + else + u = User.new + u.cse_username = params[:cse_username] + end + + # Give them a new token + token = User.authentication_token + u.authentication_token = token + u.save + + # Redirect them back with the new auth token + redirect_to("http://csesoc.unsw.edu.au/callback?auth_token=#{token}") + +Changes from Django +------------------- + +This list may be incomplete. + +### Models + +- User Model + - Added + - Added user sessions + - TODO: Facebook integration +- Static Model + - Text is now Content + - Removed creator + - Removed updater + - Now uses Markdown +- NewsItem Model + - Headline is now Title + - Text is now Content + - pubdate is now publish_date + - Now uses Markdown + - TODO: RSS Feed +- Event Model + - TODO: implement controller and views + - TODO: iCal RSS feed +- Beta Model + - Removed +- Sponsor Model + - Added Attachment + - TODO: Resize sizes and layouts +- Suggestion Model + - Changed sender to user_id + - TODO: Needs Comment nested controller and routes +- Comment Model + - name is now user_id + - suggestion to now suggestion_id + +### Design + +- Made titles of news posts links to articles +- Made Cal and Sponsors only show on home page +- Added a notice flash +- TODO: Javascript notice flash +- A lot of logic in views was removed +- Fixed murder players been different from CSE users +- Can now log out properly +- No passwords or emails, only cse auth + +### Development + +- Added Capistrano, anyone with ssh key can deploy +- Added migrations for DB +- Added DB seeds + +### TODO + +- Murder +- Scheduler +- Campleaders +- Campattendees +- rspec +- Music +- Game + +License +------- + + UNSW CSESoc Website + Copyright (C) 2011 UNSW CSESoc + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . \ No newline at end of file diff --git a/app/views/news_items/_news_item.html.erb b/app/views/news_items/_news_item.html.erb index 81a53c7..8a282fa 100644 --- a/app/views/news_items/_news_item.html.erb +++ b/app/views/news_items/_news_item.html.erb @@ -5,7 +5,7 @@ <%= news_item.publish_date.month %> <%= news_item.publish_date.year %> @ <%= news_item.publish_date.strftime("%H:%M") %> - by <%#= news_item.author.name %> + by <%= news_item.author.cse_username %>
    From 0248804ad5517d96061d86cb4479bda3ed8a0286 Mon Sep 17 00:00:00 2001 From: Maxwell Swadling Date: Sat, 14 May 2011 20:17:43 +0800 Subject: [PATCH 36/55] Improved readme. Signed-off-by: Maxwell Swadling --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index 12a3978..252c83c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,17 @@ UNSW CSESoc Website =================== +How To Setup +------------ + + rvm install 1.9.2 + rvm use 1.9.2 --default + + bundle install + rake db:migrate + rake db:seed + rails server + Auth Module ----------- From e169ea58a34896d9ca7d51a8a89291dcefb669c8 Mon Sep 17 00:00:00 2001 From: Maxwell Swadling Date: Sat, 14 May 2011 20:19:12 +0800 Subject: [PATCH 37/55] Added route for comments. Signed-off-by: Maxwell Swadling --- config/routes.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/config/routes.rb b/config/routes.rb index ebb1f00..c08ffec 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -6,12 +6,17 @@ get "callback", :to => "devise/sessions#create" end - resources :suggestions, :only => [:index, :show, :new, :create] resources :sponsors, :only => [:index] resources :events, :only => [:index, :show] resources :news_items, :only => [:index, :show] resources :statics, :only => [:show] + # Suggestions + resources :suggestions, :only => [:index, :show, :new, :create] do + # Comments on suggestions + resources :comments, :only => [:create] + end + # The priority is based upon order of creation: # first created -> highest priority. From 0741b4d94f640695eb136f5e586179f0e99798d5 Mon Sep 17 00:00:00 2001 From: Maxwell Swadling Date: Sat, 14 May 2011 20:27:17 +0800 Subject: [PATCH 38/55] Added comments controller. Signed-off-by: Maxwell Swadling --- app/controllers/comments_controller.rb | 22 ++++++++++++++++++++++ app/models/comment.rb | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 app/controllers/comments_controller.rb diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb new file mode 100644 index 0000000..bb0c36b --- /dev/null +++ b/app/controllers/comments_controller.rb @@ -0,0 +1,22 @@ +class CommentsController < ApplicationController + def create + @comment = Comment.new(params[:comment]) + + @comment.suggestion = Suggestion.find(params[:suggestion_id]) + @comment.user = current_user + + if @comment.save + respond_to do |f| + f.html {redirect_to @comment.suggestion, :notice => "Thanks for your comment!"} + f.js {} + end + else + respond_to do |f| + f.html {redirect_to @comment.suggestion, :notice => "Sorry, your comment could not be saved."} + f.js {@comment.errors} + end + end + + end + +end \ No newline at end of file diff --git a/app/models/comment.rb b/app/models/comment.rb index c95ed58..8e47d0b 100644 --- a/app/models/comment.rb +++ b/app/models/comment.rb @@ -2,7 +2,7 @@ class Comment < ActiveRecord::Base belongs_to :suggestion belongs_to :user - validates :suggestion_id, :presence => true + validates :suggestion, :presence => true validates :user, :presence => true validates :comment, :presence => true end From cde172420798c5de4aaf669569f39f04d09874e1 Mon Sep 17 00:00:00 2001 From: Maxwell Swadling Date: Sat, 14 May 2011 20:30:03 +0800 Subject: [PATCH 39/55] Added improved view of suggestions. Signed-off-by: Maxwell Swadling --- app/controllers/comments_controller.rb | 4 ++-- app/views/suggestions/show.html.erb | 30 ++++++++++++++++---------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb index bb0c36b..defd5d6 100644 --- a/app/controllers/comments_controller.rb +++ b/app/controllers/comments_controller.rb @@ -8,12 +8,12 @@ def create if @comment.save respond_to do |f| f.html {redirect_to @comment.suggestion, :notice => "Thanks for your comment!"} - f.js {} + f.js # Renders RJS template end else respond_to do |f| f.html {redirect_to @comment.suggestion, :notice => "Sorry, your comment could not be saved."} - f.js {@comment.errors} + f.js # Renders RJS template end end diff --git a/app/views/suggestions/show.html.erb b/app/views/suggestions/show.html.erb index a2a802b..112523a 100644 --- a/app/views/suggestions/show.html.erb +++ b/app/views/suggestions/show.html.erb @@ -1,20 +1,28 @@ - -

    - Subject: +

    <%= @suggestion.subject %> -

    +

    +

    + <%= @suggestion.user.cse_username %> +

    - Message: - <%= @suggestion.message %> + <%= RDiscount.new(@suggestion.message).to_html.html_safe %>

    -

    - User: - <%= @suggestion.user_id %> -

    +

    + Comments +

    + +<% @suggestion.comments.each do |comment| %> +

    <%= comment.user.cse_username %>

    +

    + <%= comment.comment %> +

    +<% end %> +<%= form_for [@suggestion, @suggestion.comments.new], :remote => true do |f| %> +<%= f.text_field :comment %> +<% end %> -<%= link_to 'Edit', edit_suggestion_path(@suggestion) %> | <%= link_to 'Back', suggestions_path %> From 7f8ec12ddedaa1779aefd520c6aa049dfe10cd8d Mon Sep 17 00:00:00 2001 From: Ritwik Roy Date: Sat, 14 May 2011 22:53:35 +1000 Subject: [PATCH 40/55] Replaced suggestions index table with index list. Yay good html practices --- app/controllers/suggestions_controller.rb | 3 ++- app/views/suggestions/_form.html.erb | 4 --- app/views/suggestions/index.html.erb | 31 ++++++++--------------- db/schema.rb | 4 +-- 4 files changed, 14 insertions(+), 28 deletions(-) diff --git a/app/controllers/suggestions_controller.rb b/app/controllers/suggestions_controller.rb index 4cc811a..4abfb7a 100644 --- a/app/controllers/suggestions_controller.rb +++ b/app/controllers/suggestions_controller.rb @@ -13,7 +13,8 @@ def new def create @suggestion = Suggestion.new(params[:suggestion]) - + @suggestion.user = current_user + if @suggestion.save redirect_to(@suggestion, :notice => 'Thanks for your suggestion.') else diff --git a/app/views/suggestions/_form.html.erb b/app/views/suggestions/_form.html.erb index 176a721..32eb709 100644 --- a/app/views/suggestions/_form.html.erb +++ b/app/views/suggestions/_form.html.erb @@ -19,10 +19,6 @@ <%= f.label :message %>
    <%= f.text_area :message %>
    -
    - <%= f.label :user_id %>
    - <%= f.text_field :user_id %> -
    <%= f.submit %>
    diff --git a/app/views/suggestions/index.html.erb b/app/views/suggestions/index.html.erb index c660ea9..b733e51 100644 --- a/app/views/suggestions/index.html.erb +++ b/app/views/suggestions/index.html.erb @@ -1,26 +1,15 @@

    Listing suggestions

    - - - - - - - - - - -<% @suggestions.each do |suggestion| %> - - - - - - - - -<% end %> -
    SubjectMessageUser
    <%= suggestion.subject %><%= suggestion.message %><%= suggestion.user_id %><%= link_to 'Show', suggestion %><%= link_to 'Edit', edit_suggestion_path(suggestion) %><%= link_to 'Destroy', suggestion, :confirm => 'Are you sure?', :method => :delete %>
    +
      + <% @suggestions.each do |suggestion|%> +
    • +
      <%= suggestion.subject %>
      +
      <%= suggestion.message.truncate(200) %>
      +
      <%= suggestion.user_id %>
      + <%=link_to 'Show',suggestion %> +
    • + <%end> +

    diff --git a/db/schema.rb b/db/schema.rb index f311e99..5fc1044 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -30,7 +30,7 @@ t.string "volunteers_email" t.text "description" t.datetime "publish_date" - t.integer "author_id" + t.integer "author" t.datetime "created_at" t.datetime "updated_at" end @@ -39,7 +39,7 @@ t.string "title" t.text "content" t.datetime "publish_date" - t.integer "author_id" + t.integer "author" t.datetime "created_at" t.datetime "updated_at" end From 26c7e6de5c6f589d0d73e928889e96b4820e25e1 Mon Sep 17 00:00:00 2001 From: Ritwik Roy Date: Sat, 14 May 2011 22:55:29 +1000 Subject: [PATCH 41/55] Fixed small typo that causes an error --- app/views/suggestions/index.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/suggestions/index.html.erb b/app/views/suggestions/index.html.erb index b733e51..524997a 100644 --- a/app/views/suggestions/index.html.erb +++ b/app/views/suggestions/index.html.erb @@ -8,7 +8,7 @@
    <%= suggestion.user_id %>
    <%=link_to 'Show',suggestion %>
  • - <%end> + <%end%>
    From 8ee8bbca7a70f7e5cb6988885ebdd26ac4fc85dc Mon Sep 17 00:00:00 2001 From: Maxwell Swadling Date: Sat, 14 May 2011 20:52:36 +0800 Subject: [PATCH 42/55] Added Ajax comment. Signed-off-by: Maxwell Swadling --- app/views/comments/_comment.html.erb | 6 ++++++ app/views/comments/create.js.rjs | 7 +++++++ app/views/suggestions/show.html.erb | 10 +++++----- 3 files changed, 18 insertions(+), 5 deletions(-) create mode 100644 app/views/comments/_comment.html.erb create mode 100644 app/views/comments/create.js.rjs diff --git a/app/views/comments/_comment.html.erb b/app/views/comments/_comment.html.erb new file mode 100644 index 0000000..f9cccd1 --- /dev/null +++ b/app/views/comments/_comment.html.erb @@ -0,0 +1,6 @@ +
    +

    <%= comment.user.cse_username %>

    +

    + <%= comment.comment %> +

    +
    \ No newline at end of file diff --git a/app/views/comments/create.js.rjs b/app/views/comments/create.js.rjs new file mode 100644 index 0000000..455940f --- /dev/null +++ b/app/views/comments/create.js.rjs @@ -0,0 +1,7 @@ +if @comment.errors.any? + # Insert errors + page[:comment_errors].replace_html @comment.errors.full_messages.to_sentence +else + # Comment saved + page[:new_comment].replace_html :partial => @comment +end diff --git a/app/views/suggestions/show.html.erb b/app/views/suggestions/show.html.erb index 112523a..1a5ae6c 100644 --- a/app/views/suggestions/show.html.erb +++ b/app/views/suggestions/show.html.erb @@ -15,14 +15,14 @@ <% @suggestion.comments.each do |comment| %> -

    <%= comment.user.cse_username %>

    -

    - <%= comment.comment %> -

    + <%= render comment %> <% end %> <%= form_for [@suggestion, @suggestion.comments.new], :remote => true do |f| %> -<%= f.text_field :comment %> +
    +
    + <%= f.text_field :comment %> + <%= f.submit %> <% end %> <%= link_to 'Back', suggestions_path %> From ad70918e61dd1d88864aa4fd26b2ee921bdef6ef Mon Sep 17 00:00:00 2001 From: Ritwik Roy Date: Sat, 14 May 2011 23:11:11 +1000 Subject: [PATCH 43/55] Fixed up programming style issues by adding spaces --- app/views/suggestions/index.html.erb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/suggestions/index.html.erb b/app/views/suggestions/index.html.erb index 524997a..a19fe2e 100644 --- a/app/views/suggestions/index.html.erb +++ b/app/views/suggestions/index.html.erb @@ -1,14 +1,14 @@

    Listing suggestions

      - <% @suggestions.each do |suggestion|%> + <% @suggestions.each do |suggestion| %>
    • <%= suggestion.subject %>
      <%= suggestion.message.truncate(200) %>
      <%= suggestion.user_id %>
      - <%=link_to 'Show',suggestion %> + <%= link_to 'See more details', suggestion %>
    • - <%end%> + <% end %>

    From 95cc154ab19b049d4d3d6e245d9bc94fcd48fe27 Mon Sep 17 00:00:00 2001 From: Ritwik Roy Date: Sun, 8 May 2011 01:44:13 +1000 Subject: [PATCH 44/55] Added favicon.ico as a conversion from icon.png --- public/favicon.ico | Bin 0 -> 1150 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/public/favicon.ico b/public/favicon.ico index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..e6555d048f9cde0fa391615fdf51619aa1966580 100644 GIT binary patch literal 1150 zcmbtTTT4_?6h1;fz$d8}vztwM%ZFZi$%qOXP4kkP%)^*7<7LLlFv|i_%x+ZRWMHKk zrDH=E(#grB7&QmVrVKU75V8_0L%)*jZJz1QCB+uvI2+lmrGztB*H zMud{TOi{vUW*}o%1X+muD@p?G{XOhiu_cbIxH)@fEZAs{qJ1$aNZ%-PUW;Gbqx0e? zAH~Eu9hq@+e1?r*xn`FI!p`DYpiZg51d_@`2g7N;{Ab3oSkK*^Q zAGq#10<&=?inHVOd*$m`UUd3e#+0Z-!z%+JrG;ld%)}+l!I+=B#v6PBJlVQ+FM=l`MM|!v|4zmb&Qqx{4@i;e*S=_+A@msV&run z#zunZYHLKduSIarlxJb;%SV)$HdA~Iv_42YV{*Pdovmo5J09I{z`5f&@LW5I>XKx% zxoc%T{HhnH%F?O7(P}NUzUQYJB;TLkXNa>K{*GqqpAA*cL<|P~gzH9OMigegO{0=} z%e7Q{sOpC2jB(h!1;Kz1(^Hdp+T{V)rM4>yE!> zW4QFXFg*&+{qe-*CdS8x;J#dmob6$_dj23DcM_kou_zB_n9gNX&R(yH9uYR^ku#S&$_V4UCv1TbuH@mFJ0?C zYf2=%#M^u8;(`{JYKUW0E^0lr{t6ErgEfA;>9o8F&WrrB6sNsk=A*t%l|SPVmR4V> UF~3LZ2^+mle)CR60shDS0ED(;H2?qr literal 0 HcmV?d00001 From 16cab48e09d5958c70d400c482a3290850927462 Mon Sep 17 00:00:00 2001 From: Maxwell Swadling Date: Sun, 8 May 2011 03:38:23 +1000 Subject: [PATCH 45/55] Added remote auth. Signed-off-by: Maxwell Swadling --- app/views/devise/sessions/new.html.erb | 14 -------------- app/views/shared/_header.html.erb | 1 + config/initializers/devise.rb | 2 +- config/routes.rb | 5 ++++- 4 files changed, 6 insertions(+), 16 deletions(-) delete mode 100644 app/views/devise/sessions/new.html.erb diff --git a/app/views/devise/sessions/new.html.erb b/app/views/devise/sessions/new.html.erb deleted file mode 100644 index 96e2025..0000000 --- a/app/views/devise/sessions/new.html.erb +++ /dev/null @@ -1,14 +0,0 @@ -

    Sign in

    - -<%= form_for(resource, :as => resource_name, :url => session_path(resource_name)) do |f| %> -

    <%= f.label :cse_username %>
    - <%= f.text_field :cse_username %>

    - - <% if devise_mapping.rememberable? -%> -

    <%= f.check_box :remember_me %> <%= f.label :remember_me %>

    - <% end -%> - -

    <%= f.submit "Sign in" %>

    -<% end %> - -<%= render :partial => "devise/shared/links" %> \ No newline at end of file diff --git a/app/views/shared/_header.html.erb b/app/views/shared/_header.html.erb index 242fe93..3cd4246 100644 --- a/app/views/shared/_header.html.erb +++ b/app/views/shared/_header.html.erb @@ -44,6 +44,7 @@ + diff --git a/public/stylesheets/scaffold.css b/public/stylesheets/scaffold.css index 31ada1a..14de1f0 100644 --- a/public/stylesheets/scaffold.css +++ b/public/stylesheets/scaffold.css @@ -43,3 +43,12 @@ div.field, div.actions { font-size: 12px; list-style: square; } + +.bar-text { + color: #88A5B4; + font-size: 18px; + padding-top: 5px; + padding-left: 10px; + padding-right: 10px; + display:block ; +} From 672b88e806a5b6e5e8f3e1ab304bd358b792c7bc Mon Sep 17 00:00:00 2001 From: Maxwell Swadling Date: Sun, 8 May 2011 19:30:35 +1000 Subject: [PATCH 48/55] All users must have an unique cse_username. Signed-off-by: Maxwell Swadling --- app/models/user.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/models/user.rb b/app/models/user.rb index aa83282..c1f49e5 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,6 +1,7 @@ class User < ActiveRecord::Base devise :token_authenticatable, :rememberable, :trackable + validates :cse_username, :presence => true, :uniqueness => true # Setup accessible (or protected) attributes for your model attr_accessible :remember_me end From 7d407d640603624cc5b238c233ac9917b5779179 Mon Sep 17 00:00:00 2001 From: Maxwell Swadling Date: Sun, 8 May 2011 20:05:18 +1000 Subject: [PATCH 49/55] Added roles. Signed-off-by: Maxwell Swadling --- Gemfile | 1 + Gemfile.lock | 2 ++ app/controllers/application_controller.rb | 4 ++++ app/models/ability.rb | 16 ++++++++++++++ app/models/event.rb | 10 +++------ app/models/news_item.rb | 2 +- app/models/user.rb | 21 +++++++++++++++++++ config/initializers/rails_admin.rb | 1 + .../20110507094824_create_news_items.rb | 2 +- db/migrate/20110507094950_create_events.rb | 2 +- .../20110508094121_add_role_to_users.rb | 9 ++++++++ db/schema.rb | 7 ++++--- 12 files changed, 64 insertions(+), 13 deletions(-) create mode 100644 app/models/ability.rb create mode 100644 config/initializers/rails_admin.rb create mode 100644 db/migrate/20110508094121_add_role_to_users.rb diff --git a/Gemfile b/Gemfile index 539a6bd..746a7cb 100644 --- a/Gemfile +++ b/Gemfile @@ -8,6 +8,7 @@ gem 'friendly_id' gem 'paperclip' gem 'rdiscount' gem 'capistrano' +gem "cancan" gem 'devise' gem 'rails_admin', :git => 'git://github.com/sferik/rails_admin.git' diff --git a/Gemfile.lock b/Gemfile.lock index 0e4f268..f5c5444 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -40,6 +40,7 @@ GEM babosa (0.3.3) bcrypt-ruby (2.1.4) builder (2.1.2) + cancan (1.6.4) capistrano (2.5.21) highline net-scp (>= 1.0.0) @@ -106,6 +107,7 @@ PLATFORMS ruby DEPENDENCIES + cancan capistrano devise friendly_id diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 31e600e..2ba25fa 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -2,6 +2,10 @@ class ApplicationController < ActionController::Base protect_from_forgery before_filter :load_sponsors + rescue_from CanCan::AccessDenied do |exception| + redirect_to root_url, :notice => exception.message + end + private def load_sponsors @sponsors = Sponsor.visible diff --git a/app/models/ability.rb b/app/models/ability.rb new file mode 100644 index 0000000..e21aba8 --- /dev/null +++ b/app/models/ability.rb @@ -0,0 +1,16 @@ +class Ability + include CanCan::Ability + + def initialize(user) + can :read, :all + + # Rails Admin + if user && user.admin? + can :access, :rails_admin + if user.role? :superadmin + can :manage, :all + end + end + + end +end diff --git a/app/models/event.rb b/app/models/event.rb index 276f2b1..a29de72 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -1,5 +1,5 @@ class Event < ActiveRecord::Base - belongs_to :author, :class_name => "User" + belongs_to :author, :class_name => "User", :foreign_key => "author_id" has_friendly_id :name, :use_slug => true @@ -10,12 +10,8 @@ class Event < ActiveRecord::Base validates :publish_date, :presence => true validates :author, :presence => true - validates :registration_required, :presence => true - validates :registration_email, - :format => {:with => /^([\w\.%\+\-]+)@([\w\-]+\.)+([\w]{2,})$/i} - validates :volunteers_required, :presence => true - validates :volunteers_email, - :format => {:with => /^([\w\.%\+\-]+)@([\w\-]+\.)+([\w]{2,})$/i} + validates_format_of :registration_email, :with => /^([\w\.%\+\-]+)@([\w\-]+\.)+([\w]{2,})$/i, :unless => lambda {|event| event.registration_email.blank?} + validates_format_of :volunteers_email, :with => /^([\w\.%\+\-]+)@([\w\-]+\.)+([\w]{2,})$/i, :unless => lambda {|event| event.volunteers_email.blank?} scope :published, lambda { where "events.publish_date <= ?", Time.now} diff --git a/app/models/news_item.rb b/app/models/news_item.rb index ed6e961..ed6f9c0 100644 --- a/app/models/news_item.rb +++ b/app/models/news_item.rb @@ -1,5 +1,5 @@ class NewsItem < ActiveRecord::Base - belongs_to :author, :class_name => "User" + belongs_to :author, :class_name => "User", :foreign_key => "author_id" has_friendly_id :title, :use_slug => true diff --git a/app/models/user.rb b/app/models/user.rb index c1f49e5..e04023d 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,7 +1,28 @@ class User < ActiveRecord::Base + ROLES = ['admin', 'superadmin'] + + has_many :events + has_many :comments + has_many :news_items + has_many :suggestions + devise :token_authenticatable, :rememberable, :trackable validates :cse_username, :presence => true, :uniqueness => true # Setup accessible (or protected) attributes for your model attr_accessible :remember_me + + # Determine email from CSE Username + def email + "#{cse_username}@cse.unsw.edu.au" + end + + # Checks if a user is an admin + def admin? + ROLES.include?(self.role) + end + + def role?(role) + self.role == role.to_s + end end diff --git a/config/initializers/rails_admin.rb b/config/initializers/rails_admin.rb new file mode 100644 index 0000000..32c68f5 --- /dev/null +++ b/config/initializers/rails_admin.rb @@ -0,0 +1 @@ +RailsAdmin.authorize_with :cancan \ No newline at end of file diff --git a/db/migrate/20110507094824_create_news_items.rb b/db/migrate/20110507094824_create_news_items.rb index e426db9..a174337 100644 --- a/db/migrate/20110507094824_create_news_items.rb +++ b/db/migrate/20110507094824_create_news_items.rb @@ -4,7 +4,7 @@ def self.up t.string :title t.text :content t.datetime :publish_date - t.integer :author + t.integer :author_id t.timestamps end diff --git a/db/migrate/20110507094950_create_events.rb b/db/migrate/20110507094950_create_events.rb index 9e72ad8..acc3969 100644 --- a/db/migrate/20110507094950_create_events.rb +++ b/db/migrate/20110507094950_create_events.rb @@ -10,7 +10,7 @@ def self.up t.string :volunteers_email t.text :description t.datetime :publish_date - t.integer :author + t.integer :author_id t.timestamps end diff --git a/db/migrate/20110508094121_add_role_to_users.rb b/db/migrate/20110508094121_add_role_to_users.rb new file mode 100644 index 0000000..9bdeb79 --- /dev/null +++ b/db/migrate/20110508094121_add_role_to_users.rb @@ -0,0 +1,9 @@ +class AddRoleToUsers < ActiveRecord::Migration + def self.up + add_column :users, :role, :string + end + + def self.down + remove_column :users, :role + end +end diff --git a/db/schema.rb b/db/schema.rb index 1e2bb99..f311e99 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20110507163906) do +ActiveRecord::Schema.define(:version => 20110508094121) do create_table "comments", :force => true do |t| t.integer "suggestion_id" @@ -30,7 +30,7 @@ t.string "volunteers_email" t.text "description" t.datetime "publish_date" - t.integer "author" + t.integer "author_id" t.datetime "created_at" t.datetime "updated_at" end @@ -39,7 +39,7 @@ t.string "title" t.text "content" t.datetime "publish_date" - t.integer "author" + t.integer "author_id" t.datetime "created_at" t.datetime "updated_at" end @@ -113,6 +113,7 @@ t.string "cse_username" t.datetime "created_at" t.datetime "updated_at" + t.string "role" end add_index "users", ["authentication_token"], :name => "index_users_on_authentication_token", :unique => true From ec40db4272574d8af33b0dd7171cd61ce0f862d6 Mon Sep 17 00:00:00 2001 From: Maxwell Swadling Date: Sun, 8 May 2011 23:51:28 +1000 Subject: [PATCH 50/55] Updated Readme. Signed-off-by: Maxwell Swadling --- README | 299 ----------------------- README.md | 108 ++++++++ app/views/news_items/_news_item.html.erb | 2 +- 3 files changed, 109 insertions(+), 300 deletions(-) delete mode 100644 README create mode 100644 README.md diff --git a/README b/README deleted file mode 100644 index ee95e44..0000000 --- a/README +++ /dev/null @@ -1,299 +0,0 @@ -== CSESoc Website - -The UNSW CSESoc Website. - -== Auth Module - -Run this Ruby Sinatra script on the server. If a user gets to it - then they have been CSE authed and we can get their cse_username - from available params. - -This Sinatra app must have remote MySQL access to the database and - an ActiveRecord model and Schema for Users. - - u = User.where(:cse_username => params[:cse_username]) - if u.any? - u = u.first - else - u = User.new - u.cse_username = params[:cse_username] - end - token = User.authentication_token - u.authentication_token = token - u.save - redirect_to("http://csesoc.unsw.edu.au/callback?auth_token=#{token}") - -== License - - UNSW CSESoc Website - Copyright (C) 2011 UNSW CSESoc - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . - -== Welcome to Rails - -Rails is a web-application framework that includes everything needed to create -database-backed web applications according to the Model-View-Control pattern. - -This pattern splits the view (also called the presentation) into "dumb" -templates that are primarily responsible for inserting pre-built data in between -HTML tags. The model contains the "smart" domain objects (such as Account, -Product, Person, Post) that holds all the business logic and knows how to -persist themselves to a database. The controller handles the incoming requests -(such as Save New Account, Update Product, Show Post) by manipulating the model -and directing data to the view. - -In Rails, the model is handled by what's called an object-relational mapping -layer entitled Active Record. This layer allows you to present the data from -database rows as objects and embellish these data objects with business logic -methods. You can read more about Active Record in -link:files/vendor/rails/activerecord/README.html. - -The controller and view are handled by the Action Pack, which handles both -layers by its two parts: Action View and Action Controller. These two layers -are bundled in a single package due to their heavy interdependence. This is -unlike the relationship between the Active Record and Action Pack that is much -more separate. Each of these packages can be used independently outside of -Rails. You can read more about Action Pack in -link:files/vendor/rails/actionpack/README.html. - - -== Getting Started - -1. At the command prompt, create a new Rails application: - rails new myapp (where myapp is the application name) - -2. Change directory to myapp and start the web server: - cd myapp; rails server (run with --help for options) - -3. Go to http://localhost:3000/ and you'll see: - "Welcome aboard: You're riding Ruby on Rails!" - -4. Follow the guidelines to start developing your application. You can find -the following resources handy: - -* The Getting Started Guide: http://guides.rubyonrails.org/getting_started.html -* Ruby on Rails Tutorial Book: http://www.railstutorial.org/ - - -== Debugging Rails - -Sometimes your application goes wrong. Fortunately there are a lot of tools that -will help you debug it and get it back on the rails. - -First area to check is the application log files. Have "tail -f" commands -running on the server.log and development.log. Rails will automatically display -debugging and runtime information to these files. Debugging info will also be -shown in the browser on requests from 127.0.0.1. - -You can also log your own messages directly into the log file from your code -using the Ruby logger class from inside your controllers. Example: - - class WeblogController < ActionController::Base - def destroy - @weblog = Weblog.find(params[:id]) - @weblog.destroy - logger.info("#{Time.now} Destroyed Weblog ID ##{@weblog.id}!") - end - end - -The result will be a message in your log file along the lines of: - - Mon Oct 08 14:22:29 +1000 2007 Destroyed Weblog ID #1! - -More information on how to use the logger is at http://www.ruby-doc.org/core/ - -Also, Ruby documentation can be found at http://www.ruby-lang.org/. There are -several books available online as well: - -* Programming Ruby: http://www.ruby-doc.org/docs/ProgrammingRuby/ (Pickaxe) -* Learn to Program: http://pine.fm/LearnToProgram/ (a beginners guide) - -These two books will bring you up to speed on the Ruby language and also on -programming in general. - - -== Debugger - -Debugger support is available through the debugger command when you start your -Mongrel or WEBrick server with --debugger. This means that you can break out of -execution at any point in the code, investigate and change the model, and then, -resume execution! You need to install ruby-debug to run the server in debugging -mode. With gems, use sudo gem install ruby-debug. Example: - - class WeblogController < ActionController::Base - def index - @posts = Post.find(:all) - debugger - end - end - -So the controller will accept the action, run the first line, then present you -with a IRB prompt in the server window. Here you can do things like: - - >> @posts.inspect - => "[#nil, "body"=>nil, "id"=>"1"}>, - #"Rails", "body"=>"Only ten..", "id"=>"2"}>]" - >> @posts.first.title = "hello from a debugger" - => "hello from a debugger" - -...and even better, you can examine how your runtime objects actually work: - - >> f = @posts.first - => #nil, "body"=>nil, "id"=>"1"}> - >> f. - Display all 152 possibilities? (y or n) - -Finally, when you're ready to resume execution, you can enter "cont". - - -== Console - -The console is a Ruby shell, which allows you to interact with your -application's domain model. Here you'll have all parts of the application -configured, just like it is when the application is running. You can inspect -domain models, change values, and save to the database. Starting the script -without arguments will launch it in the development environment. - -To start the console, run rails console from the application -directory. - -Options: - -* Passing the -s, --sandbox argument will rollback any modifications - made to the database. -* Passing an environment name as an argument will load the corresponding - environment. Example: rails console production. - -To reload your controllers and models after launching the console run -reload! - -More information about irb can be found at: -link:http://www.rubycentral.com/pickaxe/irb.html - - -== dbconsole - -You can go to the command line of your database directly through rails -dbconsole. You would be connected to the database with the credentials -defined in database.yml. Starting the script without arguments will connect you -to the development database. Passing an argument will connect you to a different -database, like rails dbconsole production. Currently works for MySQL, -PostgreSQL and SQLite 3. - -== Description of Contents - -The default directory structure of a generated Ruby on Rails application: - - |-- app - | |-- controllers - | |-- helpers - | |-- mailers - | |-- models - | `-- views - | `-- layouts - |-- config - | |-- environments - | |-- initializers - | `-- locales - |-- db - |-- doc - |-- lib - | `-- tasks - |-- log - |-- public - | |-- images - | |-- javascripts - | `-- stylesheets - |-- script - |-- test - | |-- fixtures - | |-- functional - | |-- integration - | |-- performance - | `-- unit - |-- tmp - | |-- cache - | |-- pids - | |-- sessions - | `-- sockets - `-- vendor - `-- plugins - -app - Holds all the code that's specific to this particular application. - -app/controllers - Holds controllers that should be named like weblogs_controller.rb for - automated URL mapping. All controllers should descend from - ApplicationController which itself descends from ActionController::Base. - -app/models - Holds models that should be named like post.rb. Models descend from - ActiveRecord::Base by default. - -app/views - Holds the template files for the view that should be named like - weblogs/index.html.erb for the WeblogsController#index action. All views use - eRuby syntax by default. - -app/views/layouts - Holds the template files for layouts to be used with views. This models the - common header/footer method of wrapping views. In your views, define a layout - using the layout :default and create a file named default.html.erb. - Inside default.html.erb, call <% yield %> to render the view using this - layout. - -app/helpers - Holds view helpers that should be named like weblogs_helper.rb. These are - generated for you automatically when using generators for controllers. - Helpers can be used to wrap functionality for your views into methods. - -config - Configuration files for the Rails environment, the routing map, the database, - and other dependencies. - -db - Contains the database schema in schema.rb. db/migrate contains all the - sequence of Migrations for your schema. - -doc - This directory is where your application documentation will be stored when - generated using rake doc:app - -lib - Application specific libraries. Basically, any kind of custom code that - doesn't belong under controllers, models, or helpers. This directory is in - the load path. - -public - The directory available for the web server. Contains subdirectories for - images, stylesheets, and javascripts. Also contains the dispatchers and the - default HTML files. This should be set as the DOCUMENT_ROOT of your web - server. - -script - Helper scripts for automation and generation. - -test - Unit and functional tests along with fixtures. When using the rails generate - command, template test files will be generated for you and placed in this - directory. - -vendor - External libraries that the application depends on. Also includes the plugins - subdirectory. If the app has frozen rails, those gems also go here, under - vendor/rails/. This directory is in the load path. diff --git a/README.md b/README.md new file mode 100644 index 0000000..12a3978 --- /dev/null +++ b/README.md @@ -0,0 +1,108 @@ +UNSW CSESoc Website +=================== + +Auth Module +----------- + +A script similar to this needs to run on the CSESoc account on +a CSE Server to authenticate users. + + # Find the user if it exists + u = User.where(:cse_username => params[:cse_username]) + if u.any? + u = u.first + else + u = User.new + u.cse_username = params[:cse_username] + end + + # Give them a new token + token = User.authentication_token + u.authentication_token = token + u.save + + # Redirect them back with the new auth token + redirect_to("http://csesoc.unsw.edu.au/callback?auth_token=#{token}") + +Changes from Django +------------------- + +This list may be incomplete. + +### Models + +- User Model + - Added + - Added user sessions + - TODO: Facebook integration +- Static Model + - Text is now Content + - Removed creator + - Removed updater + - Now uses Markdown +- NewsItem Model + - Headline is now Title + - Text is now Content + - pubdate is now publish_date + - Now uses Markdown + - TODO: RSS Feed +- Event Model + - TODO: implement controller and views + - TODO: iCal RSS feed +- Beta Model + - Removed +- Sponsor Model + - Added Attachment + - TODO: Resize sizes and layouts +- Suggestion Model + - Changed sender to user_id + - TODO: Needs Comment nested controller and routes +- Comment Model + - name is now user_id + - suggestion to now suggestion_id + +### Design + +- Made titles of news posts links to articles +- Made Cal and Sponsors only show on home page +- Added a notice flash +- TODO: Javascript notice flash +- A lot of logic in views was removed +- Fixed murder players been different from CSE users +- Can now log out properly +- No passwords or emails, only cse auth + +### Development + +- Added Capistrano, anyone with ssh key can deploy +- Added migrations for DB +- Added DB seeds + +### TODO + +- Murder +- Scheduler +- Campleaders +- Campattendees +- rspec +- Music +- Game + +License +------- + + UNSW CSESoc Website + Copyright (C) 2011 UNSW CSESoc + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . \ No newline at end of file diff --git a/app/views/news_items/_news_item.html.erb b/app/views/news_items/_news_item.html.erb index 81a53c7..8a282fa 100644 --- a/app/views/news_items/_news_item.html.erb +++ b/app/views/news_items/_news_item.html.erb @@ -5,7 +5,7 @@ <%= news_item.publish_date.month %> <%= news_item.publish_date.year %> @ <%= news_item.publish_date.strftime("%H:%M") %> - by <%#= news_item.author.name %> + by <%= news_item.author.cse_username %>
    From d99b74280be4a25d21a4622950dc3157c7308cd0 Mon Sep 17 00:00:00 2001 From: Maxwell Swadling Date: Sat, 14 May 2011 22:17:43 +1000 Subject: [PATCH 51/55] Improved readme. Signed-off-by: Maxwell Swadling --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index 12a3978..252c83c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,17 @@ UNSW CSESoc Website =================== +How To Setup +------------ + + rvm install 1.9.2 + rvm use 1.9.2 --default + + bundle install + rake db:migrate + rake db:seed + rails server + Auth Module ----------- From fa3b522bd0a5d0ab80b3b57d94cc74912230de35 Mon Sep 17 00:00:00 2001 From: Maxwell Swadling Date: Sat, 14 May 2011 22:19:12 +1000 Subject: [PATCH 52/55] Added route for comments. Signed-off-by: Maxwell Swadling --- config/routes.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/config/routes.rb b/config/routes.rb index ebb1f00..c08ffec 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -6,12 +6,17 @@ get "callback", :to => "devise/sessions#create" end - resources :suggestions, :only => [:index, :show, :new, :create] resources :sponsors, :only => [:index] resources :events, :only => [:index, :show] resources :news_items, :only => [:index, :show] resources :statics, :only => [:show] + # Suggestions + resources :suggestions, :only => [:index, :show, :new, :create] do + # Comments on suggestions + resources :comments, :only => [:create] + end + # The priority is based upon order of creation: # first created -> highest priority. From 34633400d4866c5126f3be636baeb66ff1dfbbe3 Mon Sep 17 00:00:00 2001 From: Maxwell Swadling Date: Sat, 14 May 2011 22:27:17 +1000 Subject: [PATCH 53/55] Added comments controller. Signed-off-by: Maxwell Swadling --- app/controllers/comments_controller.rb | 22 ++++++++++++++++++++++ app/models/comment.rb | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 app/controllers/comments_controller.rb diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb new file mode 100644 index 0000000..bb0c36b --- /dev/null +++ b/app/controllers/comments_controller.rb @@ -0,0 +1,22 @@ +class CommentsController < ApplicationController + def create + @comment = Comment.new(params[:comment]) + + @comment.suggestion = Suggestion.find(params[:suggestion_id]) + @comment.user = current_user + + if @comment.save + respond_to do |f| + f.html {redirect_to @comment.suggestion, :notice => "Thanks for your comment!"} + f.js {} + end + else + respond_to do |f| + f.html {redirect_to @comment.suggestion, :notice => "Sorry, your comment could not be saved."} + f.js {@comment.errors} + end + end + + end + +end \ No newline at end of file diff --git a/app/models/comment.rb b/app/models/comment.rb index c95ed58..8e47d0b 100644 --- a/app/models/comment.rb +++ b/app/models/comment.rb @@ -2,7 +2,7 @@ class Comment < ActiveRecord::Base belongs_to :suggestion belongs_to :user - validates :suggestion_id, :presence => true + validates :suggestion, :presence => true validates :user, :presence => true validates :comment, :presence => true end From ad4c28a3cdee95fc046334c02a495848d8f4b24d Mon Sep 17 00:00:00 2001 From: Maxwell Swadling Date: Sat, 14 May 2011 22:30:03 +1000 Subject: [PATCH 54/55] Added improved view of suggestions. Signed-off-by: Maxwell Swadling --- app/controllers/comments_controller.rb | 4 ++-- app/views/suggestions/show.html.erb | 30 ++++++++++++++++---------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb index bb0c36b..defd5d6 100644 --- a/app/controllers/comments_controller.rb +++ b/app/controllers/comments_controller.rb @@ -8,12 +8,12 @@ def create if @comment.save respond_to do |f| f.html {redirect_to @comment.suggestion, :notice => "Thanks for your comment!"} - f.js {} + f.js # Renders RJS template end else respond_to do |f| f.html {redirect_to @comment.suggestion, :notice => "Sorry, your comment could not be saved."} - f.js {@comment.errors} + f.js # Renders RJS template end end diff --git a/app/views/suggestions/show.html.erb b/app/views/suggestions/show.html.erb index a2a802b..112523a 100644 --- a/app/views/suggestions/show.html.erb +++ b/app/views/suggestions/show.html.erb @@ -1,20 +1,28 @@ - -

    - Subject: +

    <%= @suggestion.subject %> -

    +

    +

    + <%= @suggestion.user.cse_username %> +

    - Message: - <%= @suggestion.message %> + <%= RDiscount.new(@suggestion.message).to_html.html_safe %>

    -

    - User: - <%= @suggestion.user_id %> -

    +

    + Comments +

    + +<% @suggestion.comments.each do |comment| %> +

    <%= comment.user.cse_username %>

    +

    + <%= comment.comment %> +

    +<% end %> +<%= form_for [@suggestion, @suggestion.comments.new], :remote => true do |f| %> +<%= f.text_field :comment %> +<% end %> -<%= link_to 'Edit', edit_suggestion_path(@suggestion) %> | <%= link_to 'Back', suggestions_path %> From e025f78a96f65b04d4f491d4eda5b7c1a36bab0a Mon Sep 17 00:00:00 2001 From: Maxwell Swadling Date: Sat, 14 May 2011 22:52:36 +1000 Subject: [PATCH 55/55] Added Ajax comment. Signed-off-by: Maxwell Swadling --- app/views/comments/_comment.html.erb | 6 ++++++ app/views/comments/create.js.rjs | 7 +++++++ app/views/suggestions/show.html.erb | 10 +++++----- 3 files changed, 18 insertions(+), 5 deletions(-) create mode 100644 app/views/comments/_comment.html.erb create mode 100644 app/views/comments/create.js.rjs diff --git a/app/views/comments/_comment.html.erb b/app/views/comments/_comment.html.erb new file mode 100644 index 0000000..f9cccd1 --- /dev/null +++ b/app/views/comments/_comment.html.erb @@ -0,0 +1,6 @@ +
    +

    <%= comment.user.cse_username %>

    +

    + <%= comment.comment %> +

    +
    \ No newline at end of file diff --git a/app/views/comments/create.js.rjs b/app/views/comments/create.js.rjs new file mode 100644 index 0000000..455940f --- /dev/null +++ b/app/views/comments/create.js.rjs @@ -0,0 +1,7 @@ +if @comment.errors.any? + # Insert errors + page[:comment_errors].replace_html @comment.errors.full_messages.to_sentence +else + # Comment saved + page[:new_comment].replace_html :partial => @comment +end diff --git a/app/views/suggestions/show.html.erb b/app/views/suggestions/show.html.erb index 112523a..1a5ae6c 100644 --- a/app/views/suggestions/show.html.erb +++ b/app/views/suggestions/show.html.erb @@ -15,14 +15,14 @@ <% @suggestion.comments.each do |comment| %> -

    <%= comment.user.cse_username %>

    -

    - <%= comment.comment %> -

    + <%= render comment %> <% end %> <%= form_for [@suggestion, @suggestion.comments.new], :remote => true do |f| %> -<%= f.text_field :comment %> +
    +
    + <%= f.text_field :comment %> + <%= f.submit %> <% end %> <%= link_to 'Back', suggestions_path %>