Browse Source

Initial checkin

tags/2.1
Mike Macgirvin 9 years ago
commit
6348e70daa
100 changed files with 20867 additions and 0 deletions
  1. 2
    0
      .gitignore
  2. 13
    0
      .htaccess
  3. 79
    0
      auth.php
  4. 302
    0
      boot.php
  5. 227
    0
      cropper/copper.html
  6. 182
    0
      cropper/cropper.css
  7. 566
    0
      cropper/cropper.js
  8. 1331
    0
      cropper/cropper.uncompressed.js
  9. 101
    0
      cropper/lib/builder.js
  10. 815
    0
      cropper/lib/controls.js
  11. 915
    0
      cropper/lib/dragdrop.js
  12. 958
    0
      cropper/lib/effects.js
  13. 2006
    0
      cropper/lib/prototype.js
  14. 47
    0
      cropper/lib/scriptaculous.js
  15. 283
    0
      cropper/lib/slider.js
  16. 383
    0
      cropper/lib/unittest.js
  17. 12
    0
      cropper/licence.txt
  18. BIN
      cropper/marqueeHoriz.gif
  19. BIN
      cropper/marqueeVert.gif
  20. BIN
      cropper/tests/castle.jpg
  21. BIN
      cropper/tests/castleMed.jpg
  22. 106
    0
      cropper/tests/example-Basic.htm
  23. 162
    0
      cropper/tests/example-CSS-Absolute.htm
  24. 124
    0
      cropper/tests/example-CSS-Float.htm
  25. 116
    0
      cropper/tests/example-CSS-Relative.htm
  26. 108
    0
      cropper/tests/example-CoordsOnLoad.htm
  27. 109
    0
      cropper/tests/example-CoordsOnLoadWithRatio.htm
  28. 225
    0
      cropper/tests/example-Dimensions.htm
  29. 203
    0
      cropper/tests/example-DynamicImage.htm
  30. 104
    0
      cropper/tests/example-FixedRatio.htm
  31. 105
    0
      cropper/tests/example-MinimumDimensions.htm
  32. 105
    0
      cropper/tests/example-MinimumWidth.htm
  33. 117
    0
      cropper/tests/example-Preview.htm
  34. BIN
      cropper/tests/poppy.jpg
  35. 236
    0
      cropper/tests/staticHTMLStructure.htm
  36. 0
    0
      favicon.gif
  37. 0
    0
      favicon.ico
  38. BIN
      images/b_block.gif
  39. BIN
      images/b_drop.gif
  40. BIN
      images/b_drop.png
  41. BIN
      images/b_edit.gif
  42. BIN
      images/b_edit.png
  43. BIN
      images/default-profile-sm.jpg
  44. BIN
      images/default-profile.jpg
  45. BIN
      images/larrw.gif
  46. BIN
      images/rarrw.gif
  47. 171
    0
      include/Photo.php
  48. 80
    0
      include/Scrape.php
  49. 105
    0
      include/bbcode.php
  50. 145
    0
      include/datetime.php
  51. 138
    0
      include/dba.php
  52. 19
    0
      include/login.php
  53. 17
    0
      include/security.php
  54. 76
    0
      include/session.php
  55. 6
    0
      include/system_unavailable.php
  56. 113
    0
      index.php
  57. 120
    0
      library/HTML5/Data.php
  58. 284
    0
      library/HTML5/InputStream.php
  59. 36
    0
      library/HTML5/Parser.php
  60. 2307
    0
      library/HTML5/Tokenizer.php
  61. 3715
    0
      library/HTML5/TreeBuilder.php
  62. 1
    0
      library/HTML5/named-character-references.ser
  63. 116
    0
      mod/contacts.php
  64. 374
    0
      mod/dfrn_confirm.php
  65. 58
    0
      mod/dfrn_poll.php
  66. 290
    0
      mod/dfrn_request.php
  67. 24
    0
      mod/home.php
  68. 68
    0
      mod/item.php
  69. 8
    0
      mod/login.php
  70. 98
    0
      mod/notifications.php
  71. 25
    0
      mod/photo.php
  72. 136
    0
      mod/profile.php
  73. 227
    0
      mod/profile_photo.php
  74. 190
    0
      mod/profiles.php
  75. 21
    0
      mod/redir.php
  76. 175
    0
      mod/register.php
  77. 170
    0
      mod/settings.php
  78. 4
    0
      mod/test.php
  79. 23
    0
      nav.php
  80. 0
    0
      robots.txt
  81. BIN
      silho.gif
  82. BIN
      silho.ico
  83. 1075
    0
      tinymce/changelog.txt
  84. 105
    0
      tinymce/examples/css/content.css
  85. 53
    0
      tinymce/examples/css/word.css
  86. 107
    0
      tinymce/examples/custom_formats.html
  87. 96
    0
      tinymce/examples/full.html
  88. 10
    0
      tinymce/examples/index.html
  89. 9
    0
      tinymce/examples/lists/image_list.js
  90. 10
    0
      tinymce/examples/lists/link_list.js
  91. 10
    0
      tinymce/examples/lists/media_list.js
  92. 9
    0
      tinymce/examples/lists/template_list.js
  93. BIN
      tinymce/examples/media/logo.jpg
  94. BIN
      tinymce/examples/media/logo_over.jpg
  95. BIN
      tinymce/examples/media/sample.avi
  96. BIN
      tinymce/examples/media/sample.dcr
  97. BIN
      tinymce/examples/media/sample.mov
  98. 1
    0
      tinymce/examples/media/sample.ram
  99. BIN
      tinymce/examples/media/sample.rm
  100. 0
    0
      tinymce/examples/media/sample.swf

+ 2
- 0
.gitignore View File

@@ -0,0 +1,2 @@
1
+.htconfig.php
2
+\#*

+ 13
- 0
.htaccess View File

@@ -0,0 +1,13 @@
1
+
2
+Options -Indexes
3
+
4
+<IfModule mod_rewrite.c>
5
+  RewriteEngine on
6
+  
7
+  # Rewrite current-style URLs of the form 'index.php?q=x'.
8
+  RewriteCond %{REQUEST_FILENAME} !-f
9
+  RewriteCond %{REQUEST_FILENAME} !-d
10
+  RewriteRule ^(.*)$ index.php?q=$1 [L,QSA]
11
+
12
+</IfModule>
13
+

+ 79
- 0
auth.php View File

@@ -0,0 +1,79 @@
1
+<?php
2
+
3
+
4
+
5
+if((x($_SESSION,'authenticated')) && (! ($_POST['auth-params'] == 'login'))) {
6
+	if($_POST['auth-params'] == 'logout' || $a->module == "logout") {
7
+		unset($_SESSION['authenticated']);
8
+		unset($_SESSION['uid']);
9
+		unset($_SESSION['visitor_id']);
10
+		unset($_SESSION['administrator']);
11
+		$_SESSION['sysmsg'] = "Logged out." . EOL;
12
+		goaway($a->get_baseurl());
13
+	}
14
+	if(x($_SESSION,'uid')) {
15
+		$r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
16
+			intval($_SESSION['uid']));
17
+		if($r === NULL || (! count($r))) {
18
+			goaway($a->get_baseurl());
19
+		}
20
+		$a->user = $r[0];
21
+		if(strlen($a->user['timezone']))
22
+			date_default_timezone_set($a->user['timezone']);
23
+
24
+	}
25
+}
26
+else {
27
+	unset($_SESSION['authenticated']);
28
+	unset($_SESSION['uid']);
29
+	unset($_SESSION['visitor_id']);
30
+	unset($_SESSION['administrator']);
31
+	$encrypted = hash('whirlpool',trim($_POST['password']));
32
+
33
+	if((x($_POST,'auth-params')) && $_POST['auth-params'] == 'login') {
34
+		$r = q("SELECT * FROM `user` 
35
+			WHERE `email` = '%s' AND `password` = '%s' LIMIT 1",
36
+			dbesc(trim($_POST['login-name'])),
37
+			dbesc($encrypted));
38
+		if(($r === false) || (! count($r))) {
39
+			$_SESSION['sysmsg'] = 'Login failed.' . EOL ;
40
+			goaway($a->get_baseurl());
41
+  		}
42
+		$_SESSION['uid'] = $r[0]['uid'];
43
+		$_SESSION['admin'] = $r[0]['admin'];
44
+		$_SESSION['authenticated'] = 1;
45
+		if(x($r[0],'nickname'))
46
+			$_SESSION['my_url'] = $a->get_baseurl() . '/profile/' . $r[0]['nickname'];
47
+		else
48
+			$_SESSION['my_url'] = $a->get_baseurl() . '/profile/' . $r[0]['uid'];
49
+
50
+		$_SESSION['sysmsg'] = "Welcome back " . $r[0]['username'] . EOL;
51
+		$a->user = $r[0];
52
+		if(strlen($a->user['timezone']))
53
+			date_default_timezone_set($a->user['timezone']);
54
+
55
+	}
56
+}
57
+
58
+// Returns an array of group names this contact is a member of.
59
+// Since contact-id's are unique and each "belongs" to a given user uid,
60
+// this array will only contain group names related to the uid of this
61
+// DFRN contact. They are *not* neccessarily unique across the entire site. 
62
+
63
+
64
+if(! function_exists('init_groups_visitor')) {
65
+function init_groups_visitor($contact_id) {
66
+	$groups = array();
67
+	$r = q("SELECT `group_member`.`gid`, `group`.`name` 
68
+		FROM `group_member` LEFT JOIN `group` ON `group_member`.`gid` = `group`.`id` 
69
+		WHERE `group_member`.`contact-id` = %d ",
70
+		intval($contact_id)
71
+	);
72
+	if(count($r)) {
73
+		foreach($r as $rr)
74
+			$groups[] = $rr['name'];
75
+	}
76
+	return $groups;
77
+}}
78
+
79
+

+ 302
- 0
boot.php View File

@@ -0,0 +1,302 @@
1
+<?php
2
+
3
+set_time_limit(0);
4
+
5
+define('EOL', '<br />');
6
+
7
+define('REGISTER_CLOSED',  0);
8
+define('REGISTER_APPROVE', 1);
9
+define('REGISTER_OPEN',    2);
10
+
11
+
12
+if(! class_exists('App')) {
13
+class App {
14
+
15
+	public  $module_loaded = false;
16
+	public  $config;
17
+	public  $page;
18
+	public  $profile;
19
+	public  $user;
20
+	public  $content;
21
+	public  $error = false;
22
+	public  $cmd;
23
+	public  $argv;
24
+	public  $argc;
25
+	public  $module;
26
+
27
+	private $scheme;
28
+	private $hostname;
29
+	private $path;
30
+	private $db;
31
+
32
+	function __construct() {
33
+
34
+		$this->config = array();
35
+		$this->page = array();
36
+
37
+		$this->scheme = ((isset($_SERVER['HTTPS']) 
38
+				&& ($_SERVER['HTTPS']))	?  'https' : 'http' );
39
+		$this->hostname = str_replace('www.','',
40
+				$_SERVER['SERVER_NAME']);
41
+		set_include_path("include/$this->hostname" 
42
+				. PATH_SEPARATOR . 'include' 
43
+				. PATH_SEPARATOR . '.' );
44
+
45
+                if(substr($_SERVER['QUERY_STRING'],0,2) == "q=")
46
+			$_SERVER['QUERY_STRING'] = substr($_SERVER['QUERY_STRING'],2);
47
+//		$this->cmd = trim($_SERVER['QUERY_STRING'],'/');
48
+		$this->cmd = trim($_GET['q'],'/');
49
+
50
+		$this->argv = explode('/',$this->cmd);
51
+		$this->argc = count($this->argv);
52
+		if((array_key_exists('0',$this->argv)) && strlen($this->argv[0])) {
53
+			$this->module = $this->argv[0];
54
+		}
55
+		else {
56
+			$this->module = 'home';
57
+		}
58
+	}
59
+
60
+	function get_baseurl($ssl = false) {
61
+		
62
+		return (($ssl) ? 'https' : $this->scheme) . "://" . $this->hostname
63
+			. ((isset($this->path) && strlen($this->path)) 
64
+			? '/' . $this->path : '' );
65
+	}
66
+
67
+	function set_path($p) {
68
+		$this->path = ltrim(trim($p),'/');
69
+	} 
70
+
71
+	function init_pagehead() {
72
+		if(file_exists("view/head.tpl"))
73
+			$s = file_get_contents("view/head.tpl");
74
+		$this->page['htmlhead'] = replace_macros($s,array('$baseurl' => $this->get_baseurl()));
75
+	}
76
+
77
+}}
78
+
79
+
80
+if(! function_exists('x')) {
81
+function x($s,$k = NULL) {
82
+	if($k != NULL) {
83
+		if((is_array($s)) && (array_key_exists($k,$s))) {
84
+			if($s[$k])
85
+				return (int) 1;
86
+			return (int) 0;
87
+		}
88
+		return false;
89
+	}
90
+	else {		
91
+		if(isset($s)) {
92
+			if($s) {
93
+				return (int) 1;
94
+			}
95
+			return (int) 0;
96
+		}
97
+		return false;
98
+	}
99
+}}
100
+
101
+if(! function_exists('system_unavailable')) {
102
+function system_unavailable() {
103
+	include('system_unavailable.php');
104
+	killme();
105
+}}
106
+
107
+if(! function_exists('replace_macros')) {  
108
+function replace_macros($s,$r) {
109
+
110
+	$search = array();
111
+	$replace = array();
112
+
113
+	if(is_array($r) && count($r)) {
114
+		foreach ($r as $k => $v ) {
115
+			$search[] =  $k;
116
+			$replace[] = $v;
117
+		}
118
+	}
119
+	return str_replace($search,$replace,$s);
120
+}}
121
+
122
+
123
+
124
+if(! function_exists('fetch_url')) {
125
+function fetch_url($url,$binary = false) {
126
+	$ch = curl_init($url);
127
+	if(! $ch) return false;
128
+
129
+        curl_setopt($ch, CURLOPT_HEADER, 0);
130
+	curl_setopt($ch, CURLOPT_FOLLOWLOCATION,true);
131
+	curl_setopt($ch, CURLOPT_MAXREDIRS,8);
132
+	curl_setopt($ch, CURLOPT_RETURNTRANSFER,true);
133
+	if($binary)
134
+		curl_setopt($ch, CURLOPT_BINARYTRANSFER,1);
135
+
136
+	$s = curl_exec($ch);
137
+	curl_close($ch);
138
+	return($s);
139
+}}
140
+
141
+
142
+if(! function_exists('post_url')) {
143
+function post_url($url,$params) {
144
+	$ch = curl_init($url);
145
+	if(! $ch) return false;
146
+
147
+        curl_setopt($ch, CURLOPT_HEADER, 0);
148
+	curl_setopt($ch, CURLOPT_FOLLOWLOCATION,true);
149
+	curl_setopt($ch, CURLOPT_MAXREDIRS,8);
150
+	curl_setopt($ch, CURLOPT_RETURNTRANSFER,true);
151
+	curl_setopt($ch, CURLOPT_POST,1);
152
+	curl_setopt($ch, CURLOPT_POSTFIELDS,$params);
153
+
154
+	$s = curl_exec($ch);
155
+	curl_close($ch);
156
+	return($s);
157
+}}
158
+
159
+
160
+if(! function_exists('random_string')) {
161
+function random_string() {
162
+	return(hash('sha256',uniqid(rand(),true)));
163
+}}
164
+
165
+if(! function_exists('notags')) {
166
+function notags($string) {
167
+	// protect against :<> with high-bit set
168
+	return(str_replace(array("<",">","\xBA","\xBC","\xBE"), array('[',']','','',''), $string));
169
+}}
170
+
171
+// The PHP built-in tag escape function has traditionally been buggy
172
+if(! function_exists('escape_tags')) {
173
+function escape_tags($string) {
174
+	return(str_replace(array("<",">","&"), array('&lt;','&gt;','&amp;'), $string));
175
+}}
176
+
177
+if(! function_exists('login')) {
178
+function login($register = false) {
179
+	$o = "";
180
+	$register_html = (($register) ? file_get_contents("view/register-link.tpl") : "");
181
+
182
+
183
+	if(x($_SESSION,'authenticated')) {
184
+		$o = file_get_contents("view/logout.tpl");
185
+	}
186
+	else {
187
+		$o = file_get_contents("view/login.tpl");
188
+
189
+		$o = replace_macros($o,array('$register_html' => $register_html ));
190
+	}
191
+	return $o;
192
+}}
193
+
194
+
195
+if(! function_exists('autoname')) {
196
+function autoname($len) {
197
+
198
+	$vowels = array('a','a','ai','au','e','e','e','ee','ea','i','ie','o','ou','u'); 
199
+	if(mt_rand(0,5) == 4)
200
+		$vowels[] = 'y';
201
+
202
+	$cons = array(
203
+			'b','bl','br',
204
+			'c','ch','cl','cr',
205
+			'd','dr',
206
+			'f','fl','fr',
207
+			'g','gh','gl','gr',
208
+			'h',
209
+			'j',
210
+			'k','kh','kl','kr',
211
+			'l',
212
+			'm',
213
+			'n',
214
+			'p','ph','pl','pr',
215
+			'qu',
216
+			'r','rh',
217
+			's','sc','sh','sm','sp','st',
218
+			't','th','tr',
219
+			'v',
220
+			'w','wh',
221
+			'x',
222
+			'z','zh'
223
+			);
224
+
225
+	$midcons = array('ck','ct','gn','ld','lf','lm','lt','mb','mm', 'mn','mp',
226
+				'nd','ng','nk','nt','rn','rp','rt');
227
+
228
+	$noend = array('bl', 'br', 'cl','cr','dr','fl','fr','gl','gr',
229
+				'kh', 'kl','kr','mn','pl','pr','rh','tr','qu','wh');
230
+
231
+	$start = mt_rand(0,2);
232
+  	if($start == 0)
233
+    		$table = $vowels;
234
+  	else
235
+    		$table = $cons;
236
+
237
+	$word = '';
238
+
239
+	for ($x = 0; $x < $len; $x ++) {
240
+  		$r = mt_rand(0,count($table) - 1);
241
+  		$word .= $table[$r];
242
+  
243
+  		if($table == $vowels)
244
+    			$table = array_merge($cons,$midcons);
245
+  		else
246
+    			$table = $vowels;
247
+
248
+	}
249
+
250
+	$word = substr($word,0,$len);
251
+
252
+	foreach($noend as $noe) {
253
+  		if((strlen($word) > 2) && (substr($word,-2) == $noe)) {
254
+    			$word = substr($word,0,-1);
255
+    			break;
256
+  		}
257
+	}
258
+	if(substr($word,-1) == 'q')
259
+		$word = substr($word,0,-1);    
260
+	return $word;
261
+}}
262
+
263
+if(! function_exists('killme')) {
264
+function killme() {
265
+	session_write_close();
266
+	exit;
267
+}}
268
+
269
+if(! function_exists('goaway')) {
270
+function goaway($s) {
271
+	header("Location: $s");
272
+	killme();
273
+}}
274
+
275
+
276
+if(! function_exists('xml_status')) {
277
+function xml_status($st) {
278
+	header( "Content-type: text/xml");
279
+	echo '<?xml version="1.0" encoding="UTF-8"?>'."\r\n";
280
+	echo "<result><status>$st</status></result>\r\n";
281
+	killme();
282
+}}
283
+
284
+if(! function_exists('local_user')) {
285
+function local_user() {
286
+	if((x($_SESSION,'authenticated')) && (x($_SESSION,'uid')))
287
+		return $_SESSION['uid'];
288
+	return false;
289
+}}
290
+
291
+if(! function_exists('remote_user')) {
292
+function remote_user() {
293
+	if((x($_SESSION,'authenticated')) && (x($_SESSION,'cid')))
294
+		return $_SESSION['cid'];
295
+	return false;
296
+}}
297
+
298
+function footer(&$a) {
299
+
300
+	$s = fetch_url("http://fortunemod.com/cookie.php?equal=1");
301
+	$a->page['content'] .= "<div class=\"fortune\" >$s</div>"; 
302
+}

+ 227
- 0
cropper/copper.html View File

@@ -0,0 +1,227 @@
1
+   1.
2
+      <script type="text/javascript" src="scripts/cropper/lib/prototype.js" language="javascript"></script>
3
+   2.
4
+      <script type="text/javascript" src="scripts/cropper/lib/scriptaculous.js?load=builder,dragdrop" language="javascript"></script>
5
+   3.
6
+      <script type="text/javascript" src="scripts/cropper/cropper.js" language="javascript"></script>
7
+
8
+Options
9
+
10
+ratioDim obj
11
+    The pixel dimensions to apply as a restrictive ratio, with properties x & y.
12
+minWidth int
13
+    The minimum width for the select area in pixels.
14
+minHeight int
15
+    The mimimum height for the select area in pixels.
16
+maxWidth int
17
+    The maximum width for the select areas in pixels (if both minWidth & maxWidth set to same the width of the cropper will be fixed)
18
+maxHeight int
19
+    The maximum height for the select areas in pixels (if both minHeight & maxHeight set to same the height of the cropper will be fixed)
20
+displayOnInit int
21
+    Whether to display the select area on initialisation, only used when providing minimum width & height or ratio.
22
+onEndCrop func
23
+    The callback function to provide the crop details to on end of a crop.
24
+captureKeys boolean
25
+    Whether to capture the keys for moving the select area, as these can cause some problems at the moment.
26
+onloadCoords obj
27
+    A coordinates object with properties x1, y1, x2 & y2; for the coordinates of the select area to display onload
28
+
29
+The callback function
30
+
31
+The callback function is a function that allows you to capture the crop co-ordinates when the user finished a crop movement, it is passed two arguments:
32
+
33
+    * coords, obj, coordinates object with properties x1, y1, x2 & y2; for the coordinates of the select area.
34
+    * dimensions, obj, dimensions object with properities width & height; for the dimensions of the select area.
35
+
36
+An example function which outputs the crop values to form fields:
37
+Display code as plain text
38
+JavaScript:
39
+
40
+   1.
41
+      function onEndCrop( coords, dimensions ) {
42
+   2.
43
+          $( 'x1' ).value = coords.x1;
44
+   3.
45
+          $( 'y1' ).value = coords.y1;
46
+   4.
47
+          $( 'x2' ).value = coords.x2;
48
+   5.
49
+          $( 'y2' ).value = coords.y2;
50
+   6.
51
+          $( 'width' ).value = dimensions.width;
52
+   7.
53
+          $( 'height' ).value = dimensions.height;
54
+   8.
55
+      }
56
+
57
+Basic interface
58
+
59
+This basic example will attach the cropper UI to the test image and return crop results to the provided callback function.
60
+Display code as plain text
61
+HTML:
62
+
63
+   1.
64
+      <img src="test.jpg" alt="Test image" id="testImage" width="500" height="333" />
65
+   2.
66
+       
67
+   3.
68
+          <script type="text/javascript" language="javascript">
69
+   4.
70
+          Event.observe( window, 'load', function() {
71
+   5.
72
+              new Cropper.Img(
73
+   6.
74
+                  'testImage',
75
+   7.
76
+                  { onEndCrop: onEndCrop }
77
+   8.
78
+              );
79
+   9.
80
+          } );
81
+  10.
82
+      </script>
83
+
84
+Minimum dimensions
85
+
86
+You can apply minimum dimensions to a single axis or both, this example applies minimum dimensions to both axis.
87
+Display code as plain text
88
+HTML:
89
+
90
+   1.
91
+      <img src="test.jpg" alt="Test image" id="testImage" width="500" height="333" />
92
+   2.
93
+       
94
+   3.
95
+      <script type="text/javascript" language="javascript">
96
+   4.
97
+          Event.observe( window, 'load', function() {
98
+   5.
99
+              new Cropper.Img(
100
+   6.
101
+                  'testImage',
102
+   7.
103
+                  {
104
+   8.
105
+                      minWidth: 220,
106
+   9.
107
+                      minHeight: 120,
108
+  10.
109
+                      onEndCrop: onEndCrop
110
+  11.
111
+                  }
112
+  12.
113
+              );
114
+  13.
115
+          } );
116
+  14.
117
+      </script>
118
+
119
+Select area ratio
120
+
121
+You can apply a ratio to the selection area, this example applies a 4:3 ratio to the select area.
122
+Display code as plain text
123
+HTML:
124
+
125
+   1.
126
+      <img src="test.jpg" alt="Test image" id="testImage" width="500" height="333" />
127
+   2.
128
+       
129
+   3.
130
+      <script type="text/javascript" language="javascript">
131
+   4.
132
+          Event.observe( window, 'load', function() {
133
+   5.
134
+              new Cropper.Img(
135
+   6.
136
+                  'testImage',
137
+   7.
138
+                  {
139
+   8.
140
+                      ratioDim: {
141
+   9.
142
+                          x: 220,
143
+  10.
144
+                          y: 165
145
+  11.
146
+                      },
147
+  12.
148
+                      displayOnInit: true,
149
+  13.
150
+                      onEndCrop: onEndCrop
151
+  14.
152
+                  }
153
+  15.
154
+              );
155
+  16.
156
+          } );
157
+  17.
158
+      </script>
159
+
160
+With crop preview
161
+
162
+You can display a dynamically prouced preview of the resulting crop by using the ImgWithPreview subclass, a preview can only be displayed when we have a fixed size (set via minWidth & minHeight options). Note that the displayOnInit option is not required as this is the default behaviour when displaying a crop preview.
163
+Display code as plain text
164
+HTML:
165
+
166
+   1.
167
+      <img src="test.jpg" alt="Test image" id="testImage" width="500" height="333" />
168
+   2.
169
+      <div id="previewWrap"></div>
170
+   3.
171
+       
172
+   4.
173
+      <script type="text/javascript" language="javascript">
174
+   5.
175
+          Event.observe( window, 'load', function() {
176
+   6.
177
+              new Cropper.ImgWithPreview(
178
+   7.
179
+                  'testImage',
180
+   8.
181
+                  {
182
+   9.
183
+                      previewWrap: 'previewWrap',
184
+  10.
185
+                      minWidth: 120,
186
+  11.
187
+                      minHeight: 120,
188
+  12.
189
+                      ratioDim: { x: 200, y: 120 },
190
+  13.
191
+                      onEndCrop: onEndCrop
192
+  14.
193
+                  }
194
+  15.
195
+              );
196
+  16.
197
+          } );
198
+  17.
199
+      </script>
200
+
201
+Known Issues
202
+
203
+    * Safari animated gifs, only one of each will animate, this seems to be a known Safari issue.
204
+    * After drawing an area and then clicking to start a new drag in IE 5.5 the rendered height appears as the last height until the user drags, this appears to be the related to another IE error (which has been fixed) where IE does not always redraw the select area properly.
205
+    * Lack of CSS opacity support in Opera before version 9 mean we disable those style rules, if Opera 8 support is important you & you want the overlay to work then you can use the Opera rules in the CSS to apply a black PNG with 50% alpha transparency to replicate the effect.
206
+    * Styling & borders on image, any CSS styling applied directly to the image itself (floats, borders, padding, margin, etc.) will cause problems with the cropper. The use of a wrapper element to apply these styles to is recommended.
207
+    * overflow: auto or overflow: scroll on parent will cause cropper to burst out of parent in IE and Opera when applied (maybe Mac browsers too) I'm not sure why yet.
208
+
209
+If you use CakePHP you will notice that including this in your script will break the CSS layout. This is due to the CSS rule
210
+
211
+form div{
212
+vertical-align: text-top;
213
+margin-left: 1em;
214
+margin-bottom:2em;
215
+overflow: auto;
216
+}
217
+
218
+A simple workaround is to add another rule directly after this like so:
219
+
220
+form div.no_cake, form div.no_cake div {
221
+margin:0;
222
+overflow:hidden;
223
+}
224
+
225
+and then in your code surround the img tag with a div with the class name of no_cake.
226
+
227
+Cheers

+ 182
- 0
cropper/cropper.css View File

@@ -0,0 +1,182 @@
1
+.imgCrop_wrap {
2
+	/* width: 500px;   @done_in_js */
3
+	/* height: 375px;  @done_in_js */
4
+	position: relative;
5
+	cursor: crosshair;
6
+}
7
+
8
+/* an extra classname is applied for Opera < 9.0 to fix it's lack of opacity support */
9
+.imgCrop_wrap.opera8 .imgCrop_overlay,
10
+.imgCrop_wrap.opera8 .imgCrop_clickArea { 
11
+	background-color: transparent;
12
+}
13
+
14
+/* fix for IE displaying all boxes at line-height by default, although they are still 1 pixel high until we combine them with the pointless span */
15
+.imgCrop_wrap,
16
+.imgCrop_wrap * {
17
+	font-size: 0;
18
+}
19
+
20
+.imgCrop_overlay {
21
+	background-color: #000;
22
+	opacity: 0.5;
23
+	filter:alpha(opacity=50);
24
+	position: absolute;
25
+	width: 100%;
26
+	height: 100%;
27
+}
28
+
29
+.imgCrop_selArea {
30
+	position: absolute;
31
+	/* @done_in_js 
32
+	top: 20px;
33
+	left: 20px;
34
+	width: 200px;
35
+	height: 200px;
36
+	background: transparent url(castle.jpg) no-repeat  -210px -110px;
37
+	*/
38
+	cursor: move;
39
+	z-index: 2;
40
+}
41
+
42
+/* clickArea is all a fix for IE 5.5 & 6 to allow the user to click on the given area */
43
+.imgCrop_clickArea {
44
+	width: 100%;
45
+	height: 100%;
46
+	background-color: #FFF;
47
+	opacity: 0.01;
48
+	filter:alpha(opacity=01);
49
+}
50
+
51
+.imgCrop_marqueeHoriz {
52
+	position: absolute;
53
+	width: 100%;
54
+	height: 1px;
55
+	background: transparent url(marqueeHoriz.gif) repeat-x 0 0;
56
+	z-index: 3;
57
+}
58
+
59
+.imgCrop_marqueeVert {
60
+	position: absolute;
61
+	height: 100%;
62
+	width: 1px;
63
+	background: transparent url(marqueeVert.gif) repeat-y 0 0;
64
+	z-index: 3;
65
+}
66
+
67
+/* 
68
+ *  FIX MARCHING ANTS IN IE
69
+ *	As IE <6 tries to load background images we can uncomment the follwoing hack 
70
+ *  to remove that issue, not as pretty - but is anything in IE?
71
+ *  And yes I do know that 'filter' is evil, but it will make it look semi decent in IE
72
+ *
73
+* html .imgCrop_marqueeHoriz,
74
+* html .imgCrop_marqueeVert {
75
+	background: transparent;
76
+	filter: Invert; 
77
+}
78
+* html .imgCrop_marqueeNorth { border-top: 1px dashed #000; }
79
+* html .imgCrop_marqueeEast  { border-right: 1px dashed #000; }
80
+* html .imgCrop_marqueeSouth { border-bottom: 1px dashed #000; }
81
+* html .imgCrop_marqueeWest  { border-left: 1px dashed #000; }
82
+*/
83
+
84
+.imgCrop_marqueeNorth { top: 0; left: 0; }
85
+.imgCrop_marqueeEast  { top: 0; right: 0; }
86
+.imgCrop_marqueeSouth { bottom: 0px; left: 0; }
87
+.imgCrop_marqueeWest  { top: 0; left: 0; }
88
+
89
+
90
+.imgCrop_handle {
91
+	position: absolute;
92
+	border: 1px solid #333;
93
+	width: 6px;
94
+	height: 6px;
95
+	background: #FFF;
96
+	opacity: 0.5;
97
+	filter:alpha(opacity=50);
98
+	z-index: 4;
99
+}
100
+
101
+/* fix IE 5 box model */
102
+* html .imgCrop_handle {
103
+	width: 8px;
104
+	height: 8px;
105
+	wid\th: 6px;
106
+	hei\ght: 6px;
107
+}
108
+
109
+.imgCrop_handleN {
110
+	top: -3px;
111
+	left: 0;
112
+	/* margin-left: 49%;    @done_in_js */
113
+	cursor: n-resize;
114
+}
115
+
116
+.imgCrop_handleNE { 
117
+	top: -3px;
118
+	right: -3px;
119
+	cursor: ne-resize;
120
+}
121
+
122
+.imgCrop_handleE {
123
+	top: 0;
124
+	right: -3px;
125
+	/* margin-top: 49%;    @done_in_js */
126
+	cursor: e-resize;
127
+}
128
+
129
+.imgCrop_handleSE {
130
+	right: -3px;
131
+	bottom: -3px;
132
+	cursor: se-resize;
133
+}
134
+
135
+.imgCrop_handleS {
136
+	right: 0;
137
+	bottom: -3px;
138
+	/* margin-right: 49%; @done_in_js */
139
+	cursor: s-resize;
140
+}
141
+
142
+.imgCrop_handleSW {
143
+	left: -3px;
144
+	bottom: -3px;
145
+	cursor: sw-resize;
146
+}
147
+
148
+.imgCrop_handleW {
149
+	top: 0;
150
+	left: -3px;
151
+	/* margin-top: 49%;  @done_in_js */
152
+	cursor: w-resize;
153
+}
154
+
155
+.imgCrop_handleNW {
156
+	top: -3px;
157
+	left: -3px;
158
+	cursor: nw-resize;
159
+}
160
+
161
+/**
162
+ * Create an area to click & drag around on as the default browser behaviour is to let you drag the image 
163
+ */
164
+.imgCrop_dragArea {
165
+	width: 100%;
166
+	height: 100%;
167
+	z-index: 200;
168
+	position: absolute;
169
+	top: 0;
170
+	left: 0;
171
+}
172
+
173
+.imgCrop_previewWrap {
174
+	/* width: 200px;  @done_in_js */
175
+	/* height: 200px; @done_in_js */
176
+	overflow: hidden;
177
+	position: relative;
178
+}
179
+
180
+.imgCrop_previewWrap img {
181
+	position: absolute;
182
+}

+ 566
- 0
cropper/cropper.js View File

@@ -0,0 +1,566 @@
1
+/** 
2
+ * Copyright (c) 2006, David Spurr (http://www.defusion.org.uk/)
3
+ * All rights reserved.
4
+ * 
5
+ * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
6
+ * 
7
+ *     * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
8
+ *     * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
9
+ *     * Neither the name of the David Spurr nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
10
+ * 
11
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
12
+ * 
13
+ * http://www.opensource.org/licenses/bsd-license.php
14
+ * 
15
+ * See scriptaculous.js for full scriptaculous licence
16
+ */
17
+
18
+var CropDraggable=Class.create();
19
+Object.extend(Object.extend(CropDraggable.prototype,Draggable.prototype),{initialize:function(_1){
20
+this.options=Object.extend({drawMethod:function(){
21
+}},arguments[1]||{});
22
+this.element=$(_1);
23
+this.handle=this.element;
24
+this.delta=this.currentDelta();
25
+this.dragging=false;
26
+this.eventMouseDown=this.initDrag.bindAsEventListener(this);
27
+Event.observe(this.handle,"mousedown",this.eventMouseDown);
28
+Draggables.register(this);
29
+},draw:function(_2){
30
+var _3=Position.cumulativeOffset(this.element);
31
+var d=this.currentDelta();
32
+_3[0]-=d[0];
33
+_3[1]-=d[1];
34
+var p=[0,1].map(function(i){
35
+return (_2[i]-_3[i]-this.offset[i]);
36
+}.bind(this));
37
+this.options.drawMethod(p);
38
+}});
39
+var Cropper={};
40
+Cropper.Img=Class.create();
41
+Cropper.Img.prototype={initialize:function(_7,_8){
42
+this.options=Object.extend({ratioDim:{x:0,y:0},minWidth:0,minHeight:0,displayOnInit:false,onEndCrop:Prototype.emptyFunction,captureKeys:true,onloadCoords:null,maxWidth:0,maxHeight:0},_8||{});
43
+this.img=$(_7);
44
+this.clickCoords={x:0,y:0};
45
+this.dragging=false;
46
+this.resizing=false;
47
+this.isWebKit=/Konqueror|Safari|KHTML/.test(navigator.userAgent);
48
+this.isIE=/MSIE/.test(navigator.userAgent);
49
+this.isOpera8=/Opera\s[1-8]/.test(navigator.userAgent);
50
+this.ratioX=0;
51
+this.ratioY=0;
52
+this.attached=false;
53
+this.fixedWidth=(this.options.maxWidth>0&&(this.options.minWidth>=this.options.maxWidth));
54
+this.fixedHeight=(this.options.maxHeight>0&&(this.options.minHeight>=this.options.maxHeight));
55
+if(typeof this.img=="undefined"){
56
+return;
57
+}
58
+$A(document.getElementsByTagName("script")).each(function(s){
59
+if(s.src.match(/cropper\.js/)){
60
+var _a=s.src.replace(/cropper\.js(.*)?/,"");
61
+var _b=document.createElement("link");
62
+_b.rel="stylesheet";
63
+_b.type="text/css";
64
+_b.href=_a+"cropper.css";
65
+_b.media="screen";
66
+document.getElementsByTagName("head")[0].appendChild(_b);
67
+}
68
+});
69
+if(this.options.ratioDim.x>0&&this.options.ratioDim.y>0){
70
+var _c=this.getGCD(this.options.ratioDim.x,this.options.ratioDim.y);
71
+this.ratioX=this.options.ratioDim.x/_c;
72
+this.ratioY=this.options.ratioDim.y/_c;
73
+}
74
+this.subInitialize();
75
+if(this.img.complete||this.isWebKit){
76
+this.onLoad();
77
+}else{
78
+Event.observe(this.img,"load",this.onLoad.bindAsEventListener(this));
79
+}
80
+},getGCD:function(a,b){
81
+if(b==0){
82
+return a;
83
+}
84
+return this.getGCD(b,a%b);
85
+},onLoad:function(){
86
+var _f="imgCrop_";
87
+var _10=this.img.parentNode;
88
+var _11="";
89
+if(this.isOpera8){
90
+_11=" opera8";
91
+}
92
+this.imgWrap=Builder.node("div",{"class":_f+"wrap"+_11});
93
+this.north=Builder.node("div",{"class":_f+"overlay "+_f+"north"},[Builder.node("span")]);
94
+this.east=Builder.node("div",{"class":_f+"overlay "+_f+"east"},[Builder.node("span")]);
95
+this.south=Builder.node("div",{"class":_f+"overlay "+_f+"south"},[Builder.node("span")]);
96
+this.west=Builder.node("div",{"class":_f+"overlay "+_f+"west"},[Builder.node("span")]);
97
+var _12=[this.north,this.east,this.south,this.west];
98
+this.dragArea=Builder.node("div",{"class":_f+"dragArea"},_12);
99
+this.handleN=Builder.node("div",{"class":_f+"handle "+_f+"handleN"});
100
+this.handleNE=Builder.node("div",{"class":_f+"handle "+_f+"handleNE"});
101
+this.handleE=Builder.node("div",{"class":_f+"handle "+_f+"handleE"});
102
+this.handleSE=Builder.node("div",{"class":_f+"handle "+_f+"handleSE"});
103
+this.handleS=Builder.node("div",{"class":_f+"handle "+_f+"handleS"});
104
+this.handleSW=Builder.node("div",{"class":_f+"handle "+_f+"handleSW"});
105
+this.handleW=Builder.node("div",{"class":_f+"handle "+_f+"handleW"});
106
+this.handleNW=Builder.node("div",{"class":_f+"handle "+_f+"handleNW"});
107
+this.selArea=Builder.node("div",{"class":_f+"selArea"},[Builder.node("div",{"class":_f+"marqueeHoriz "+_f+"marqueeNorth"},[Builder.node("span")]),Builder.node("div",{"class":_f+"marqueeVert "+_f+"marqueeEast"},[Builder.node("span")]),Builder.node("div",{"class":_f+"marqueeHoriz "+_f+"marqueeSouth"},[Builder.node("span")]),Builder.node("div",{"class":_f+"marqueeVert "+_f+"marqueeWest"},[Builder.node("span")]),this.handleN,this.handleNE,this.handleE,this.handleSE,this.handleS,this.handleSW,this.handleW,this.handleNW,Builder.node("div",{"class":_f+"clickArea"})]);
108
+this.imgWrap.appendChild(this.img);
109
+this.imgWrap.appendChild(this.dragArea);
110
+this.dragArea.appendChild(this.selArea);
111
+this.dragArea.appendChild(Builder.node("div",{"class":_f+"clickArea"}));
112
+_10.appendChild(this.imgWrap);
113
+this.startDragBind=this.startDrag.bindAsEventListener(this);
114
+Event.observe(this.dragArea,"mousedown",this.startDragBind);
115
+this.onDragBind=this.onDrag.bindAsEventListener(this);
116
+Event.observe(document,"mousemove",this.onDragBind);
117
+this.endCropBind=this.endCrop.bindAsEventListener(this);
118
+Event.observe(document,"mouseup",this.endCropBind);
119
+this.resizeBind=this.startResize.bindAsEventListener(this);
120
+this.handles=[this.handleN,this.handleNE,this.handleE,this.handleSE,this.handleS,this.handleSW,this.handleW,this.handleNW];
121
+this.registerHandles(true);
122
+if(this.options.captureKeys){
123
+this.keysBind=this.handleKeys.bindAsEventListener(this);
124
+Event.observe(document,"keypress",this.keysBind);
125
+}
126
+new CropDraggable(this.selArea,{drawMethod:this.moveArea.bindAsEventListener(this)});
127
+this.setParams();
128
+},registerHandles:function(_13){
129
+for(var i=0;i<this.handles.length;i++){
130
+var _15=$(this.handles[i]);
131
+if(_13){
132
+var _16=false;
133
+if(this.fixedWidth&&this.fixedHeight){
134
+_16=true;
135
+}else{
136
+if(this.fixedWidth||this.fixedHeight){
137
+var _17=_15.className.match(/([S|N][E|W])$/);
138
+var _18=_15.className.match(/(E|W)$/);
139
+var _19=_15.className.match(/(N|S)$/);
140
+if(_17){
141
+_16=true;
142
+}else{
143
+if(this.fixedWidth&&_18){
144
+_16=true;
145
+}else{
146
+if(this.fixedHeight&&_19){
147
+_16=true;
148
+}
149
+}
150
+}
151
+}
152
+}
153
+if(_16){
154
+_15.hide();
155
+}else{
156
+Event.observe(_15,"mousedown",this.resizeBind);
157
+}
158
+}else{
159
+_15.show();
160
+Event.stopObserving(_15,"mousedown",this.resizeBind);
161
+}
162
+}
163
+},setParams:function(){
164
+this.imgW=this.img.width;
165
+this.imgH=this.img.height;
166
+$(this.north).setStyle({height:0});
167
+$(this.east).setStyle({width:0,height:0});
168
+$(this.south).setStyle({height:0});
169
+$(this.west).setStyle({width:0,height:0});
170
+$(this.imgWrap).setStyle({"width":this.imgW+"px","height":this.imgH+"px"});
171
+$(this.selArea).hide();
172
+var _1a={x1:0,y1:0,x2:0,y2:0};
173
+var _1b=false;
174
+if(this.options.onloadCoords!=null){
175
+_1a=this.cloneCoords(this.options.onloadCoords);
176
+_1b=true;
177
+}else{
178
+if(this.options.ratioDim.x>0&&this.options.ratioDim.y>0){
179
+_1a.x1=Math.ceil((this.imgW-this.options.ratioDim.x)/2);
180
+_1a.y1=Math.ceil((this.imgH-this.options.ratioDim.y)/2);
181
+_1a.x2=_1a.x1+this.options.ratioDim.x;
182
+_1a.y2=_1a.y1+this.options.ratioDim.y;
183
+_1b=true;
184
+}
185
+}
186
+this.setAreaCoords(_1a,false,false,1);
187
+if(this.options.displayOnInit&&_1b){
188
+this.selArea.show();
189
+this.drawArea();
190
+this.endCrop();
191
+}
192
+this.attached=true;
193
+},remove:function(){
194
+if(this.attached){
195
+this.attached=false;
196
+this.imgWrap.parentNode.insertBefore(this.img,this.imgWrap);
197
+this.imgWrap.parentNode.removeChild(this.imgWrap);
198
+Event.stopObserving(this.dragArea,"mousedown",this.startDragBind);
199
+Event.stopObserving(document,"mousemove",this.onDragBind);
200
+Event.stopObserving(document,"mouseup",this.endCropBind);
201
+this.registerHandles(false);
202
+if(this.options.captureKeys){
203
+Event.stopObserving(document,"keypress",this.keysBind);
204
+}
205
+}
206
+},reset:function(){
207
+if(!this.attached){
208
+this.onLoad();
209
+}else{
210
+this.setParams();
211
+}
212
+this.endCrop();
213
+},handleKeys:function(e){
214
+var dir={x:0,y:0};
215
+if(!this.dragging){
216
+switch(e.keyCode){
217
+case (37):
218
+dir.x=-1;
219
+break;
220
+case (38):
221
+dir.y=-1;
222
+break;
223
+case (39):
224
+dir.x=1;
225
+break;
226
+case (40):
227
+dir.y=1;
228
+break;
229
+}
230
+if(dir.x!=0||dir.y!=0){
231
+if(e.shiftKey){
232
+dir.x*=10;
233
+dir.y*=10;
234
+}
235
+this.moveArea([this.areaCoords.x1+dir.x,this.areaCoords.y1+dir.y]);
236
+Event.stop(e);
237
+}
238
+}
239
+},calcW:function(){
240
+return (this.areaCoords.x2-this.areaCoords.x1);
241
+},calcH:function(){
242
+return (this.areaCoords.y2-this.areaCoords.y1);
243
+},moveArea:function(_1e){
244
+this.setAreaCoords({x1:_1e[0],y1:_1e[1],x2:_1e[0]+this.calcW(),y2:_1e[1]+this.calcH()},true,false);
245
+this.drawArea();
246
+},cloneCoords:function(_1f){
247
+return {x1:_1f.x1,y1:_1f.y1,x2:_1f.x2,y2:_1f.y2};
248
+},setAreaCoords:function(_20,_21,_22,_23,_24){
249
+if(_21){
250
+var _25=_20.x2-_20.x1;
251
+var _26=_20.y2-_20.y1;
252
+if(_20.x1<0){
253
+_20.x1=0;
254
+_20.x2=_25;
255
+}
256
+if(_20.y1<0){
257
+_20.y1=0;
258
+_20.y2=_26;
259
+}
260
+if(_20.x2>this.imgW){
261
+_20.x2=this.imgW;
262
+_20.x1=this.imgW-_25;
263
+}
264
+if(_20.y2>this.imgH){
265
+_20.y2=this.imgH;
266
+_20.y1=this.imgH-_26;
267
+}
268
+}else{
269
+if(_20.x1<0){
270
+_20.x1=0;
271
+}
272
+if(_20.y1<0){
273
+_20.y1=0;
274
+}
275
+if(_20.x2>this.imgW){
276
+_20.x2=this.imgW;
277
+}
278
+if(_20.y2>this.imgH){
279
+_20.y2=this.imgH;
280
+}
281
+if(_23!=null){
282
+if(this.ratioX>0){
283
+this.applyRatio(_20,{x:this.ratioX,y:this.ratioY},_23,_24);
284
+}else{
285
+if(_22){
286
+this.applyRatio(_20,{x:1,y:1},_23,_24);
287
+}
288
+}
289
+var _27=[this.options.minWidth,this.options.minHeight];
290
+var _28=[this.options.maxWidth,this.options.maxHeight];
291
+if(_27[0]>0||_27[1]>0||_28[0]>0||_28[1]>0){
292
+var _29={a1:_20.x1,a2:_20.x2};
293
+var _2a={a1:_20.y1,a2:_20.y2};
294
+var _2b={min:0,max:this.imgW};
295
+var _2c={min:0,max:this.imgH};
296
+if((_27[0]!=0||_27[1]!=0)&&_22){
297
+if(_27[0]>0){
298
+_27[1]=_27[0];
299
+}else{
300
+if(_27[1]>0){
301
+_27[0]=_27[1];
302
+}
303
+}
304
+}
305
+if((_28[0]!=0||_28[0]!=0)&&_22){
306
+if(_28[0]>0&&_28[0]<=_28[1]){
307
+_28[1]=_28[0];
308
+}else{
309
+if(_28[1]>0&&_28[1]<=_28[0]){
310
+_28[0]=_28[1];
311
+}
312
+}
313
+}
314
+if(_27[0]>0){
315
+this.applyDimRestriction(_29,_27[0],_23.x,_2b,"min");
316
+}
317
+if(_27[1]>1){
318
+this.applyDimRestriction(_2a,_27[1],_23.y,_2c,"min");
319
+}
320
+if(_28[0]>0){
321
+this.applyDimRestriction(_29,_28[0],_23.x,_2b,"max");
322
+}
323
+if(_28[1]>1){
324
+this.applyDimRestriction(_2a,_28[1],_23.y,_2c,"max");
325
+}
326
+_20={x1:_29.a1,y1:_2a.a1,x2:_29.a2,y2:_2a.a2};
327
+}
328
+}
329
+}
330
+this.areaCoords=_20;
331
+},applyDimRestriction:function(_2d,val,_2f,_30,_31){
332
+var _32;
333
+if(_31=="min"){
334
+_32=((_2d.a2-_2d.a1)<val);
335
+}else{
336
+_32=((_2d.a2-_2d.a1)>val);
337
+}
338
+if(_32){
339
+if(_2f==1){
340
+_2d.a2=_2d.a1+val;
341
+}else{
342
+_2d.a1=_2d.a2-val;
343
+}
344
+if(_2d.a1<_30.min){
345
+_2d.a1=_30.min;
346
+_2d.a2=val;
347
+}else{
348
+if(_2d.a2>_30.max){
349
+_2d.a1=_30.max-val;
350
+_2d.a2=_30.max;
351
+}
352
+}
353
+}
354
+},applyRatio:function(_33,_34,_35,_36){
355
+var _37;
356
+if(_36=="N"||_36=="S"){
357
+_37=this.applyRatioToAxis({a1:_33.y1,b1:_33.x1,a2:_33.y2,b2:_33.x2},{a:_34.y,b:_34.x},{a:_35.y,b:_35.x},{min:0,max:this.imgW});
358
+_33.x1=_37.b1;
359
+_33.y1=_37.a1;
360
+_33.x2=_37.b2;
361
+_33.y2=_37.a2;
362
+}else{
363
+_37=this.applyRatioToAxis({a1:_33.x1,b1:_33.y1,a2:_33.x2,b2:_33.y2},{a:_34.x,b:_34.y},{a:_35.x,b:_35.y},{min:0,max:this.imgH});
364
+_33.x1=_37.a1;
365
+_33.y1=_37.b1;
366
+_33.x2=_37.a2;
367
+_33.y2=_37.b2;
368
+}
369
+},applyRatioToAxis:function(_38,_39,_3a,_3b){
370
+var _3c=Object.extend(_38,{});
371
+var _3d=_3c.a2-_3c.a1;
372
+var _3e=Math.floor(_3d*_39.b/_39.a);
373
+var _3f;
374
+var _40;
375
+var _41=null;
376
+if(_3a.b==1){
377
+_3f=_3c.b1+_3e;
378
+if(_3f>_3b.max){
379
+_3f=_3b.max;
380
+_41=_3f-_3c.b1;
381
+}
382
+_3c.b2=_3f;
383
+}else{
384
+_3f=_3c.b2-_3e;
385
+if(_3f<_3b.min){
386
+_3f=_3b.min;
387
+_41=_3f+_3c.b2;
388
+}
389
+_3c.b1=_3f;
390
+}
391
+if(_41!=null){
392
+_40=Math.floor(_41*_39.a/_39.b);
393
+if(_3a.a==1){
394
+_3c.a2=_3c.a1+_40;
395
+}else{
396
+_3c.a1=_3c.a1=_3c.a2-_40;
397
+}
398
+}
399
+return _3c;
400
+},drawArea:function(){
401
+var _42=this.calcW();
402
+var _43=this.calcH();
403
+var px="px";
404
+var _45=[this.areaCoords.x1+px,this.areaCoords.y1+px,_42+px,_43+px,this.areaCoords.x2+px,this.areaCoords.y2+px,(this.img.width-this.areaCoords.x2)+px,(this.img.height-this.areaCoords.y2)+px];
405
+var _46=this.selArea.style;
406
+_46.left=_45[0];
407
+_46.top=_45[1];
408
+_46.width=_45[2];
409
+_46.height=_45[3];
410
+var _47=Math.ceil((_42-6)/2)+px;
411
+var _48=Math.ceil((_43-6)/2)+px;
412
+this.handleN.style.left=_47;
413
+this.handleE.style.top=_48;
414
+this.handleS.style.left=_47;
415
+this.handleW.style.top=_48;
416
+this.north.style.height=_45[1];
417
+var _49=this.east.style;
418
+_49.top=_45[1];
419
+_49.height=_45[3];
420
+_49.left=_45[4];
421
+_49.width=_45[6];
422
+var _4a=this.south.style;
423
+_4a.top=_45[5];
424
+_4a.height=_45[7];
425
+var _4b=this.west.style;
426
+_4b.top=_45[1];
427
+_4b.height=_45[3];
428
+_4b.width=_45[0];
429
+this.subDrawArea();
430
+this.forceReRender();
431
+},forceReRender:function(){
432
+if(this.isIE||this.isWebKit){
433
+var n=document.createTextNode(" ");
434
+var d,el,fixEL,i;
435
+if(this.isIE){
436
+fixEl=this.selArea;
437
+}else{
438
+if(this.isWebKit){
439
+fixEl=document.getElementsByClassName("imgCrop_marqueeSouth",this.imgWrap)[0];
440
+d=Builder.node("div","");
441
+d.style.visibility="hidden";
442
+var _4e=["SE","S","SW"];
443
+for(i=0;i<_4e.length;i++){
444
+el=document.getElementsByClassName("imgCrop_handle"+_4e[i],this.selArea)[0];
445
+if(el.childNodes.length){
446
+el.removeChild(el.childNodes[0]);
447
+}
448
+el.appendChild(d);
449
+}
450
+}
451
+}
452
+fixEl.appendChild(n);
453
+fixEl.removeChild(n);
454
+}
455
+},startResize:function(e){
456
+this.startCoords=this.cloneCoords(this.areaCoords);
457
+this.resizing=true;
458
+this.resizeHandle=Event.element(e).classNames().toString().replace(/([^N|NE|E|SE|S|SW|W|NW])+/,"");
459
+Event.stop(e);
460
+},startDrag:function(e){
461
+this.selArea.show();
462
+this.clickCoords=this.getCurPos(e);
463
+this.setAreaCoords({x1:this.clickCoords.x,y1:this.clickCoords.y,x2:this.clickCoords.x,y2:this.clickCoords.y},false,false,null);
464
+this.dragging=true;
465
+this.onDrag(e);
466
+Event.stop(e);
467
+},getCurPos:function(e){
468
+var el=this.imgWrap,wrapOffsets=Position.cumulativeOffset(el);
469
+while(el.nodeName!="BODY"){
470
+wrapOffsets[1]-=el.scrollTop||0;
471
+wrapOffsets[0]-=el.scrollLeft||0;
472
+el=el.parentNode;
473
+}
474
+return curPos={x:Event.pointerX(e)-wrapOffsets[0],y:Event.pointerY(e)-wrapOffsets[1]};
475
+},onDrag:function(e){
476
+if(this.dragging||this.resizing){
477
+var _54=null;
478
+var _55=this.getCurPos(e);
479
+var _56=this.cloneCoords(this.areaCoords);
480
+var _57={x:1,y:1};
481
+if(this.dragging){
482
+if(_55.x<this.clickCoords.x){
483
+_57.x=-1;
484
+}
485
+if(_55.y<this.clickCoords.y){
486
+_57.y=-1;
487
+}
488
+this.transformCoords(_55.x,this.clickCoords.x,_56,"x");
489
+this.transformCoords(_55.y,this.clickCoords.y,_56,"y");
490
+}else{
491
+if(this.resizing){
492
+_54=this.resizeHandle;
493
+if(_54.match(/E/)){
494
+this.transformCoords(_55.x,this.startCoords.x1,_56,"x");
495
+if(_55.x<this.startCoords.x1){
496
+_57.x=-1;
497
+}
498
+}else{
499
+if(_54.match(/W/)){
500
+this.transformCoords(_55.x,this.startCoords.x2,_56,"x");
501
+if(_55.x<this.startCoords.x2){
502
+_57.x=-1;
503
+}
504
+}
505
+}
506
+if(_54.match(/N/)){
507
+this.transformCoords(_55.y,this.startCoords.y2,_56,"y");
508
+if(_55.y<this.startCoords.y2){
509
+_57.y=-1;
510
+}
511
+}else{
512
+if(_54.match(/S/)){
513
+this.transformCoords(_55.y,this.startCoords.y1,_56,"y");
514
+if(_55.y<this.startCoords.y1){
515
+_57.y=-1;
516
+}
517
+}
518
+}
519
+}
520
+}
521
+this.setAreaCoords(_56,false,e.shiftKey,_57,_54);
522
+this.drawArea();
523
+Event.stop(e);
524
+}
525
+},transformCoords:function(_58,_59,_5a,_5b){
526
+var _5c=[_58,_59];
527
+if(_58>_59){
528
+_5c.reverse();
529
+}
530
+_5a[_5b+"1"]=_5c[0];
531
+_5a[_5b+"2"]=_5c[1];
532
+},endCrop:function(){
533
+this.dragging=false;
534
+this.resizing=false;
535
+this.options.onEndCrop(this.areaCoords,{width:this.calcW(),height:this.calcH()});
536
+},subInitialize:function(){
537
+},subDrawArea:function(){
538
+}};
539
+Cropper.ImgWithPreview=Class.create();
540
+Object.extend(Object.extend(Cropper.ImgWithPreview.prototype,Cropper.Img.prototype),{subInitialize:function(){
541
+this.hasPreviewImg=false;
542
+if(typeof (this.options.previewWrap)!="undefined"&&this.options.minWidth>0&&this.options.minHeight>0){
543
+this.previewWrap=$(this.options.previewWrap);
544
+this.previewImg=this.img.cloneNode(false);
545
+this.previewImg.id="imgCrop_"+this.previewImg.id;
546
+this.options.displayOnInit=true;
547
+this.hasPreviewImg=true;
548
+this.previewWrap.addClassName("imgCrop_previewWrap");
549
+this.previewWrap.setStyle({width:this.options.minWidth+"px",height:this.options.minHeight+"px"});
550
+this.previewWrap.appendChild(this.previewImg);
551
+}
552
+},subDrawArea:function(){
553
+if(this.hasPreviewImg){
554
+var _5d=this.calcW();
555
+var _5e=this.calcH();
556
+var _5f={x:this.imgW/_5d,y:this.imgH/_5e};
557
+var _60={x:_5d/this.options.minWidth,y:_5e/this.options.minHeight};
558
+var _61={w:Math.ceil(this.options.minWidth*_5f.x)+"px",h:Math.ceil(this.options.minHeight*_5f.y)+"px",x:"-"+Math.ceil(this.areaCoords.x1/_60.x)+"px",y:"-"+Math.ceil(this.areaCoords.y1/_60.y)+"px"};
559
+var _62=this.previewImg.style;
560
+_62.width=_61.w;
561
+_62.height=_61.h;
562
+_62.left=_61.x;
563
+_62.top=_61.y;
564
+}
565
+}});
566
+

+ 1331
- 0
cropper/cropper.uncompressed.js
File diff suppressed because it is too large
View File


+ 101
- 0
cropper/lib/builder.js View File

@@ -0,0 +1,101 @@
1
+// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
2
+//
3
+// See scriptaculous.js for full license.
4
+
5
+var Builder = {
6
+  NODEMAP: {
7
+    AREA: 'map',
8
+    CAPTION: 'table',
9
+    COL: 'table',
10
+    COLGROUP: 'table',
11
+    LEGEND: 'fieldset',
12
+    OPTGROUP: 'select',
13
+    OPTION: 'select',
14
+    PARAM: 'object',
15
+    TBODY: 'table',
16
+    TD: 'table',
17
+    TFOOT: 'table',
18
+    TH: 'table',
19
+    THEAD: 'table',
20
+    TR: 'table'
21
+  },
22
+  // note: For Firefox < 1.5, OPTION and OPTGROUP tags are currently broken,
23
+  //       due to a Firefox bug
24
+  node: function(elementName) {
25
+    elementName = elementName.toUpperCase();
26
+    
27
+    // try innerHTML approach
28
+    var parentTag = this.NODEMAP[elementName] || 'div';
29
+    var parentElement = document.createElement(parentTag);
30
+    try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
31
+      parentElement.innerHTML = "<" + elementName + "></" + elementName + ">";
32
+    } catch(e) {}
33
+    var element = parentElement.firstChild || null;
34
+      
35
+    // see if browser added wrapping tags
36
+    if(element && (element.tagName != elementName))
37
+      element = element.getElementsByTagName(elementName)[0];
38
+    
39
+    // fallback to createElement approach
40
+    if(!element) element = document.createElement(elementName);
41
+    
42
+    // abort if nothing could be created
43
+    if(!element) return;
44
+
45
+    // attributes (or text)
46
+    if(arguments[1])
47
+      if(this._isStringOrNumber(arguments[1]) ||
48
+        (arguments[1] instanceof Array)) {
49
+          this._children(element, arguments[1]);
50
+        } else {
51
+          var attrs = this._attributes(arguments[1]);
52
+          if(attrs.length) {
53
+            try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
54
+              parentElement.innerHTML = "<" +elementName + " " +
55
+                attrs + "></" + elementName + ">";
56
+            } catch(e) {}
57
+            element = parentElement.firstChild || null;
58
+            // workaround firefox 1.0.X bug
59
+            if(!element) {
60
+              element = document.createElement(elementName);
61
+              for(attr in arguments[1]) 
62
+                element[attr == 'class' ? 'className' : attr] = arguments[1][attr];
63
+            }
64
+            if(element.tagName != elementName)
65
+              element = parentElement.getElementsByTagName(elementName)[0];
66
+            }
67
+        } 
68
+
69
+    // text, or array of children
70
+    if(arguments[2])
71
+      this._children(element, arguments[2]);
72
+
73
+     return element;
74
+  },
75
+  _text: function(text) {
76
+     return document.createTextNode(text);
77
+  },
78
+  _attributes: function(attributes) {
79
+    var attrs = [];
80
+    for(attribute in attributes)
81
+      attrs.push((attribute=='className' ? 'class' : attribute) +
82
+          '="' + attributes[attribute].toString().escapeHTML() + '"');
83
+    return attrs.join(" ");
84
+  },
85
+  _children: function(element, children) {
86
+    if(typeof children=='object') { // array can hold nodes and text
87
+      children.flatten().each( function(e) {
88
+        if(typeof e=='object')
89
+          element.appendChild(e)
90
+        else
91
+          if(Builder._isStringOrNumber(e))
92
+            element.appendChild(Builder._text(e));
93
+      });
94
+    } else
95
+      if(Builder._isStringOrNumber(children)) 
96
+         element.appendChild(Builder._text(children));
97
+  },
98
+  _isStringOrNumber: function(param) {
99
+    return(typeof param=='string' || typeof param=='number');
100
+  }
101
+}

+ 815
- 0
cropper/lib/controls.js View File

@@ -0,0 +1,815 @@
1
+// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
2
+//           (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
3
+//           (c) 2005 Jon Tirsen (http://www.tirsen.com)
4
+// Contributors:
5
+//  Richard Livsey
6
+//  Rahul Bhargava
7
+//  Rob Wills
8
+// 
9
+// See scriptaculous.js for full license.
10
+
11
+// Autocompleter.Base handles all the autocompletion functionality 
12
+// that's independent of the data source for autocompletion. This
13
+// includes drawing the autocompletion menu, observing keyboard
14
+// and mouse events, and similar.
15
+//
16
+// Specific autocompleters need to provide, at the very least, 
17
+// a getUpdatedChoices function that will be invoked every time
18
+// the text inside the monitored textbox changes. This method 
19
+// should get the text for which to provide autocompletion by
20
+// invoking this.getToken(), NOT by directly accessing
21
+// this.element.value. This is to allow incremental tokenized
22
+// autocompletion. Specific auto-completion logic (AJAX, etc)
23
+// belongs in getUpdatedChoices.
24
+//
25
+// Tokenized incremental autocompletion is enabled automatically
26
+// when an autocompleter is instantiated with the 'tokens' option
27
+// in the options parameter, e.g.:
28
+// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
29
+// will incrementally autocomplete with a comma as the token.
30
+// Additionally, ',' in the above example can be replaced with
31
+// a token array, e.g. { tokens: [',', '\n'] } which
32
+// enables autocompletion on multiple tokens. This is most 
33
+// useful when one of the tokens is \n (a newline), as it 
34
+// allows smart autocompletion after linebreaks.
35
+
36
+var Autocompleter = {}
37
+Autocompleter.Base = function() {};
38
+Autocompleter.Base.prototype = {
39
+  baseInitialize: function(element, update, options) {
40
+    this.element     = $(element); 
41
+    this.update      = $(update);  
42
+    this.hasFocus    = false; 
43
+    this.changed     = false; 
44
+    this.active      = false; 
45
+    this.index       = 0;     
46
+    this.entryCount  = 0;
47
+
48
+    if (this.setOptions)
49
+      this.setOptions(options);
50
+    else
51
+      this.options = options || {};
52
+
53
+    this.options.paramName    = this.options.paramName || this.element.name;
54
+    this.options.tokens       = this.options.tokens || [];
55
+    this.options.frequency    = this.options.frequency || 0.4;
56
+    this.options.minChars     = this.options.minChars || 1;
57
+    this.options.onShow       = this.options.onShow || 
58
+    function(element, update){ 
59
+      if(!update.style.position || update.style.position=='absolute') {
60
+        update.style.position = 'absolute';
61
+        Position.clone(element, update, {setHeight: false, offsetTop: element.offsetHeight});
62
+      }
63
+      Effect.Appear(update,{duration:0.15});
64
+    };
65
+    this.options.onHide = this.options.onHide || 
66
+    function(element, update){ new Effect.Fade(update,{duration:0.15}) };
67
+
68
+    if (typeof(this.options.tokens) == 'string') 
69
+      this.options.tokens = new Array(this.options.tokens);
70
+
71
+    this.observer = null;
72
+    
73
+    this.element.setAttribute('autocomplete','off');
74
+
75
+    Element.hide(this.update);
76
+
77
+    Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this));
78
+    Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this));
79
+  },
80
+
81
+  show: function() {
82
+    if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
83
+    if(!this.iefix && 
84
+      (navigator.appVersion.indexOf('MSIE')>0) &&
85
+      (navigator.userAgent.indexOf('Opera')<0) &&
86
+      (Element.getStyle(this.update, 'position')=='absolute')) {
87
+      new Insertion.After(this.update, 
88
+       '<iframe id="' + this.update.id + '_iefix" '+
89
+       'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
90
+       'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
91
+      this.iefix = $(this.update.id+'_iefix');
92
+    }
93
+    if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
94
+  },
95
+  
96
+  fixIEOverlapping: function() {
97
+    Position.clone(this.update, this.iefix);
98
+    this.iefix.style.zIndex = 1;
99
+    this.update.style.zIndex = 2;
100
+    Element.show(this.iefix);
101
+  },
102
+
103
+  hide: function() {
104
+    this.stopIndicator();
105
+    if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
106
+    if(this.iefix) Element.hide(this.iefix);
107
+  },
108
+
109
+  startIndicator: function() {
110
+    if(this.options.indicator) Element.show(this.options.indicator);
111
+  },
112
+
113
+  stopIndicator: function() {
114
+    if(this.options.indicator) Element.hide(this.options.indicator);
115
+  },
116
+
117
+  onKeyPress: function(event) {
118
+    if(this.active)
119
+      switch(event.keyCode) {
120
+       case Event.KEY_TAB:
121
+       case Event.KEY_RETURN:
122
+         this.selectEntry();
123
+         Event.stop(event);
124
+       case Event.KEY_ESC:
125
+         this.hide();
126
+         this.active = false;
127
+         Event.stop(event);
128
+         return;
129
+       case Event.KEY_LEFT:
130
+       case Event.KEY_RIGHT:
131
+         return;
132
+       case Event.KEY_UP:
133
+         this.markPrevious();
134
+         this.render();
135
+         if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
136
+         return;
137
+       case Event.KEY_DOWN:
138
+         this.markNext();
139
+         this.render();
140
+         if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
141
+         return;
142
+      }
143
+     else 
144
+       if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || 
145
+         (navigator.appVersion.indexOf('AppleWebKit') > 0 && event.keyCode == 0)) return;
146
+
147
+    this.changed = true;
148
+    this.hasFocus = true;
149
+
150
+    if(this.observer) clearTimeout(this.observer);
151
+      this.observer = 
152
+        setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
153
+  },
154
+
155
+  activate: function() {
156
+    this.changed = false;
157
+    this.hasFocus = true;
158
+    this.getUpdatedChoices();
159
+  },
160
+
161
+  onHover: function(event) {
162
+    var element = Event.findElement(event, 'LI');
163
+    if(this.index != element.autocompleteIndex) 
164
+    {
165
+        this.index = element.autocompleteIndex;
166
+        this.render();
167
+    }
168
+    Event.stop(event);
169
+  },
170
+  
171
+  onClick: function(event) {
172
+    var element = Event.findElement(event, 'LI');
173
+    this.index = element.autocompleteIndex;
174
+    this.selectEntry();
175
+    this.hide();
176
+  },
177
+  
178
+  onBlur: function(event) {
179
+    // needed to make click events working
180
+    setTimeout(this.hide.bind(this), 250);
181
+    this.hasFocus = false;
182
+    this.active = false;     
183
+  }, 
184
+  
185
+  render: function() {
186
+    if(this.entryCount > 0) {
187
+      for (var i = 0; i < this.entryCount; i++)
188
+        this.index==i ? 
189
+          Element.addClassName(this.getEntry(i),"selected") : 
190
+          Element.removeClassName(this.getEntry(i),"selected");
191
+        
192
+      if(this.hasFocus) { 
193
+        this.show();
194
+        this.active = true;
195
+      }
196
+    } else {
197
+      this.active = false;
198
+      this.hide();
199
+    }
200
+  },
201
+  
202
+  markPrevious: function() {
203
+    if(this.index > 0) this.index--
204
+      else this.index = this.entryCount-1;
205
+  },
206
+  
207
+  markNext: function() {
208
+    if(this.index < this.entryCount-1) this.index++
209
+      else this.index = 0;
210
+  },
211
+  
212
+  getEntry: function(index) {
213
+    return this.update.firstChild.childNodes[index];
214
+  },
215
+  
216
+  getCurrentEntry: function() {
217
+    return this.getEntry(this.index);
218
+  },
219
+  
220
+  selectEntry: function() {
221
+    this.active = false;
222
+    this.updateElement(this.getCurrentEntry());
223
+  },
224
+
225
+  updateElement: function(selectedElement) {
226
+    if (this.options.updateElement) {
227
+      this.options.updateElement(selectedElement);
228
+      return;
229
+    }
230
+    var value = '';
231
+    if (this.options.select) {
232
+      var nodes = document.getElementsByClassName(this.options.select, selectedElement) || [];
233
+      if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
234
+    } else
235
+      value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
236
+    
237
+    var lastTokenPos = this.findLastToken();
238
+    if (lastTokenPos != -1) {
239
+      var newValue = this.element.value.substr(0, lastTokenPos + 1);
240
+      var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/);
241
+      if (whitespace)
242
+        newValue += whitespace[0];
243
+      this.element.value = newValue + value;
244
+    } else {
245
+      this.element.value = value;
246
+    }
247
+    this.element.focus();
248
+    
249
+    if (this.options.afterUpdateElement)
250
+      this.options.afterUpdateElement(this.element, selectedElement);
251
+  },
252
+
253
+  updateChoices: function(choices) {
254
+    if(!this.changed && this.hasFocus) {
255
+      this.update.innerHTML = choices;
256
+      Element.cleanWhitespace(this.update);
257
+      Element.cleanWhitespace(this.update.firstChild);
258
+
259
+      if(this.update.firstChild && this.update.firstChild.childNodes) {
260
+        this.entryCount = 
261
+          this.update.firstChild.childNodes.length;
262
+        for (var i = 0; i < this.entryCount; i++) {
263
+          var entry = this.getEntry(i);
264
+          entry.autocompleteIndex = i;
265
+          this.addObservers(entry);
266
+        }
267
+      } else { 
268
+        this.entryCount = 0;
269
+      }
270
+
271
+      this.stopIndicator();
272
+
273
+      this.index = 0;
274
+      this.render();
275
+    }
276
+  },
277
+
278
+  addObservers: function(element) {
279
+    Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
280
+    Event.observe(element, "click", this.onClick.bindAsEventListener(this));
281
+  },
282
+
283
+  onObserverEvent: function() {
284
+    this.changed = false;   
285
+    if(this.getToken().length>=this.options.minChars) {
286
+      this.startIndicator();
287
+      this.getUpdatedChoices();
288
+    } else {
289
+      this.active = false;
290
+      this.hide();
291
+    }
292
+  },
293
+
294
+  getToken: function() {
295
+    var tokenPos = this.findLastToken();
296
+    if (tokenPos != -1)
297
+      var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,'');
298
+    else
299
+      var ret = this.element.value;
300
+
301
+    return /\n/.test(ret) ? '' : ret;
302
+  },
303
+
304
+  findLastToken: function() {
305
+    var lastTokenPos = -1;
306
+
307
+    for (var i=0; i<this.options.tokens.length; i++) {
308
+      var thisTokenPos = this.element.value.lastIndexOf(this.options.tokens[i]);
309
+      if (thisTokenPos > lastTokenPos)
310
+        lastTokenPos = thisTokenPos;
311
+    }
312
+    return lastTokenPos;
313
+  }
314
+}
315
+
316
+Ajax.Autocompleter = Class.create();
317
+Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), {
318
+  initialize: function(element, update, url, options) {
319
+    this.baseInitialize(element, update, options);
320
+    this.options.asynchronous  = true;
321
+    this.options.onComplete    = this.onComplete.bind(this);
322
+    this.options.defaultParams = this.options.parameters || null;
323
+    this.url                   = url;
324
+  },
325
+
326
+  getUpdatedChoices: function() {
327
+    entry = encodeURIComponent(this.options.paramName) + '=' + 
328
+      encodeURIComponent(this.getToken());
329
+
330
+    this.options.parameters = this.options.callback ?
331
+      this.options.callback(this.element, entry) : entry;
332
+
333
+    if(this.options.defaultParams) 
334
+      this.options.parameters += '&' + this.options.defaultParams;
335
+
336
+    new Ajax.Request(this.url, this.options);
337
+  },
338
+
339
+  onComplete: function(request) {
340
+    this.updateChoices(request.responseText);
341
+  }
342
+
343
+});
344
+
345
+// The local array autocompleter. Used when you'd prefer to
346
+// inject an array of autocompletion options into the page, rather
347
+// than sending out Ajax queries, which can be quite slow sometimes.
348
+//
349
+// The constructor takes four parameters. The first two are, as usual,
350
+// the id of the monitored textbox, and id of the autocompletion menu.
351
+// The third is the array you want to autocomplete from, and the fourth
352
+// is the options block.
353
+//
354
+// Extra local autocompletion options:
355
+// - choices - How many autocompletion choices to offer
356
+//
357
+// - partialSearch - If false, the autocompleter will match entered
358
+//                    text only at the beginning of strings in the 
359
+//                    autocomplete array. Defaults to true, which will
360
+//                    match text at the beginning of any *word* in the
361
+//                    strings in the autocomplete array. If you want to
362
+//                    search anywhere in the string, additionally set
363
+//                    the option fullSearch to true (default: off).
364
+//
365
+// - fullSsearch - Search anywhere in autocomplete array strings.
366
+//
367
+// - partialChars - How many characters to enter before triggering
368
+//                   a partial match (unlike minChars, which defines
369
+//                   how many characters are required to do any match
370
+//                   at all). Defaults to 2.
371
+//
372
+// - ignoreCase - Whether to ignore case when autocompleting.
373
+//                 Defaults to true.
374
+//
375
+// It's possible to pass in a custom function as the 'selector' 
376
+// option, if you prefer to write your own autocompletion logic.
377
+// In that case, the other options above will not apply unless
378
+// you support them.
379
+
380
+Autocompleter.Local = Class.create();
381
+Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
382
+  initialize: function(element, update, array, options) {
383
+    this.baseInitialize(element, update, options);
384
+    this.options.array = array;
385
+  },
386
+
387
+  getUpdatedChoices: function() {
388
+    this.updateChoices(this.options.selector(this));
389
+  },
390
+
391
+  setOptions: function(options) {
392
+    this.options = Object.extend({
393
+      choices: 10,
394
+      partialSearch: true,
395
+      partialChars: 2,
396
+      ignoreCase: true,
397
+      fullSearch: false,
398
+      selector: function(instance) {
399
+        var ret       = []; // Beginning matches
400
+        var partial   = []; // Inside matches
401
+        var entry     = instance.getToken();
402
+        var count     = 0;
403
+
404
+        for (var i = 0; i < instance.options.array.length &&  
405
+          ret.length < instance.options.choices ; i++) { 
406
+
407
+          var elem = instance.options.array[i];
408
+          var foundPos = instance.options.ignoreCase ? 
409
+            elem.toLowerCase().indexOf(entry.toLowerCase()) : 
410
+            elem.indexOf(entry);
411
+
412
+          while (foundPos != -1) {
413
+            if (foundPos == 0 && elem.length != entry.length) { 
414
+              ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" + 
415
+                elem.substr(entry.length) + "</li>");
416
+              break;
417
+            } else if (entry.length >= instance.options.partialChars && 
418
+              instance.options.partialSearch && foundPos != -1) {
419
+              if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
420
+                partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
421
+                  elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
422
+                  foundPos + entry.length) + "</li>");
423
+                break;
424
+              }
425
+            }
426
+
427
+            foundPos = instance.options.ignoreCase ? 
428
+              elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : 
429
+              elem.indexOf(entry, foundPos + 1);
430
+
431
+          }
432
+        }
433
+        if (partial.length)
434
+          ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
435
+        return "<ul>" + ret.join('') + "</ul>";
436
+      }
437
+    }, options || {});
438
+  }
439
+});
440
+
441
+// AJAX in-place editor
442
+//
443
+// see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor
444
+
445
+// Use this if you notice weird scrolling problems on some browsers,
446
+// the DOM might be a bit confused when this gets called so do this
447
+// waits 1 ms (with setTimeout) until it does the activation
448
+Field.scrollFreeActivate = function(field) {
449
+  setTimeout(function() {
450
+    Field.activate(field);
451
+  }, 1);
452
+}
453
+
454
+Ajax.InPlaceEditor = Class.create();
455
+Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99";
456
+Ajax.InPlaceEditor.prototype = {
457
+  initialize: function(element, url, options) {
458
+    this.url = url;
459
+    this.element = $(element);
460
+
461
+    this.options = Object.extend({
462
+      okButton: true,
463
+      okText: "ok",
464
+      cancelLink: true,
465
+      cancelText: "cancel",
466
+      savingText: "Saving...",
467
+      clickToEditText: "Click to edit",
468
+      okText: "ok",
469
+      rows: 1,
470
+      onComplete: function(transport, element) {
471
+        new Effect.Highlight(element, {startcolor: this.options.highlightcolor});
472
+      },
473
+      onFailure: function(transport) {
474
+        alert("Error communicating with the server: " + transport.responseText.stripTags());
475
+      },
476
+      callback: function(form) {
477
+        return Form.serialize(form);
478
+      },
479
+      handleLineBreaks: true,
480
+      loadingText: 'Loading...',
481
+      savingClassName: 'inplaceeditor-saving',
482
+      loadingClassName: 'inplaceeditor-loading',
483
+      formClassName: 'inplaceeditor-form',
484
+      highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor,
485
+      highlightendcolor: "#FFFFFF",
486
+      externalControl: null,
487
+      submitOnBlur: false,
488
+      ajaxOptions: {},
489
+      evalScripts: false
490
+    }, options || {});
491
+
492
+    if(!this.options.formId && this.element.id) {
493
+      this.options.formId = this.element.id + "-inplaceeditor";
494
+      if ($(this.options.formId)) {
495
+        // there's already a form with that name, don't specify an id
496
+        this.options.formId = null;
497
+      }
498
+    }
499
+    
500
+    if (this.options.externalControl) {
501
+      this.options.externalControl = $(this.options.externalControl);
502
+    }
503
+    
504
+    this.originalBackground = Element.getStyle(this.element, 'background-color');
505
+    if (!this.originalBackground) {
506
+      this.originalBackground = "transparent";
507
+    }
508
+    
509
+    this.element.title = this.options.clickToEditText;
510
+    
511
+    this.onclickListener = this.enterEditMode.bindAsEventListener(this);
512
+    this.mouseoverListener = this.enterHover.bindAsEventListener(this);
513
+    this.mouseoutListener = this.leaveHover.bindAsEventListener(this);
514
+    Event.observe(this.element, 'click', this.onclickListener);
515
+    Event.observe(this.element, 'mouseover', this.mouseoverListener);
516
+    Event.observe(this.element, 'mouseout', this.mouseoutListener);
517
+    if (this.options.externalControl) {
518
+      Event.observe(this.options.externalControl, 'click', this.onclickListener);
519
+      Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener);
520
+      Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener);
521
+    }
522
+  },
523
+  enterEditMode: function(evt) {
524
+    if (this.saving) return;
525
+    if (this.editing) return;
526
+    this.editing = true;
527
+    this.onEnterEditMode();
528
+    if (this.options.externalControl) {
529
+      Element.hide(this.options.externalControl);
530
+    }
531
+    Element.hide(this.element);
532
+    this.createForm();
533
+    this.element.parentNode.insertBefore(this.form, this.element);
534
+    Field.scrollFreeActivate(this.editField);
535
+    // stop the event to avoid a page refresh in Safari
536
+    if (evt) {
537
+      Event.stop(evt);
538
+    }
539
+    return false;
540
+  },
541
+  createForm: function() {
542
+    this.form = document.createElement("form");
543
+    this.form.id = this.options.formId;
544
+    Element.addClassName(this.form, this.options.formClassName)
545
+    this.form.onsubmit = this.onSubmit.bind(this);
546
+
547
+    this.createEditField();
548
+
549
+    if (this.options.textarea) {
550
+      var br = document.createElement("br");
551
+      this.form.appendChild(br);
552
+    }
553
+
554
+    if (this.options.okButton) {
555
+      okButton = document.createElement("input");
556
+      okButton.type = "submit";
557
+      okButton.value = this.options.okText;
558
+      okButton.className = 'editor_ok_button';
559
+      this.form.appendChild(okButton);
560
+    }
561
+
562
+    if (this.options.cancelLink) {
563
+      cancelLink = document.createElement("a");
564
+      cancelLink.href = "#";
565
+      cancelLink.appendChild(document.createTextNode(this.options.cancelText));
566
+      cancelLink.onclick = this.onclickCancel.bind(this);
567
+      cancelLink.className = 'editor_cancel';      
568
+      this.form.appendChild(cancelLink);
569
+    }
570
+  },
571
+  hasHTMLLineBreaks: function(string) {
572
+    if (!this.options.handleLineBreaks) return false;
573
+    return string.match(/<br/i) || string.match(/<p>/i);
574
+  },
575
+  convertHTMLLineBreaks: function(string) {
576
+    return string.replace(/<br>/gi, "\n").replace(/<br\/>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<p>/gi, "");
577
+  },
578
+  createEditField: function() {
579
+    var text;
580
+    if(this.options.loadTextURL) {
581
+      text = this.options.loadingText;
582
+    } else {
583
+      text = this.getText();
584
+    }
585
+
586
+    var obj = this;
587
+    
588
+    if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) {
589
+      this.options.textarea = false;
590
+      var textField = document.createElement("input");
591
+      textField.obj = this;
592
+      textField.type = "text";
593
+      textField.name = "value";
594
+      textField.value = text;
595
+      textField.style.backgroundColor = this.options.highlightcolor;
596
+      textField.className = 'editor_field';
597
+      var size = this.options.size || this.options.cols || 0;
598
+      if (size != 0) textField.size = size;
599
+      if (this.options.submitOnBlur)
600
+        textField.onblur = this.onSubmit.bind(this);
601
+      this.editField = textField;
602
+    } else {
603
+      this.options.textarea = true;
604
+      var textArea = document.createElement("textarea");
605
+      textArea.obj = this;
606
+      textArea.name = "value";
607
+      textArea.value = this.convertHTMLLineBreaks(text);
608
+      textArea.rows = this.options.rows;
609
+      textArea.cols = this.options.cols || 40;
610
+      textArea.className = 'editor_field';      
611
+      if (this.options.submitOnBlur)
612
+        textArea.onblur = this.onSubmit.bind(this);
613
+      this.editField = textArea;
614
+    }
615
+    
616
+    if(this.options.loadTextURL) {
617
+      this.loadExternalText();
618
+    }
619
+    this.form.appendChild(this.editField);
620
+  },
621
+  getText: function() {
622
+    return this.element.innerHTML;
623
+  },
624
+  loadExternalText: function() {
625
+    Element.addClassName(this.form, this.options.loadingClassName);
626
+    this.editField.disabled = true;
627
+    new Ajax.Request(
628
+      this.options.loadTextURL,
629
+      Object.extend({
630
+        asynchronous: true,
631
+        onComplete: this.onLoadedExternalText.bind(this)
632
+      }, this.options.ajaxOptions)
633
+    );
634
+  },
635
+  onLoadedExternalText: function(transport) {
636
+    Element.removeClassName(this.form, this.options.loadingClassName);
637
+    this.editField.disabled = false;
638
+    this.editField.value = transport.responseText.stripTags();
639
+  },
640
+  onclickCancel: function() {
641
+    this.onComplete();
642
+    this.leaveEditMode();
643
+    return false;
644
+  },
645
+  onFailure: function(transport) {
646
+    this.options.onFailure(transport);
647
+    if (this.oldInnerHTML) {
648
+      this.element.innerHTML = this.oldInnerHTML;
649
+      this.oldInnerHTML = null;
650
+    }
651
+    return false;
652
+  },
653
+  onSubmit: function() {
654
+    // onLoading resets these so we need to save them away for the Ajax call
655
+    var form = this.form;
656
+    var value = this.editField.value;
657
+    
658
+    // do this first, sometimes the ajax call returns before we get a chance to switch on Saving...
659
+    // which means this will actually switch on Saving... *after* we've left edit mode causing Saving...
660
+    // to be displayed indefinitely
661
+    this.onLoading();
662
+    
663
+    if (this.options.evalScripts) {
664
+      new Ajax.Request(
665
+        this.url, Object.extend({
666
+          parameters: this.options.callback(form, value),
667
+          onComplete: this.onComplete.bind(this),
668
+          onFailure: this.onFailure.bind(this),
669
+          asynchronous:true, 
670
+          evalScripts:true
671
+        }, this.options.ajaxOptions));
672
+    } else  {
673
+      new Ajax.Updater(
674
+        { success: this.element,
675
+          // don't update on failure (this could be an option)
676
+          failure: null }, 
677
+        this.url, Object.extend({
678
+          parameters: this.options.callback(form, value),
679
+          onComplete: this.onComplete.bind(this),
680
+          onFailure: this.onFailure.bind(this)
681
+        }, this.options.ajaxOptions));
682
+    }
683
+    // stop the event to avoid a page refresh in Safari
684
+    if (arguments.length > 1) {
685
+      Event.stop(arguments[0]);
686
+    }
687
+    return false;
688
+  },
689
+  onLoading: function() {
690
+    this.saving = true;
691
+    this.removeForm();
692
+    this.leaveHover();
693
+    this.showSaving();
694
+  },
695
+  showSaving: function() {
696
+    this.oldInnerHTML = this.element.innerHTML;
697
+    this.element.innerHTML = this.options.savingText;
698
+    Element.addClassName(this.element, this.options.savingClassName);
699
+    this.element.style.backgroundColor = this.originalBackground;
700
+    Element.show(this.element);
701
+  },
702
+  removeForm: function() {
703
+    if(this.form) {
704
+      if (this.form.parentNode) Element.remove(this.form);
705
+      this.form = null;
706
+    }
707
+  },
708
+  enterHover: function() {
709
+    if (this.saving) return;
710
+    this.element.style.backgroundColor = this.options.highlightcolor;
711
+    if (this.effect) {
712
+      this.effect.cancel();
713
+    }
714
+    Element.addClassName(this.element, this.options.hoverClassName)
715
+  },
716
+  leaveHover: function() {
717
+    if (this.options.backgroundColor) {
718
+      this.element.style.backgroundColor = this.oldBackground;
719
+    }
720
+    Element.removeClassName(this.element, this.options.hoverClassName)
721
+    if (this.saving) return;
722
+    this.effect = new Effect.Highlight(this.element, {
723
+      startcolor: this.options.highlightcolor,
724
+      endcolor: this.options.highlightendcolor,
725
+      restorecolor: this.originalBackground
726
+    });
727
+  },
728
+  leaveEditMode: function() {
729
+    Element.removeClassName(this.element, this.options.savingClassName);
730
+    this.removeForm();
731
+    this.leaveHover();
732
+    this.element.style.backgroundColor = this.originalBackground;
733
+    Element.show(this.element);
734
+    if (this.options.externalControl) {
735
+      Element.show(this.options.externalControl);
736
+    }
737
+    this.editing = false;
738
+    this.saving = false;
739
+    this.oldInnerHTML = null;
740
+    this.onLeaveEditMode();
741
+  },
742
+  onComplete: function(transport) {
743
+    this.leaveEditMode();
744
+    this.options.onComplete.bind(this)(transport, this.element);
745
+  },
746
+  onEnterEditMode: function() {},
747
+  onLeaveEditMode: function() {},
748
+  dispose: function() {
749
+    if (this.oldInnerHTML) {
750
+      this.element.innerHTML = this.oldInnerHTML;
751
+    }
752
+    this.leaveEditMode();
753
+    Event.stopObserving(this.element, 'click', this.onclickListener);
754
+    Event.stopObserving(this.element, 'mouseover', this.mouseoverListener);
755
+    Event.stopObserving(this.element, 'mouseout', this.mouseoutListener);
756
+    if (this.options.externalControl) {
757
+      Event.stopObserving(this.options.externalControl, 'click', this.onclickListener);
758
+      Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener);
759
+      Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener);
760
+    }
761
+  }
762
+};
763
+
764
+Ajax.InPlaceCollectionEditor = Class.create();
765
+Object.extend(Ajax.InPlaceCollectionEditor.prototype, Ajax.InPlaceEditor.prototype);
766
+Object.extend(Ajax.InPlaceCollectionEditor.prototype, {
767
+  createEditField: function() {
768
+    if (!this.cached_selectTag) {
769
+      var selectTag = document.createElement("select");
770
+      var collection = this.options.collection || [];
771
+      var optionTag;
772
+      collection.each(function(e,i) {
773
+        optionTag = document.createElement("option");
774
+        optionTag.value = (e instanceof Array) ? e[0] : e;
775
+        if(this.options.value==optionTag.value) optionTag.selected = true;
776
+        optionTag.appendChild(document.createTextNode((e instanceof Array) ? e[1] : e));
777
+        selectTag.appendChild(optionTag);
778
+      }.bind(this));
779
+      this.cached_selectTag = selectTag;
780
+    }
781
+
782
+    this.editField = this.cached_selectTag;
783
+    if(this.options.loadTextURL) this.loadExternalText();
784
+    this.form.appendChild(this.editField);
785
+    this.options.callback = function(form, value) {
786
+      return "value=" + encodeURIComponent(value);
787
+    }
788
+  }
789
+});
790
+
791
+// Delayed observer, like Form.Element.Observer, 
792
+// but waits for delay after last key input
793
+// Ideal for live-search fields
794
+
795
+Form.Element.DelayedObserver = Class.create();
796
+Form.Element.DelayedObserver.prototype = {
797
+  initialize: function(element, delay, callback) {
798
+    this.delay     = delay || 0.5;
799
+    this.element   = $(element);
800
+    this.callback  = callback;
801
+    this.timer     = null;
802
+    this.lastValue = $F(this.element); 
803
+    Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
804
+  },
805
+  delayedListener: function(event) {
806
+    if(this.lastValue == $F(this.element)) return;
807
+    if(this.timer) clearTimeout(this.timer);
808
+    this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
809
+    this.lastValue = $F(this.element);
810
+  },
811
+  onTimerEvent: function() {
812
+    this.timer = null;
813
+    this.callback(this.element, $F(this.element));
814
+  }
815
+};

+ 915
- 0
cropper/lib/dragdrop.js View File

@@ -0,0 +1,915 @@
1
+// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
2
+//           (c) 2005 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)
3
+// 
4
+// See scriptaculous.js for full license.
5
+
6
+/*--------------------------------------------------------------------------*/
7
+
8
+var Droppables = {
9
+  drops: [],
10
+
11
+  remove: function(element) {
12
+    this.drops = this.drops.reject(function(d) { return d.element==$(element) });
13
+  },
14
+
15
+  add: function(element) {
16
+    element = $(element);
17
+    var options = Object.extend({
18
+      greedy:     true,
19
+      hoverclass: null,
20
+      tree:       false
21
+    }, arguments[1] || {});
22
+
23
+    // cache containers
24
+    if(options.containment) {
25
+      options._containers = [];
26
+      var containment = options.containment;
27
+      if((typeof containment == 'object') && 
28
+        (containment.constructor == Array)) {
29
+        containment.each( function(c) { options._containers.push($(c)) });
30
+      } else {
31
+        options._containers.push($(containment));
32
+      }
33
+    }
34
+    
35
+    if(options.accept) options.accept = [options.accept].flatten();
36
+
37
+    Element.makePositioned(element); // fix IE
38
+    options.element = element;
39
+
40
+    this.drops.push(options);
41
+  },
42
+  
43
+  findDeepestChild: function(drops) {
44
+    deepest = drops[0];
45
+      
46
+    for (i = 1; i < drops.length; ++i)
47
+      if (Element.isParent(drops[i].element, deepest.element))
48
+        deepest = drops[i];
49
+    
50
+    return deepest;
51
+  },
52
+
53
+  isContained: function(element, drop) {
54
+    var containmentNode;
55
+    if(drop.tree) {
56
+      containmentNode = element.treeNode; 
57
+    } else {
58
+      containmentNode = element.parentNode;
59
+    }
60
+    return drop._containers.detect(function(c) { return containmentNode == c });
61
+  },
62
+  
63
+  isAffected: function(point, element, drop) {
64
+    return (
65
+      (drop.element!=element) &&
66
+      ((!drop._containers) ||
67
+        this.isContained(element, drop)) &&
68
+      ((!drop.accept) ||
69
+        (Element.classNames(element).detect( 
70
+          function(v) { return drop.accept.include(v) } ) )) &&
71
+      Position.within(drop.element, point[0], point[1]) );
72
+  },
73
+
74
+  deactivate: function(drop) {
75
+    if(drop.hoverclass)
76
+      Element.removeClassName(drop.element, drop.hoverclass);
77
+    this.last_active = null;
78
+  },
79
+
80
+  activate: function(drop) {
81
+    if(drop.hoverclass)
82
+      Element.addClassName(drop.element, drop.hoverclass);
83
+    this.last_active = drop;
84
+  },
85
+
86
+  show: function(point, element) {
87
+    if(!this.drops.length) return;
88
+    var affected = [];
89
+    
90
+    if(this.last_active) this.deactivate(this.last_active);
91
+    this.drops.each( function(drop) {
92
+      if(Droppables.isAffected(point, element, drop))
93
+        affected.push(drop);
94
+    });
95
+        
96
+    if(affected.length>0) {
97
+      drop = Droppables.findDeepestChild(affected);
98
+      Position.within(drop.element, point[0], point[1]);
99
+      if(drop.onHover)
100
+        drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
101
+      
102
+      Droppables.activate(drop);
103
+    }
104
+  },
105
+
106
+  fire: function(event, element) {
107
+    if(!this.last_active) return;
108
+    Position.prepare();
109
+
110
+    if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active))
111
+      if (this.last_active.onDrop) 
112
+        this.last_active.onDrop(element, this.last_active.element, event);
113
+  },
114
+
115
+  reset: function() {
116
+    if(this.last_active)
117
+      this.deactivate(this.last_active);
118
+  }
119
+}
120
+
121
+var Draggables = {
122
+  drags: [],
123
+  observers: [],
124
+  
125
+  register: function(draggable) {
126
+    if(this.drags.length == 0) {
127
+      this.eventMouseUp   = this.endDrag.bindAsEventListener(this);
128
+      this.eventMouseMove = this.updateDrag.bindAsEventListener(this);
129
+      this.eventKeypress  = this.keyPress.bindAsEventListener(this);
130
+      
131
+      Event.observe(document, "mouseup", this.eventMouseUp);
132
+      Event.observe(document, "mousemove", this.eventMouseMove);
133
+      Event.observe(document, "keypress", this.eventKeypress);
134
+    }
135
+    this.drags.push(draggable);
136
+  },
137
+  
138
+  unregister: function(draggable) {
139
+    this.drags = this.drags.reject(function(d) { return d==draggable });
140
+    if(this.drags.length == 0) {
141
+      Event.stopObserving(document, "mouseup", this.eventMouseUp);
142
+      Event.stopObserving(document, "mousemove", this.eventMouseMove);
143
+      Event.stopObserving(document, "keypress", this.eventKeypress);
144
+    }
145
+  },
146
+  
147
+  activate: function(draggable) {
148
+    window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
149
+    this.activeDraggable = draggable;
150
+  },
151
+  
152
+  deactivate: function() {
153
+    this.activeDraggable = null;
154
+  },
155
+  
156
+  updateDrag: function(event) {
157
+    if(!this.activeDraggable) return;
158
+    var pointer = [Event.pointerX(event), Event.pointerY(event)];
159
+    // Mozilla-based browsers fire successive mousemove events with
160
+    // the same coordinates, prevent needless redrawing (moz bug?)
161
+    if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return;
162
+    this._lastPointer = pointer;
163
+    this.activeDraggable.updateDrag(event, pointer);
164
+  },
165
+  
166
+  endDrag: function(event) {
167
+    if(!this.activeDraggable) return;
168
+    this._lastPointer = null;
169
+    this.activeDraggable.endDrag(event);
170
+    this.activeDraggable = null;
171
+  },
172
+  
173
+  keyPress: function(event) {
174
+    if(this.activeDraggable)
175
+      this.activeDraggable.keyPress(event);
176
+  },
177
+  
178
+  addObserver: function(observer) {
179
+    this.observers.push(observer);
180
+    this._cacheObserverCallbacks();
181
+  },
182
+  
183
+  removeObserver: function(element) {  // element instead of observer fixes mem leaks
184
+    this.observers = this.observers.reject( function(o) { return o.element==element });
185
+    this._cacheObserverCallbacks();
186
+  },
187
+  
188
+  notify: function(eventName, draggable, event) {  // 'onStart', 'onEnd', 'onDrag'
189
+    if(this[eventName+'Count'] > 0)
190
+      this.observers.each( function(o) {
191
+        if(o[eventName]) o[eventName](eventName, draggable, event);
192
+      });
193
+  },
194
+  
195
+  _cacheObserverCallbacks: function() {
196
+    ['onStart','onEnd','onDrag'].each( function(eventName) {
197
+      Draggables[eventName+'Count'] = Draggables.observers.select(
198
+        function(o) { return o[eventName]; }
199
+      ).length;
200
+    });
201
+  }
202
+}
203
+
204
+/*--------------------------------------------------------------------------*/
205
+
206
+var Draggable = Class.create();
207
+Draggable.prototype = {
208
+  initialize: function(element) {
209
+    var options = Object.extend({
210
+      handle: false,
211
+      starteffect: function(element) {
212
+        element._opacity = Element.getOpacity(element); 
213
+        new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7}); 
214
+      },
215
+      reverteffect: function(element, top_offset, left_offset) {
216
+        var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
217
+        element._revert = new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur});
218
+      },
219
+      endeffect: function(element) {
220
+        var toOpacity = typeof element._opacity == 'number' ? element._opacity : 1.0
221
+        new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity}); 
222
+      },
223
+      zindex: 1000,
224
+      revert: false,
225
+      scroll: false,
226
+      scrollSensitivity: 20,
227
+      scrollSpeed: 15,
228
+      snap: false   // false, or xy or [x,y] or function(x,y){ return [x,y] }
229
+    }, arguments[1] || {});
230
+
231
+    this.element = $(element);
232
+    
233
+    if(options.handle && (typeof options.handle == 'string')) {
234
+      var h = Element.childrenWithClassName(this.element, options.handle, true);
235
+      if(h.length>0) this.handle = h[0];
236
+    }
237
+    if(!this.handle) this.handle = $(options.handle);
238
+    if(!this.handle) this.handle = this.element;
239
+    
240
+    if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML)
241
+      options.scroll = $(options.scroll);
242
+