Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Changed admin area to make full use of Dispatcher and moved admin dir…

…ectory.
  • Loading branch information...
commit 5eba796295ea4be5b9014d80b482317dd2c0d64f 1 parent 4201a8b
Martijn mvdkleijn authored
Showing with 9,691 additions and 85 deletions.
  1. +0 −6 _.htaccess
  2. +1 −0  docs/changelog.txt
  3. +48 −6 index.php
  4. +1 −1  public/themes/simple/screen.css
  5. +1 −1  public/themes/wolf/screen.css
  6. +1 −0  wolf/Framework.php
  7. BIN  wolf/admin/images/clear.gif
  8. BIN  wolf/admin/images/collapse.png
  9. BIN  wolf/admin/images/drag-disabled.gif
  10. BIN  wolf/admin/images/drag.gif
  11. BIN  wolf/admin/images/drag_to_copy.gif
  12. BIN  wolf/admin/images/drag_to_sort.gif
  13. BIN  wolf/admin/images/expand.png
  14. BIN  wolf/admin/images/file.png
  15. BIN  wolf/admin/images/icon-remove-disabled.gif
  16. BIN  wolf/admin/images/icon-remove.gif
  17. BIN  wolf/admin/images/icon-rename.gif
  18. BIN  wolf/admin/images/icon_cal.gif
  19. BIN  wolf/admin/images/layout.png
  20. BIN  wolf/admin/images/magnify.png
  21. BIN  wolf/admin/images/minus.png
  22. BIN  wolf/admin/images/page.png
  23. BIN  wolf/admin/images/plus.png
  24. BIN  wolf/admin/images/snippet.png
  25. BIN  wolf/admin/images/spinner.gif
  26. BIN  wolf/admin/images/toolbar-icons.gif
  27. BIN  wolf/admin/images/user.png
  28. +465 −0 wolf/admin/javascripts/calendar.js
  29. +115 −0 wolf/admin/javascripts/control.textarea.js
  30. +1 −0  wolf/admin/javascripts/cp-datepicker.js
  31. +974 −0 wolf/admin/javascripts/dragdrop.js
  32. +1,122 −0 wolf/admin/javascripts/effects.js
  33. +19 −0 wolf/admin/javascripts/jquery-1.3.2.min.js
  34. +298 −0 wolf/admin/javascripts/jquery-ui-1.7.2.custom.min.js
  35. +4,184 −0 wolf/admin/javascripts/prototype.js
  36. +4 −0 wolf/admin/javascripts/unitpngfix.js
  37. +651 −0 wolf/admin/javascripts/wolf.js
  38. +1,062 −0 wolf/admin/stylesheets/admin.css
  39. +158 −0 wolf/admin/stylesheets/login.css
  40. +101 −0 wolf/admin/stylesheets/toolbar.css
  41. +181 −0 wolf/admin/themes/black_and_white/styles.css
  42. +26 −0 wolf/admin/themes/brown_and_blue/styles.css
  43. +207 −0 wolf/admin/themes/brown_and_green/styles.css
  44. +1 −1  wolf/app/i18n/bn-message.php
  45. +1 −1  wolf/app/i18n/da-message.php
  46. +1 −1  wolf/app/i18n/es-message.php
  47. +1 −1  wolf/app/i18n/pt-message.php
  48. +1 −1  wolf/app/i18n/ro-message.php
  49. +12 −12 wolf/app/layouts/backend.php
  50. +1 −1  wolf/app/models/Setting.php
  51. +4 −4 wolf/app/views/layout/index.php
  52. +1 −1  wolf/app/views/layout/sidebar.php
  53. +5 −5 wolf/app/views/login/forgot.php
  54. +5 −5 wolf/app/views/login/login.php
  55. +8 −8 wolf/app/views/page/children.php
  56. +5 −5 wolf/app/views/page/edit.php
  57. +5 −5 wolf/app/views/page/index.php
  58. +1 −1  wolf/app/views/setting/index.php
  59. +4 −4 wolf/app/views/snippet/index.php
  60. +1 −1  wolf/app/views/snippet/sidebar.php
  61. +2 −2 wolf/app/views/translate/sidebar.php
  62. +3 −3 wolf/app/views/user/index.php
  63. +1 −1  wolf/app/views/user/sidebar.php
  64. +1 −1  wolf/install/config.tmpl
  65. +1 −1  wolf/plugins/comment/i18n/hr-message.php
  66. +1 −1  wolf/plugins/comment/i18n/pl-message.php
  67. +1 −1  wolf/plugins/comment/i18n/ro-message.php
  68. +1 −1  wolf/plugins/file_manager/i18n/hr-message.php
  69. +2 −2 wolf/plugins/file_manager/views/index.php
  70. +1 −1  wolf/plugins/skeleton/i18n/pl-message.php
6 _.htaccess
View
@@ -26,12 +26,6 @@ Options -Indexes +FollowSymLinks
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-l
- # Administration URL rewriting.
- RewriteRule ^admin(.*)$ admin/index.php?$1 [L,QSA]
-
- RewriteCond %{REQUEST_FILENAME} !-f
- RewriteCond %{REQUEST_FILENAME} !-d
- RewriteCond %{REQUEST_FILENAME} !-l
# Main URL rewriting.
RewriteRule ^(.*)$ index.php?WOLFPAGE=$1 [L,QSA]
1  docs/changelog.txt
View
@@ -9,6 +9,7 @@ Sigla:
0.7.0 - released 2010-??-??
++ Changed admin area to make full use of Dispatcher and moved admin directory.
+ Added new Helper, Hash, which basically is Crypt_Hash from phpseclib.
+ Added tracking of failed login count and last failed login attempt.
+ Added session regeneration upon user privilege increase. (login for now)
54 index.php
View
@@ -30,22 +30,22 @@
// Constants ---------------------------------------------------------------
define('IN_CMS', true);
+define('CMS_VERSION', '0.6.0');
define('CMS_ROOT', dirname(__FILE__));
-define('FROG_ROOT', CMS_ROOT); // DEFINED ONLY FOR BACKWARDS SUPPORT - to be taken out before 0.9.0
define('CORE_ROOT', CMS_ROOT.'/wolf');
define('PLUGINS_ROOT', CORE_ROOT.'/plugins');
-
define('APP_PATH', CORE_ROOT.'/app');
require_once(CORE_ROOT.'/utils.php');
$config_file = CMS_ROOT.'/config.php';
-
require_once($config_file);
// if you have installed wolf and see this line, you can comment it or delete it :)
if ( ! defined('DEBUG')) { header('Location: wolf/install/'); exit(); }
+$url = URL_PUBLIC;
+
// Figure out what the public URI is based on URL_PUBLIC.
// @todo improve
$changedurl = str_replace('//','|',URL_PUBLIC);
@@ -57,6 +57,28 @@
define('URI_PUBLIC', substr($changedurl, $lastslash));
}
+// Determine URI for backend check
+if (USE_MOD_REWRITE && isset($_GET['WOLFPAGE'])) {
+ $admin_check = $_GET['WOLFPAGE'];
+}
+else {
+ $admin_check = urldecode($_SERVER['QUERY_STRING']);
+}
+
+// Are we in frontend or backend?
+if (startsWith($admin_check, 'admin') || startsWith($admin_check, '/admin') || isset($_GET['WOLFAJAX'])) {
+ define('CMS_BACKEND', true);
+ if (defined('USE_HTTPS') && USE_HTTPS) {
+ $url = str_replace('http://', 'https://', $url);
+ }
+ define('BASE_URL', $url . (endsWith($url, '/') ? '': '/') . (USE_MOD_REWRITE ? '': '?/') . ADMIN_DIR . (endsWith(ADMIN_DIR, '/') ? '': '/'));
+ define('BASE_URI', URI_PUBLIC . (endsWith($url, '/') ? '': '/') . (USE_MOD_REWRITE ? '': '?/') . ADMIN_DIR . (endsWith(ADMIN_DIR, '/') ? '': '/'));
+}
+else {
+ define('BASE_URL', URL_PUBLIC . (endsWith(URL_PUBLIC, '/') ? '': '/') . (USE_MOD_REWRITE ? '': '?'));
+ define('BASE_URI', URI_PUBLIC . (endsWith(URI_PUBLIC, '/') ? '': '/') . (USE_MOD_REWRITE ? '': '?'));
+}
+
define('PLUGINS_URI', URI_PUBLIC.'wolf/plugins/');
if (!defined('THEMES_ROOT')) { define('THEMES_ROOT', CMS_ROOT.'/public/themes'); }
if (!defined('THEMES_URI')) { define('THEMES_URI', URI_PUBLIC.'public/themes/'); }
@@ -77,8 +99,11 @@
// Init --------------------------------------------------------------------
-define('BASE_URL', URL_PUBLIC . (endsWith(URL_PUBLIC, '/') ? '': '/') . (USE_MOD_REWRITE ? '': '?'));
-define('BASE_URI', URI_PUBLIC . (endsWith(URI_PUBLIC, '/') ? '': '/') . (USE_MOD_REWRITE ? '': '?'));
+define('SESSION_LIFETIME', 3600);
+define('REMEMBER_LOGIN_LIFETIME', 1209600); // two weeks
+
+define('DEFAULT_CONTROLLER', 'page');
+define('DEFAULT_ACTION', 'index');
require CORE_ROOT.'/Framework.php';
@@ -114,7 +139,13 @@ function mysql_function_date_format($date, $format) {
Setting::init();
use_helper('I18n');
-I18n::setLocale(Setting::get('language'));
+AuthUser::load();
+if (AuthUser::isLoggedIn()) {
+ I18n::setLocale(AuthUser::getRecord()->language);
+}
+else {
+ I18n::setLocale(Setting::get('language'));
+}
// Only add the cron web bug when necessary
if (defined('USE_POORMANSCRON') && USE_POORMANSCRON && defined('POORMANSCRON_INTERVAL')) {
@@ -131,5 +162,16 @@ function run_cron() {
}
}
+Plugin::init();
+
+// Setup admin routes
+$admin_routes = array (
+ '/'.ADMIN_DIR => Setting::get('default_tab'),
+ '/'.ADMIN_DIR.'/' => Setting::get('default_tab'),
+ '/'.ADMIN_DIR.'/:any' => '$1'
+);
+
+Dispatcher::addRoute($admin_routes);
+
// run everything!
require APP_PATH.'/main.php';
2  public/themes/simple/screen.css
View
@@ -1,4 +1,4 @@
-/* Simple CSS for Wolf CMS */
+/* Simple CSS for Wolf CMS */
body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,input,textarea,p,blockquote,th,td { margin:0; padding:0; }
table { border-collapse:collapse; border-spacing:0; }
2  public/themes/wolf/screen.css
View
@@ -1,4 +1,4 @@
-/* WOLF cms CSS
+/* WOLF cms CSS
*
* Initial public release: 20090801
* Appearance inspired by EveryBlock = http://www.everyblock.com/
1  wolf/Framework.php
View
@@ -112,6 +112,7 @@
*/
public static function addRoute($route, $destination=null) {
if ($destination != null && !is_array($route)) {
+ //if (!is_array($route)) {
$route = array($route => $destination);
}
self::$routes = array_merge(self::$routes, $route);
BIN  wolf/admin/images/clear.gif
View
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  wolf/admin/images/collapse.png
View
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  wolf/admin/images/drag-disabled.gif
View
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  wolf/admin/images/drag.gif
View
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  wolf/admin/images/drag_to_copy.gif
View
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  wolf/admin/images/drag_to_sort.gif
View
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  wolf/admin/images/expand.png
View
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  wolf/admin/images/file.png
View
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  wolf/admin/images/icon-remove-disabled.gif
View
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  wolf/admin/images/icon-remove.gif
View
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  wolf/admin/images/icon-rename.gif
View
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  wolf/admin/images/icon_cal.gif
View
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  wolf/admin/images/layout.png
View
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  wolf/admin/images/magnify.png
View
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  wolf/admin/images/minus.png
View
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  wolf/admin/images/page.png
View
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  wolf/admin/images/plus.png
View
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  wolf/admin/images/snippet.png
View
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  wolf/admin/images/spinner.gif
View
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  wolf/admin/images/toolbar-icons.gif
View
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  wolf/admin/images/user.png
View
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
465 wolf/admin/javascripts/calendar.js
View
@@ -0,0 +1,465 @@
+/**
+This is a JavaScript library that will allow you to easily add some basic DHTML
+drop-down datepicker functionality to your Notes forms. This script is not as
+full-featured as others you may find on the Internet, but it's free, it's easy to
+understand, and it's easy to change.
+
+You'll also want to include a stylesheet that makes the datepicker elements
+look nice. An example one can be found in the database that this script was
+originally released with, at:
+
+http://www.nsftools.com/tips/NotesTips.htm#datepicker
+
+I've tested this lightly with Internet Explorer 6 and Mozilla Firefox. I have no idea
+how compatible it is with other browsers.
+
+version 1.5
+December 4, 2005
+Julian Robichaux -- http://www.nsftools.com
+
+HISTORY
+-- version 1.0 (Sept. 4, 2004):
+Initial release.
+
+-- version 1.1 (Sept. 5, 2004):
+Added capability to define the date format to be used, either globally (using the
+defaultDateSeparator and defaultDateFormat variables) or when the displayDatePicker
+function is called.
+
+-- version 1.2 (Sept. 7, 2004):
+Fixed problem where datepicker x-y coordinates weren't right inside of a table.
+Fixed problem where datepicker wouldn't display over selection lists on a page.
+Added a call to the datePickerClosed function (if one exists) after the datepicker
+is closed, to allow the developer to add their own custom validation after a date
+has been chosen. For this to work, you must have a function called datePickerClosed
+somewhere on the page, that accepts a field object as a parameter. See the
+example in the comments of the updateDateField function for more details.
+
+-- version 1.3 (Sept. 9, 2004)
+Fixed problem where adding the <div> and <iFrame> used for displaying the datepicker
+was causing problems on IE 6 with global variables that had handles to objects on
+the page (I fixed the problem by adding the elements using document.createElement()
+and document.body.appendChild() instead of document.body.innerHTML += ...).
+
+-- version 1.4 (Dec. 20, 2004)
+Added "targetDateField.focus();" to the updateDateField function (as suggested
+by Alan Lepofsky) to avoid a situation where the cursor focus is at the top of the
+form after a date has been picked. Added "padding: 0px;" to the dpButton CSS
+style, to keep the table from being so wide when displayed in Firefox.
+
+-- version 1.5 (Dec 4, 2005)
+Added display=none when datepicker is hidden, to fix problem where cursor is
+not visible on input fields that are beneath the date picker. Added additional null
+date handling for date errors in Safari when the date is empty. Added additional
+error handling for iFrame creation, to avoid reported errors in Opera. Added
+onMouseOver event for day cells, to allow color changes when the mouse hovers
+over a cell (to make it easier to determine what cell you're over). Added comments
+in the style sheet, to make it more clear what the different style elements are for.
+*/
+
+var datePickerDivID = "datepicker";
+var iFrameDivID = "datepickeriframe";
+
+var dayArrayShort = new Array('Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa');
+var dayArrayMed = new Array('Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat');
+var dayArrayLong = new Array('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday');
+var monthArrayShort = new Array('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec');
+var monthArrayMed = new Array('Jan', 'Feb', 'Mar', 'Apr', 'May', 'June', 'July', 'Aug', 'Sept', 'Oct', 'Nov', 'Dec');
+var monthArrayLong = new Array('January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December');
+
+// these variables define the date formatting we're expecting and outputting.
+// If you want to use a different format by default, change the defaultDateSeparator
+// and defaultDateFormat variables either here or on your HTML page.
+var defaultDateSeparator = "-"; // common values would be "/" or "."
+var defaultDateFormat = "ymd"; // valid values are "mdy", "dmy", and "ymd"
+var dateSeparator = defaultDateSeparator;
+var dateFormat = defaultDateFormat;
+
+/**
+This is the main function you'll call from the onClick event of a button.
+Normally, you'll have something like this on your HTML page:
+
+Start Date: <input name="StartDate">
+<input type=button value="select" onclick="displayDatePicker('StartDate');">
+
+That will cause the datepicker to be displayed beneath the StartDate field and
+any date that is chosen will update the value of that field. If you'd rather have the
+datepicker display beneath the button that was clicked, you can code the button
+like this:
+
+<input type=button value="select" onclick="displayDatePicker('StartDate', this);">
+
+So, pretty much, the first argument (dateFieldName) is a string representing the
+name of the field that will be modified if the user picks a date, and the second
+argument (displayBelowThisObject) is optional and represents an actual node
+on the HTML document that the datepicker should be displayed below.
+
+In version 1.1 of this code, the dtFormat and dtSep variables were added, allowing
+you to use a specific date format or date separator for a given call to this function.
+Normally, you'll just want to set these defaults globally with the defaultDateSeparator
+and defaultDateFormat variables, but it doesn't hurt anything to add them as optional
+parameters here. An example of use is:
+
+<input type=button value="select" onclick="displayDatePicker('StartDate', false, 'dmy', '.');">
+
+This would display the datepicker beneath the StartDate field (because the
+displayBelowThisObject parameter was false), and update the StartDate field with
+the chosen value of the datepicker using a date format of dd.mm.yyyy
+*/
+function displayDatePicker(dateFieldName, displayBelowThisObject, dtFormat, dtSep)
+{
+ var targetDateField = document.getElementsByName (dateFieldName).item(0);
+
+ // if we weren't told what node to display the datepicker beneath, just display it
+ // beneath the date field we're updating
+ if (!displayBelowThisObject)
+ displayBelowThisObject = targetDateField;
+
+ // if a date separator character was given, update the dateSeparator variable
+ if (dtSep)
+ dateSeparator = dtSep;
+ else
+ dateSeparator = defaultDateSeparator;
+
+ // if a date format was given, update the dateFormat variable
+ if (dtFormat)
+ dateFormat = dtFormat;
+ else
+ dateFormat = defaultDateFormat;
+
+ var x = displayBelowThisObject.offsetLeft;
+ var y = displayBelowThisObject.offsetTop + displayBelowThisObject.offsetHeight;
+
+ // deal with elements inside tables and such
+ var parent = displayBelowThisObject;
+ while (parent.offsetParent) {
+ parent = parent.offsetParent;
+ x += parent.offsetLeft;
+ y += parent.offsetTop ;
+ }
+
+ drawDatePicker(targetDateField, x, y);
+};
+
+
+/**
+Draw the datepicker object (which is just a table with calendar elements) at the
+specified x and y coordinates, using the targetDateField object as the input tag
+that will ultimately be populated with a date.
+
+This function will normally be called by the displayDatePicker function.
+*/
+function drawDatePicker(targetDateField, x, y)
+{
+ var dt = getFieldDate(targetDateField.value );
+
+ // the datepicker table will be drawn inside of a <div> with an ID defined by the
+ // global datePickerDivID variable. If such a div doesn't yet exist on the HTML
+ // document we're working with, add one.
+ if (!document.getElementById(datePickerDivID)) {
+ // don't use innerHTML to update the body, because it can cause global variables
+ // that are currently pointing to objects on the page to have bad references
+ //document.body.innerHTML += "<div id='" + datePickerDivID + "' class='dpDiv'></div>";
+ var newNode = document.createElement("div");
+ newNode.setAttribute("id", datePickerDivID);
+ newNode.setAttribute("class", "dpDiv");
+ newNode.setAttribute("style", "visibility: hidden;");
+ document.body.appendChild(newNode);
+ }
+
+ // move the datepicker div to the proper x,y coordinate and toggle the visiblity
+ var pickerDiv = document.getElementById(datePickerDivID);
+ pickerDiv.style.position = "absolute";
+ pickerDiv.style.left = x + "px";
+ pickerDiv.style.top = y + "px";
+ pickerDiv.style.visibility = (pickerDiv.style.visibility == "visible" ? "hidden" : "visible");
+ pickerDiv.style.display = (pickerDiv.style.display == "block" ? "none" : "block");
+ pickerDiv.style.zIndex = 10000;
+
+ // draw the datepicker table
+ refreshDatePicker(targetDateField.name, dt.getFullYear(), dt.getMonth(), dt.getDate());
+};
+
+
+/**
+This is the function that actually draws the datepicker calendar.
+*/
+function refreshDatePicker(dateFieldName, year, month, day)
+{
+ // if no arguments are passed, use today's date; otherwise, month and year
+ // are required (if a day is passed, it will be highlighted later)
+ var thisDay = new Date();
+
+ if ((month >= 0) && (year > 0)) {
+ thisDay = new Date(year, month, 1);
+ } else {
+ day = thisDay.getDate();
+ thisDay.setDate(1);
+ }
+
+ // the calendar will be drawn as a table
+ // you can customize the table elements with a global CSS style sheet,
+ // or by hardcoding style and formatting elements below
+ var crlf = "\r\n";
+ var TABLE = "<table cols=7 class='dpTable'>" + crlf;
+ var xTABLE = "</table>" + crlf;
+ var TR = "<tr class='dpTR'>";
+ var TR_title = "<tr class='dpTitleTR'>";
+ var TR_days = "<tr class='dpDayTR'>";
+ var TR_todaybutton = "<tr class='dpTodayButtonTR'>";
+ var xTR = "</tr>" + crlf;
+ var TD = "<td class='dpTD' onMouseOut='this.className=\"dpTD\";' onMouseOver=' this.className=\"dpTDHover\";' "; // leave this tag open, because we'll be adding an onClick event
+ var empty_TD = "<td class='dpTDempty'>";
+ var TD_title = "<td colspan=5 class='dpTitleTD'>";
+ var TD_buttons = "<td class='dpButtonTD'>";
+ var TD_todaybutton = "<td colspan=7 class='dpTodayButtonTD'>";
+ var TD_days = "<td class='dpDayTD'>";
+ var TD_selected = "<td class='dpDayHighlightTD' onMouseOut='this.className=\"dpDayHighlightTD\";' onMouseOver='this.className=\"dpTDHover\";' "; // leave this tag open, because we'll be adding an onClick event
+ var xTD = "</td>" + crlf;
+ var DIV_title = "<div class='dpTitleText'>";
+ var DIV_selected = "<div class='dpDayHighlight'>";
+ var xDIV = "</div>";
+
+ // start generating the code for the calendar table
+ var html = TABLE;
+
+ // this is the title bar, which displays the month and the buttons to
+ // go back to a previous month or forward to the next month
+ html += TR_title;
+ html += TD_buttons + getButtonCode(dateFieldName, thisDay, -1, "&lt;") + xTD;
+ html += TD_title + DIV_title + monthArrayLong[ thisDay.getMonth()] + " " + thisDay.getFullYear() + xDIV + xTD;
+ html += TD_buttons + getButtonCode(dateFieldName, thisDay, 1, "&gt;") + xTD;
+ html += xTR;
+
+ // this is the row that indicates which day of the week we're on
+ html += TR_days;
+ for(i = 0; i < dayArrayShort.length; i++)
+ html += TD_days + dayArrayShort[i] + xTD;
+ html += xTR;
+
+ // now we'll start populating the table with days of the month
+ html += TR;
+
+ // first, the leading blanks
+ for (i = 0; i < thisDay.getDay(); i++)
+ html += empty_TD + "&nbsp;" + xTD;
+
+ // now, the days of the month
+ do {
+ dayNum = thisDay.getDate();
+ TD_onclick = " onclick=\"updateDateField('" + dateFieldName + "', '" + getDateString(thisDay) + "');\">";
+
+ if (dayNum == day)
+ html += TD_selected + TD_onclick + DIV_selected + dayNum + xDIV + xTD;
+ else
+ html += TD + TD_onclick + dayNum + xTD;
+
+ // if this is a Saturday, start a new row
+ if (thisDay.getDay() == 6)
+ html += xTR + TR;
+
+ // increment the day
+ thisDay.setDate(thisDay.getDate() + 1);
+ } while (thisDay.getDate() > 1)
+
+ // fill in any trailing blanks
+ if (thisDay.getDay() > 0) {
+ for (i = 6; i > thisDay.getDay(); i--)
+ html += empty_TD + "&nbsp;" + xTD;
+ }
+ html += xTR;
+
+ // add a button to allow the user to easily return to today, or close the calendar
+ var today = new Date();
+ var todayString = "Today is " + dayArrayMed[today.getDay()] + ", " + monthArrayMed[ today.getMonth()] + " " + today.getDate();
+ html += TR_todaybutton + TD_todaybutton;
+ html += "<button class='dpTodayButton' onClick='refreshDatePicker(\"" + dateFieldName + "\");'>this month</button> ";
+ html += "<button class='dpTodayButton' onClick='updateDateField(\"" + dateFieldName + "\");'>close</button>";
+ html += xTD + xTR;
+
+ // and finally, close the table
+ html += xTABLE;
+
+ document.getElementById(datePickerDivID).innerHTML = html;
+ // add an "iFrame shim" to allow the datepicker to display above selection lists
+ adjustiFrame();
+};
+
+
+/**
+Convenience function for writing the code for the buttons that bring us back or forward
+a month.
+*/
+function getButtonCode(dateFieldName, dateVal, adjust, label)
+{
+ var newMonth = (dateVal.getMonth () + adjust) % 12;
+ var newYear = dateVal.getFullYear() + parseInt((dateVal.getMonth() + adjust) / 12);
+ if (newMonth < 0) {
+ newMonth += 12;
+ newYear += -1;
+ }
+
+ return "<button class='dpButton' onClick='refreshDatePicker(\"" + dateFieldName + "\", " + newYear + ", " + newMonth + ");'>" + label + "</button>";
+};
+
+
+/**
+Convert a JavaScript Date object to a string, based on the dateFormat and dateSeparator
+variables at the beginning of this script library.
+*/
+function getDateString(dateVal)
+{
+ var dayString = "00" + dateVal.getDate();
+ var monthString = "00" + (dateVal.getMonth()+1);
+ dayString = dayString.substring(dayString.length - 2);
+ monthString = monthString.substring(monthString.length - 2);
+
+ switch (dateFormat) {
+ case "dmy" :
+ return dayString + dateSeparator + monthString + dateSeparator + dateVal.getFullYear();
+ case "ymd" :
+ return dateVal.getFullYear() + dateSeparator + monthString + dateSeparator + dayString;
+ case "mdy" :
+ default :
+ return monthString + dateSeparator + dayString + dateSeparator + dateVal.getFullYear();
+ }
+};
+
+
+/**
+Convert a string to a JavaScript Date object.
+*/
+function getFieldDate(dateString)
+{
+ var dateVal;
+ var dArray;
+ var d, m, y;
+
+ try {
+ dArray = splitDateString(dateString);
+ if (dArray) {
+ switch (dateFormat) {
+ case "dmy" :
+ d = parseInt(dArray[0], 10);
+ m = parseInt(dArray[1], 10) - 1;
+ y = parseInt(dArray[2], 10);
+ break;
+ case "ymd" :
+ d = parseInt(dArray[2], 10);
+ m = parseInt(dArray[1], 10) - 1;
+ y = parseInt(dArray[0], 10);
+ break;
+ case "mdy" :
+ default :
+ d = parseInt(dArray[1], 10);
+ m = parseInt(dArray[0], 10) - 1;
+ y = parseInt(dArray[2], 10);
+ break;
+ }
+ dateVal = new Date(y, m, d);
+ } else if (dateString) {
+ dateVal = new Date(dateString);
+ } else {
+ dateVal = new Date();
+ }
+ } catch(e) {
+ dateVal = new Date();
+ }
+
+ return dateVal;
+};
+
+
+/**
+Try to split a date string into an array of elements, using common date separators.
+If the date is split, an array is returned; otherwise, we just return false.
+*/
+function splitDateString(dateString)
+{
+ var dArray;
+ if (dateString.indexOf("/") >= 0)
+ dArray = dateString.split("/");
+ else if (dateString.indexOf(".") >= 0)
+ dArray = dateString.split(".");
+ else if (dateString.indexOf("-") >= 0)
+ dArray = dateString.split("-");
+ else if (dateString.indexOf("\\") >= 0)
+ dArray = dateString.split("\\");
+ else
+ dArray = false;
+
+ return dArray;
+};
+
+function updateDateField(dateFieldName, dateString)
+{
+ var targetDateField = document.getElementsByName (dateFieldName).item(0);
+ if (dateString)
+ targetDateField.value = dateString;
+
+ var pickerDiv = document.getElementById(datePickerDivID);
+ pickerDiv.style.visibility = "hidden";
+ pickerDiv.style.display = "none";
+
+ adjustiFrame();
+ targetDateField.focus();
+
+ // after the datepicker has closed, optionally run a user-defined function called
+ // datePickerClosed, passing the field that was just updated as a parameter
+ // (note that this will only run if the user actually selected a date from the datepicker)
+ if ((dateString) && (typeof(datePickerClosed) == "function"))
+ datePickerClosed(targetDateField);
+};
+
+
+/**
+Use an "iFrame shim" to deal with problems where the datepicker shows up behind
+selection list elements, if they're below the datepicker. The problem and solution are
+described at:
+
+http://dotnetjunkies.com/WebLog/jking/archive/2003/07/21/488.aspx
+http://dotnetjunkies.com/WebLog/jking/archive/2003/10/30/2975.aspx
+*/
+function adjustiFrame(pickerDiv, iFrameDiv)
+{
+ // we know that Opera doesn't like something about this, so if we
+ // think we're using Opera, don't even try
+ var is_opera = (navigator.userAgent.toLowerCase().indexOf("opera") != -1);
+ if (is_opera)
+ return;
+
+ // put a try/catch block around the whole thing, just in case
+ try {
+ if (!document.getElementById(iFrameDivID)) {
+ // don't use innerHTML to update the body, because it can cause global variables
+ // that are currently pointing to objects on the page to have bad references
+ //document.body.innerHTML += "<iframe id='" + iFrameDivID + "' src='javascript:false;' scrolling='no' frameborder='0'>";
+ var newNode = document.createElement("iFrame");
+ newNode.setAttribute("id", iFrameDivID);
+ newNode.setAttribute("src", "javascript:false;");
+ newNode.setAttribute("scrolling", "no");
+ newNode.setAttribute ("frameborder", "0");
+ document.body.appendChild(newNode);
+ }
+
+ if (!pickerDiv)
+ pickerDiv = document.getElementById(datePickerDivID);
+ if (!iFrameDiv)
+ iFrameDiv = document.getElementById(iFrameDivID);
+
+ try {
+ iFrameDiv.style.position = "absolute";
+ iFrameDiv.style.width = pickerDiv.offsetWidth;
+ iFrameDiv.style.height = pickerDiv.offsetHeight ;
+ iFrameDiv.style.top = pickerDiv.style.top;
+ iFrameDiv.style.left = pickerDiv.style.left;
+ iFrameDiv.style.zIndex = pickerDiv.style.zIndex - 1;
+ iFrameDiv.style.visibility = pickerDiv.style.visibility ;
+ iFrameDiv.style.display = pickerDiv.style.display;
+ } catch(e) {
+ }
+
+ } catch (ee) {
+ }
+
+};
115 wolf/admin/javascripts/control.textarea.js
View
@@ -0,0 +1,115 @@
+/**
+ * @author Ryan Johnson <ryan@livepipe.net>
+ * @copyright 2007 LivePipe LLC
+ * @package Control.TextArea
+ * @license MIT
+ * @url http://livepipe.net/projects/control_textarea/
+ * @version 2.0.0.RC1
+ */
+
+if(typeof(Control) == 'undefined')
+ Control = {};
+Control.TextArea = Class.create();
+Object.extend(Control.TextArea.prototype,{
+ onChangeTimeoutLength: 500,
+ element: false,
+ onChangeTimeout: false,
+ initialize: function(textarea){
+ this.element = $(textarea);
+ $(this.element).observe('keyup',this.doOnChange.bindAsEventListener(this));
+ $(this.element).observe('paste',this.doOnChange.bindAsEventListener(this));
+ $(this.element).observe('input',this.doOnChange.bindAsEventListener(this));
+ },
+ doOnChange: function(event){
+ if(this.onChangeTimeout)
+ window.clearTimeout(this.onChangeTimeout);
+ this.onChangeTimeout = window.setTimeout(function(){
+ if(this.notify)
+ this.notify('change',this.getValue());
+ }.bind(this),this.onChangeTimeoutLength);
+ },
+ getValue: function(){
+ return this.element.value;
+ },
+ getSelection: function(){
+ if(!!document.selection)
+ return document.selection.createRange().text;
+ else if(!!this.element.setSelectionRange)
+ return this.element.value.substring(this.element.selectionStart,this.element.selectionEnd);
+ else
+ return false;
+ },
+ replaceSelection: function(text){
+ if(!!document.selection){
+ this.element.focus();
+ var old = document.selection.createRange().text;
+ var range = document.selection.createRange();
+ if(old == '')
+ this.element.innerHTML += text;
+ else{
+ range.text = text;
+ range -= old.length - text.length;
+ }
+ }else if(!!this.element.setSelectionRange){
+ var selection_start = this.element.selectionStart;
+ this.element.value = this.element.value.substring(0,selection_start) + text + this.element.value.substring(this.element.selectionEnd);
+ //this.element.setSelectionRange(selection_start + text.length,selection_start + text.length);
+ this.element.setSelectionRange(selection_start, selection_start + text.length);
+ }
+ this.doOnChange();
+ this.element.focus();
+ },
+ wrapSelection: function(before,after){
+ this.replaceSelection(before + this.getSelection() + after);
+ },
+ insertBeforeSelection: function(text){
+ this.replaceSelection(text + this.getSelection());
+ },
+ insertAfterSelection: function(text){
+ this.replaceSelection(this.getSelection() + text);
+ },
+ injectEachSelectedLine: function(callback,before,after){
+ this.replaceSelection((before || '') + $A(this.getSelection().split("\n")).inject([],callback).join("\n") + (after || ''));
+ },
+ insertBeforeEachSelectedLine: function(text,before,after){
+ this.injectEachSelectedLine(function(lines,line){
+ lines.push(text + line);
+ return lines;
+ },before,after);
+ }
+});
+if(typeof(Object.Event) != 'undefined')
+ Object.Event.extend(Control.TextArea);
+
+Control.TextArea.ToolBar = Class.create();
+Object.extend(Control.TextArea.ToolBar.prototype,{
+ textarea: false,
+ container: false,
+ initialize: function(textarea,toolbar){
+ this.textarea = textarea;
+ if(toolbar)
+ this.container = $(toolbar);
+ else{
+ this.container = $(document.createElement('ul'));
+ this.textarea.element.parentNode.insertBefore(this.container,this.textarea.element);
+ }
+ },
+ attachButton: function(node,callback){
+ node.onclick = function(){return false;}
+ $(node).observe('click',callback.bindAsEventListener(this.textarea));
+ },
+ addButton: function(link_text,callback,attrs){
+ var li = document.createElement('li');
+ var a = document.createElement('a');
+ a.href = '#';
+ this.attachButton(a,callback);
+ li.appendChild(a);
+ Object.extend(a,attrs || {});
+ if(link_text){
+ var span = document.createElement('span');
+ span.innerHTML = link_text;
+ a.appendChild(span);
+ }
+ this.container.appendChild(li);
+ }
+});
1  wolf/admin/javascripts/cp-datepicker.js
View
@@ -0,0 +1 @@
+eval(function(p,a,c,k,e,d){e=function(c){return(c<a?"":e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)d[e(c)]=k[c]||e(c);k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('3 B="40";3 Y="3Z";3 1v=k Q(\'3Y\',\'3X\',\'3W\',\'3V\',\'3U\',\'3T\',\'3S\');3 26=k Q(\'3R\',\'3Q\',\'3P\',\'3O\',\'3N\',\'3M\',\'3L\');3 3K=k Q(\'3J\',\'3I\',\'3H\',\'3G\',\'3F\',\'3E\',\'3D\');3 3C=k Q(\'2H\',\'2G\',\'2F\',\'2E\',\'1C\',\'3B\',\'3A\',\'2D\',\'3z\',\'2C\',\'2B\',\'2A\');3 25=k Q(\'2H\',\'2G\',\'2F\',\'2E\',\'1C\',\'2z\',\'2y\',\'2D\',\'3y\',\'2C\',\'2B\',\'2A\');3 2f=k Q(\'3x\',\'3w\',\'3v\',\'3u\',\'1C\',\'2z\',\'2y\',\'3t\',\'3s\',\'3r\',\'3q\',\'3p\');3 1A="-";3 1y="1j";3 w=1A;3 T=1y;q 3o(l,I,1z,1B){3 s=j.1Q(l).1P(0);a(!I)I=s;a(1B)w=1B;t w=1A;a(1z)T=1z;t T=1y;3 x=I.2w;3 y=I.2v+I.1D;3 P=I;28(P.2x){P=P.2x;x+=P.2w;y+=P.2v}2u(s,x,y)};q 2u(s,x,y){3 19=1W(s.1O);a(!j.A(B)){3 p=j.1K("17");p.C("1J",B);p.C("c","3n");p.C("4","J: 1g;");j.1H.1G(p)}3 8=j.A(B);8.4.1F="1E";8.4.1c=x+"2t";8.4.1d=y+"2t";8.4.J=(8.4.J=="2s"?"1g":"2s");8.4.R=(8.4.R=="2r"?"1N":"2r");8.4.1b=3m;13(s.3l,19.L(),19.M(),19.H())};q 13(l,1x,14,1u){3 g=k E();a((14>=0)&&(1x>0)){g=k E(1x,14,1)}t{1u=g.H();g.29(1)}3 W="\\r\\n";3 2j="<2q 3k=7 c=\'3j\'>"+W;3 21="</2q>"+W;3 1r="<X c=\'3i\'>";3 2i="<X c=\'3h\'>";3 2e="<X c=\'3g\'>";3 24="<X c=\'3f\'>";3 O="</X>"+W;3 2a="<z c=\'2p\' 2n=\'U.18=\\"2p\\";\' 2l=\' U.18=\\"2k\\";\' ";3 1p="<z c=\'3e\'>";3 2h="<z 2o=5 c=\'3d\'>";3 1w="<z c=\'3c\'>";3 23="<z 2o=7 c=\'3b\'>";3 2d="<z c=\'3a\'>";3 2c="<z c=\'2m\' 2n=\'U.18=\\"2m\\";\' 2l=\'U.18=\\"2k\\";\' ";3 v="</z>"+W;3 2g="<17 c=\'39\'>";3 2b="<17 c=\'38\'>";3 1t="</17>";3 9=2j;9+=2i;9+=1w+1o(l,g,-1,"&37;")+v;9+=2h+2g+2f[g.M()]+" "+g.L()+1t+v;9+=1w+1o(l,g,1,"&36;")+v;9+=O;9+=2e;1q(i=0;i<1v.1k;i++)9+=2d+1v[i]+v;9+=O;9+=1r;1q(i=0;i<g.V();i++)9+=1p+"&27;"+v;35{16=g.H();1s=" 34=\\"1h(\'"+l+"\', \'"+1Z(g)+"\');\\">";a(16==1u)9+=2c+1s+2b+16+1t+v;t 9+=2a+1s+16+v;a(g.V()==6)9+=O+1r;g.29(g.H()+1)}28(g.H()>1)a(g.V()>0){1q(i=6;i>g.V();i--)9+=1p+"&27;"+v}9+=O;3 15=k E();3 33="32 31 "+26[15.V()]+", "+25[15.M()]+" "+15.H();9+=24+23;9+="<N c=\'22\' 1m=\'13(\\""+l+"\\");\'>U 14</N> ";9+="<N c=\'22\' 1m=\'1h(\\""+l+"\\");\'>30</N>";9+=v+O;9+=21;j.A(B).2Z=9;1f()};q 1o(l,h,1n,20){3 11=(h.M()+1n)%12;3 1l=h.L()+u((h.M()+1n)/12);a(11<0){11+=12;1l+=-1}D"<N c=\'2Y\' 1m=\'13(\\""+l+"\\", "+1l+", "+11+");\'>"+20+"</N>"};q 1Z(h){3 F="1Y"+h.H();3 G="1Y"+(h.M()+1);F=F.1X(F.1k-2);G=G.1X(G.1k-2);1V(T){K"1U":D F+w+G+w+h.L();K"1j":D h.L()+w+G+w+F;K"1T":1S:D G+w+F+w+h.L()}};q 1W(f){3 h;3 b;3 d,m,y;1e{b=1R(f);a(b){1V(T){K"1U":d=u(b[0],10);m=u(b[1],10)-1;y=u(b[2],10);1i;K"1j":d=u(b[2],10);m=u(b[1],10)-1;y=u(b[0],10);1i;K"1T":1S:d=u(b[1],10);m=u(b[0],10)-1;y=u(b[2],10);1i}h=k E(y,m,d)}t a(f){h=k E(f)}t{h=k E()}}1a(e){h=k E()}D h};q 1R(f){3 b;a(f.S("/")>=0)b=f.Z("/");t a(f.S(".")>=0)b=f.Z(".");t a(f.S("-")>=0)b=f.Z("-");t a(f.S("\\\\")>=0)b=f.Z("\\\\");t b=1I;D b};q 1h(l,f){3 s=j.1Q(l).1P(0);a(f)s.1O=f;3 8=j.A(B);8.4.J="1g";8.4.R="1N";1f();s.2X();a((f)&&(2W(1M)=="q"))1M(s)};q 1f(8,o){3 1L=(2V.2U.2T().S("2S")!=-1);a(1L)D;1e{a(!j.A(Y)){3 p=j.1K("2R");p.C("1J",Y);p.C("2Q","2P:1I;");p.C("2O","2N");p.C("2M","0");j.1H.1G(p)}a(!8)8=j.A(B);a(!o)o=j.A(Y);1e{o.4.1F="1E";o.4.2L=8.2K;o.4.2J=8.1D;o.4.1d=8.4.1d;o.4.1c=8.4.1c;o.4.1b=8.4.1b-1;o.4.J=8.4.J;o.4.R=8.4.R}1a(e){}}1a(2I){}};',62,249,'|||var|style||||pickerDiv|html|if|dArray|class|||dateString|thisDay|dateVal||document|new|dateFieldName|||iFrameDiv|newNode|function||targetDateField|else|parseInt|xTD|dateSeparator|||td|getElementById|datePickerDivID|setAttribute|return|Date|dayString|monthString|getDate|displayBelowThisObject|visibility|case|getFullYear|getMonth|button|xTR|parent|Array|display|indexOf|dateFormat|this|getDay|crlf|tr|iFrameDivID|split||newMonth||refreshDatePicker|month|today|dayNum|div|className|dt|catch|zIndex|left|top|try|adjustiFrame|hidden|updateDateField|break|ymd|length|newYear|onClick|adjust|getButtonCode|empty_TD|for|TR|TD_onclick|xDIV|day|dayArrayShort|TD_buttons|year|defaultDateFormat|dtFormat|defaultDateSeparator|dtSep|May|offsetHeight|absolute|position|appendChild|body|false|id|createElement|is_opera|datePickerClosed|none|value|item|getElementsByName|splitDateString|default|mdy|dmy|switch|getFieldDate|substring|00|getDateString|label|xTABLE|dpTodayButton|TD_todaybutton|TR_todaybutton|monthArrayMed|dayArrayMed|nbsp|while|setDate|TD|DIV_selected|TD_selected|TD_days|TR_days|monthArrayLong|DIV_title|TD_title|TR_title|TABLE|dpTDHover|onMouseOver|dpDayHighlightTD|onMouseOut|colspan|dpTD|table|block|visible|px|drawDatePicker|offsetTop|offsetLeft|offsetParent|July|June|Dec|Nov|Oct|Aug|Apr|Mar|Feb|Jan|ee|height|offsetWidth|width|frameborder|no|scrolling|javascript|src|iFrame|opera|toLowerCase|userAgent|navigator|typeof|focus|dpButton|innerHTML|close|is|Today|todayString|onclick|do|gt|lt|dpDayHighlight|dpTitleText|dpDayTD|dpTodayButtonTD|dpButtonTD|dpTitleTD|dpTDempty|dpTodayButtonTR|dpDayTR|dpTitleTR|dpTR|dpTable|cols|name|10000|dpDiv|displayDatePicker|December|November|October|September|August|April|March|February|January|Sept|Sep|Jul|Jun|monthArrayShort|Saturday|Friday|Thursday|Wednesday|Tuesday|Monday|Sunday|dayArrayLong|Sat|Fri|Thu|Wed|Tue|Mon|Sun|Sa|Fr|Th|We|Tu|Mo|Su|datepickeriframe|datepicker'.split('|'),0,{}))
974 wolf/admin/javascripts/dragdrop.js
View
@@ -0,0 +1,974 @@
+// script.aculo.us dragdrop.js v1.8.0, Tue Nov 06 15:01:40 +0300 2007
+
+// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+// (c) 2005-2007 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)
+//
+// 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 = Position.cumulativeOffset(this.element);
+ 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.element._originallyAbsolute = (this.element.getStyle('position') == 'absolute');
+ if (!this.element._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.element._originallyAbsolute)
+ Position.relativize(this.element);
+ delete this.element._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 = Position.cumulativeOffset(this.element);
+ 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){
+ var s = Sortable.options(element);
+
+ 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.id] = 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 = Position.cumulativeOffset(dropon);
+ 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')];
+}
1,122 wolf/admin/javascripts/effects.js
View
@@ -0,0 +1,1122 @@
+// script.aculo.us effects.js v1.8.0, Tue Nov 06 15:01:40 +0300 2007
+
+// Copyright (c) 2005-2007 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) + 0.5;
+ },
+ reverse: function(pos) {
+ return 1-pos;
+ },
+ flicker: function(pos) {
+ var pos = ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
+ return pos > 1 ? 1 : pos;
+ },
+ wobble: function(pos) {
+ return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
+ },
+ pulse: function(pos, pulses) {
+ pulses = pulses || 5;
+ return (
+ ((pos % (1/pulses)) * pulses).round() == 0 ?
+ ((pos * pulses * 2) - (pos * pulses * 2).floor()) :
+ 1 - ((pos * pulses * 2) - (pos * pulses * 2).floor())
+ );
+ },
+ 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) {
+ element = $(element);
+ effect = (effect || 'appear').toLowerCase();
+ var options = Object.extend({
+ queue: { position:'end', scope:(element.id || 'global'), limit: 1 }
+ }, arguments[2] || { });
+ Effect[element.visible() ?
+ Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, 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<len;i++)
+ this.effects[i] && this.effects[i].loop(timePos);
+ }
+});
+
+Effect.Queues = {
+ instances: $H(),
+ get: function(queueName) {
+ if (!Object.isString(queueName)) return queueName;
+
+ return this.instances.get(queueName) ||
+ this.instances.set(queueName, new Effect.ScopedQueue());
+ }
+};
+Effect.Queue = Effect.Queues.get('global');
+
+Effect.Base = Class.create({
+ position: null,
+ start: function(options) {
+ function codeForEvent(options,eventName){
+ return (
+ (options[eventName+'Internal'] ? 'this.options.'+eventName+'Internal(this);' : '') +
+ (options[eventName] ? 'this.options.'+eventName+'(this);' : '')
+ );
+ }
+ if (options && options.transition === false) options.transition = Effect.Transitions.linear;
+ this.options = Object.extend(Object.extend({ },Effect.DefaultOptions), options || { });
+ this.currentFrame = 0;
+ this.state = 'idle';
+ this.startOn = this.options.delay*1000;
+ this.finishOn = this.startOn+(this.options.duration*1000);
+ this.fromToDelta = this.options.to-this.options.from;
+ this.totalTime = this.finishOn-this.startOn;
+ this.totalFrames = this.options.fps*this.options.duration;
+
+ eval('this.render = function(pos){ '+
+ 'if (this.state=="idle"){this.state="running";'+
+ codeForEvent(this.options,'beforeSetup')+
+ (this.setup ? 'this.setup();':'')+
+ codeForEvent(this.options,'afterSetup')+
+ '};if (this.state=="running"){'+
+ 'pos=this.options.transition(pos)*'+this.fromToDelta+'+'+this.options.from+';'+
+ 'this.position=pos;'+
+ codeForEvent(this.options,'beforeUpdate')+
+ (this.update ? 'this.update(pos);':'')+
+ codeForEvent(this.options,'afterUpdate')+
+ '}}');
+
+ this.event('beforeStart');
+ if (!this.options.sync)
+ Effect.Queues.get(Object.isString(this.options.queue) ?
+ 'global' : this.options.queue.scope).add(this);
+ },
+ loop: function(timePos) {
+ if (timePos >= 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:' + data.inspect() + ',options:' + $H(this.options).inspect() + '>';
+ }
+});
+
+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(