From 19921775fc4ddaa157f3314cd3f2298548f1992b Mon Sep 17 00:00:00 2001 From: Garux Date: Sun, 3 May 2020 11:58:25 +0300 Subject: [PATCH] docs: add NetRadiant specific docs --- docs/Additional_map_compiler_features.htm | 310 ++ docs/Additional_map_editor_features.htm | 236 + ...mplete_list_of_command_line_parameters.htm | 385 ++ docs/Complete_list_of_entity_keys.htm | 262 + docs/Complete_list_of_shader_keywords.htm | 254 + docs/application.css | 881 ++++ docs/application.js | 209 + docs/attachment.png | Bin 0 -> 995 bytes docs/balloons.png | Bin 0 -> 715 bytes docs/clipline-small.png | Bin 0 -> 1856 bytes docs/controls.js | 963 ++++ docs/document-horizontal-text.png | Bin 0 -> 529 bytes docs/document-text-image.png | Bin 0 -> 647 bytes docs/document-zipper.png | Bin 0 -> 646 bytes docs/documents-text.png | Bin 0 -> 675 bytes docs/dotproduct-small.jpg | Bin 0 -> 26565 bytes docs/dragdrop.js | 973 ++++ docs/effects.js | 1128 +++++ docs/exposure.jpg | Bin 0 -> 28472 bytes docs/external.png | Bin 0 -> 194 bytes docs/floodlight.jpg | Bin 0 -> 7171 bytes docs/header_gradient.png | Bin 0 -> 164 bytes docs/help-top.png | Bin 0 -> 786 bytes docs/history.png | Bin 0 -> 322 bytes docs/house.png | Bin 0 -> 806 bytes docs/jstoolbar.css | 95 + docs/lightning.png | Bin 0 -> 669 bytes docs/newspaper.png | Bin 0 -> 581 bytes docs/projects.png | Bin 0 -> 811 bytes docs/prototype.js | 4320 +++++++++++++++++ docs/safe.png | Bin 0 -> 623 bytes docs/scm.css | 183 + docs/stylesheet.css | 4 + docs/ticket.png | Bin 0 -> 730 bytes 34 files changed, 10203 insertions(+) create mode 100644 docs/Additional_map_compiler_features.htm create mode 100644 docs/Additional_map_editor_features.htm create mode 100644 docs/Complete_list_of_command_line_parameters.htm create mode 100644 docs/Complete_list_of_entity_keys.htm create mode 100644 docs/Complete_list_of_shader_keywords.htm create mode 100644 docs/application.css create mode 100644 docs/application.js create mode 100644 docs/attachment.png create mode 100644 docs/balloons.png create mode 100644 docs/clipline-small.png create mode 100644 docs/controls.js create mode 100644 docs/document-horizontal-text.png create mode 100644 docs/document-text-image.png create mode 100644 docs/document-zipper.png create mode 100644 docs/documents-text.png create mode 100644 docs/dotproduct-small.jpg create mode 100644 docs/dragdrop.js create mode 100644 docs/effects.js create mode 100644 docs/exposure.jpg create mode 100644 docs/external.png create mode 100644 docs/floodlight.jpg create mode 100644 docs/header_gradient.png create mode 100644 docs/help-top.png create mode 100644 docs/history.png create mode 100644 docs/house.png create mode 100644 docs/jstoolbar.css create mode 100644 docs/lightning.png create mode 100644 docs/newspaper.png create mode 100644 docs/projects.png create mode 100644 docs/prototype.js create mode 100644 docs/safe.png create mode 100644 docs/scm.css create mode 100644 docs/stylesheet.css create mode 100644 docs/ticket.png diff --git a/docs/Additional_map_compiler_features.htm b/docs/Additional_map_compiler_features.htm new file mode 100644 index 00000000..ce3415a3 --- /dev/null +++ b/docs/Additional_map_compiler_features.htm @@ -0,0 +1,310 @@ + + + + +NetRadiant - Additional map compiler features - Alientrap Development + + + + + + + + + + + + + + + + + +
+ + + + +
+ + +
+ +
+ + + + + + + + + +История +
+ + + + + +
+

Additional map compiler features

+ + +

The following features for use by mappers have been added in NetRadiant and thus are not documented elsewhere:

+ +

Floodlighting

+ + +

+ + +

A very quick and dirty way to light a map is adding the following key to worldspawn:

+ + +
+"_floodlight" "240 240 255 1024 128" 
+

The parameters work as follow: the first three numbers select the color of the lighting. The fourth number sets a distance to trace, and the last number sets the intensity of the floodlight. + +

This lighting is somewhat similar to -dirty, combined with use of the ambient key in worldspawn.

+ + +

This feature was contributed by the Urban Terror team.

+ + +

Lightmap exposure

+ + +

+ + +

The q3map2 light compile parameter -exposure changes the handling of overbright pixels to look more realistic. Try values like -exposure 200! The higher the value, the darker the result gets.

+ + +

It is most interesting if you intentionally put colored lights of a far too high light value in your map.

+ + +

This feature was contributed by the Urban Terror team.

+ + +

dotProductScale

+ + + + + + + + + + + + + + + +
dotProduct dotProduct2 dotProductScale dotProduct2scale
+ + + + +

The following shader parameters are added for use with terrain blending:

+ + +
+q3map_alphagen dotProduct2scale ( X Y Z MIN MAX )
+
+ +

As with dotProduct2, X Y Z denote the normal on the plane to use for dotProduct terrain blending. The values MIN and MAX specify a range of squared cosine values, so that MIN is mapped to alpha value 0, MAX is mapped to alpha value 1, and everything in between is mapped linearily.

+ + +

If you prefer working with angles, you can set MIN to the squared cosine of the most steep angle of the blending, and MAX to the squared cosine of the most flat angle of the blending. Example:

+ + +
+q3map_alphagen dotProduct2scale ( 0 0 1 0.250 0.933 )
+
+ +

will set alpha value 0 for anything steeper than 60 degrees, and alpha value 1 for anything more flat than 15 degrees.

+ + +

The same extension to dotProduct is called dotProductScale. Note that the MIN and MAX values are not squared there. You get a different curve with the same min/max angles by writing:

+ + +
+q3map_alphagen dotProductScale ( 0 0 1 0.500 0.966 )
+
+ +

Minimum sample size

+ + +

The compiler option -minsamplesize in the BSP stage enforces a given minimum
sample size, even if shaders or func_groups set a lightmap scale. This allows
higher quality compiles of existing maps that use these features without having
to create extraordinarily large amounts of lightmaps, especially when using
external lightmaps.

+ + +

Converting to ASE prefabs

+ + +

Use -convert -format ase -shadersasbitmap for this purpose. That way, the
created ASE files work as prefabs, and thus contain shader names, instead of
texture file names. In a model editor, however, these ASE files will not show
up with textures, as modelling software does not support Q3 shaders.

+ + +

Zero-effort cel shading

+ + +

If you already have a cel shader (in Nexuiz, cel/black_ink), you can compile
the map using it easily by adding the option -celshader cel/black_ink to the
BSP stage.

+ + +

MiniMap generator

+ + +

+ + +This version of the map compiler can generate minimaps as used by Nexuiz.
The specs of the minimap's texture mapping are: +
    +
  • Let M be the rectangle of the world's mins/maxs coordinates.
  • +
  • If keepaspect is set: let S be the smallest square completely covering M whose center matches the one of M. Otherwise, let S be M.
  • +
  • Let S' be S scaled by factor 1/(1-2*border) around the center of S.
  • +
  • The "mins" corner of S' corresponds to the top left corner of the image.
  • +
  • The "maxs" corner of S' corresponds to the bottom right corner of the image.
  • +
+
+ + +
+ +

floodlight.jpg + (7 КБ) + + + divVerent, Вт, 05 мая 2009, 13:26:00 -0400 + +

+ +

exposure.jpg + (27.8 КБ) + + + divVerent, Вт, 05 мая 2009, 13:26:31 -0400 + +

+ +

dotproduct-small.jpg + (25.9 КБ) + + + divVerent, Вт, 05 мая 2009, 13:29:00 -0400 + +

+ +

dotproduct2-small.jpg + (24.2 КБ) + + + divVerent, Вт, 05 мая 2009, 13:29:36 -0400 + +

+ +

dotproductscale-small.jpg + (23.3 КБ) + + + divVerent, Вт, 05 мая 2009, 13:29:54 -0400 + +

+ +

dotproduct2scale-small.jpg + (22.8 КБ) + + + divVerent, Вт, 05 мая 2009, 13:30:08 -0400 + +

+ +

minimap.jpg + (6.4 КБ) + + + divVerent, Вт, 05 мая 2009, 13:30:30 -0400 + +

+ +
+ + + + +

Экспортировать в + HTML + TXT +

+ + + + + + + + +
+
+
+ + + + +
+ + + diff --git a/docs/Additional_map_editor_features.htm b/docs/Additional_map_editor_features.htm new file mode 100644 index 00000000..e38f2221 --- /dev/null +++ b/docs/Additional_map_editor_features.htm @@ -0,0 +1,236 @@ + + + + +NetRadiant - Additional map editor features - Alientrap Development + + + + + + + + + + + + + + + + + +
+ + + + +
+ + +
+ +
+ + + + + + + + + +История +
+ + + + + +
+

Additional map editor features

+ + +

The following features were added to the map editor:

+ + +

OBJ model format

+ + +

OBJ files can be used as models, and texture information is taken from their associated MTL file.

+ + +

In Blender, make sure "use material groups" is enabled when exporting.

+ + +

Expand selection to whole entities

+ + +

Ctrl-Alt-E now selects the parent entity, too. Useful for duplicating the entity, as opposed to just its brushes.

+ + +

Targeting lines

+ + +

Targeting lines are also shown for target2, target3, target4 and killtarget keys to match usage in Nexuiz.

+ + +

Non-modal Rotate and Scale dialogs

+ + +

Rotate and Scale dialogs are no longer modal, so you can keep the dialog open and e.g. change the selection and then apply another rotation.

+ + +

Four-pane view improvement

+ + +

In the four-pane view, Ctrl-Tab now centers all three 2D views to the currently selected entity, so you do not need to hold Shift too.

+ + +

Strafe mode

+ + +

The option "Strafe mode" can be changed to select the strafing behaviour: default mode "Both" matches GtkRadiant 1.5: Ctrl enables sideways strafing, Shift-Ctrl enables forward moving. The other modes allow to switch between one of these two behaviours permanently, so Shift-Ctrl can be used to select faces while moving around in the 3D view.

+ + +

Regrouping entities

+ + +

When having a brush of an entity selected, this command removes that brush from the entity and puts it in worldspawn. When having a brush and an entity selected, it puts the brush into that entity (note: do this in the entity list or using the Ctrl-Alt-E bind "Expand selection to whole entities" to make sure that the entity, and not just one of its brushes, is selected). That way, you can insert/remove brushes from entities without having to retype all entity keys or redoing the brushwork.

+ + +

Clone selection change

+ + +

"Clone selection" (space) no longer changes targetname/target of entities, so you can clone to create many entities to target one. If you want to change targetname/target when cloning like Radiant did before, use Shift+Space.

+ + +

The clip line

+ + +

+ + +

The clip tool shows an extra line pointing in the direction of what's getting erased when you hit Enter. That way, the mistake of accidentally deleting the wrong half when clipping can be avoided.

+ + +

Automatic game configuration

+ + +

NetRadiant detects being inside a Q2World or Nexuiz install, and automatically configures its engine path to it. Other games can be easily added to the list.

+ + +

Editable keyboard shortcuts

+ + +

The Help/Keyboard shortcuts dialog now allows changing the shortcuts, so you can e.g. enable previously unbound features.

+ + +

Portable NetRadiant

+ + +

If you create a subdirectory called settings in your Radiant install, the install becomes "portable", that means it'll write its settings there, and not in a user-specific directory. Useful to take Radiant together with its settings with you on an USB stick.

+ + +

Makes especially much sense in conjunction with automatic game configuration.

+
+ + +
+ +

clipline-small.png + (1.8 КБ) + + + divVerent, Вт, 05 мая 2009, 13:13:56 -0400 + +

+ +

clipline.png + (400.6 КБ) + + + divVerent, Вт, 05 мая 2009, 13:14:57 -0400 + +

+ +
+ + + + +

Экспортировать в + HTML + TXT +

+ + + + + + + + +
+
+
+ + + + +
+ + + diff --git a/docs/Complete_list_of_command_line_parameters.htm b/docs/Complete_list_of_command_line_parameters.htm new file mode 100644 index 00000000..90b6879a --- /dev/null +++ b/docs/Complete_list_of_command_line_parameters.htm @@ -0,0 +1,385 @@ + + + + +NetRadiant - Complete list of command line parameters - Alientrap Development + + + + + + + + + + + + + + + + + +
+ + + + +
+ + +
+ +
+ + + + + + + + + +История +
+ + + + + +
+

Complete list of command line parameters

+ + +

Common options

+ + +
    +
  • -connect address: Talk to a NetRadiant instance using a specific XML based protocol
  • +
  • -force: Allow reading some broken/unsupported BSP files e.g. when decompiling, may also crash
  • +
  • -fs_basepath path: Sets the given path as main directory of the game (can be used more than once to look in multiple paths)
  • +
  • -fs_game gamename: Sets a different game directory name (default for Q3A: baseq3)
  • +
  • -fs_homebase dir: Specifies where the user home directory name is on Linux (default for Q3A: .q3a)
  • +
  • -game gamename: Load settings for the given game (default: quake3)
  • +
  • -subdivisions F: multiplier for patch subdivisions quality
  • +
  • -threads N: number of threads to use
  • +
  • -v: Verbose mode
  • +
+ + +

BSP stage

+ + +
    +
  • -bsp ... filename.map: Switch that enters this stage
  • +
  • -altsplit: Alternate BSP tree splitting weights (should give more fps)
  • +
  • -celshader shadername: Sets a global cel shader name
  • +
  • -custinfoparms: Read scripts/custinfoparms.txt
  • +
  • -debuginset: Push all triangle vertexes towards the triangle center
  • +
  • -debugportals: Make BSP portals visible in the map
  • +
  • -debugsurfaces: Color the vertexes according to the index of the surface
  • +
  • -deep: Use detail brushes in the BSP tree, but at lowest priority (should give more fps)
  • +
  • -de F: Distance epsilon for plane snapping etc.
  • +
  • -fakemap: Write fakemap.map containing all world brushes
  • +
  • -flares: Turn on support for flares (TEST?)
  • +
  • -flat: Enable flat shading (good for combining with -celshader)
  • +
  • -fulldetail: Treat detail brushes as structural ones
  • +
  • -leaktest: Abort if a leak was found
  • +
  • -meta: Combine adjacent triangles of the same texture to surfaces (ALWAYS USE THIS)
  • +
  • -minsamplesize N: Sets minimum lightmap resolution in luxels/qu
  • +
  • -mi N: Sets the maximum number of indexes per surface
  • +
  • -mv N: Sets the maximum number of vertices of a lightmapped surface
  • +
  • -ne F: Normal epsilon for plane snapping etc.
  • +
  • -nocurves: Turn off support for patches
  • +
  • -nodetail: Leave out detail brushes
  • +
  • -noflares: Turn off support for flares
  • +
  • -nofog: Turn off support for fog volumes
  • +
  • -nohint: Turn off support for hint brushes
  • +
  • -nosubdivide: Turn off support for q3map_tessSize (breaks water vertex deforms)
  • +
  • -notjunc: Do not fix T-junctions (causes cracks between triangles, do not use)
  • +
  • -nowater: Turn off support for water, slime or lava (Stef, this is for you)
  • +
  • -np A: Force all surfaces to be nonplanar with a given shade angle
  • +
  • -onlyents: Only update entities in the BSP
  • +
  • -patchmeta: Turn patches into triangle meshes for display
  • +
  • -rename: Append "\_bsp" suffix to misc\_model shaders (needed for SoF2)
  • +
  • -samplesize N: Sets default lightmap resolution in luxels/qu
  • +
  • -skyfix: Turn sky box into six surfaces to work around ATI problems
  • +
  • -snap N: Snap brush bevel planes to the given number of units
  • +
  • -tempname filename.map: Read the MAP file from the given file name
  • +
  • -texrange N: Limit per-surface texture range to the given number of units, and subdivide surfaces like with q3map_tessSize if this is not met
  • +
  • -tmpout: Write the BSP file to /tmp
  • +
  • -verboseentities: Enable -v only for map entities, not for the world
  • +
+ + +

VIS stage

+ + +
    +
  • -vis ... filename.map: Switch that enters this stage
  • +
  • -fast: Very fast and crude vis calculation
  • +
  • -mergeportals: The less crude half of -merge, makes vis sometimes much faster but doesn't hurt fps usually
  • +
  • -merge: Faster but still okay vis calculation
  • +
  • -nopassage: Just use PortalFlow vis (usually less fps)
  • +
  • -nosort: Do not sort the portals before calculating vis (usually slower)
  • +
  • -passageOnly: Just use PassageFlow vis (usually less fps)
  • +
  • -saveprt: Keep the PRT file after running vis (so you can run vis again)
  • +
  • -tmpin: Use /tmp folder for input
  • +
  • -tmpout: Use /tmp folder for output
  • +
+ + +

LIGHT stage

+ + +
    +
  • -light ... filename.map: Switch that enters this stage
  • +
  • -vlight ... filename.map: Deprecated alias for -light -fast ... filename.map
  • +
  • -approx N: Vertex light approximation tolerance (never use in conjunction with deluxemapping)
  • +
  • -areascale F, -area F: Scaling factor for area lights (surfacelight)
  • +
  • -border: Add a red border to lightmaps for debugging
  • +
  • -bouncegrid: Also compute radiosity on the light grid
  • +
  • -bounceonly: Only compute radiosity
  • +
  • -bouncescale F: Scaling factor for radiosity
  • +
  • -bounce N: Number of bounces for radiosity
  • +
  • -cheapgrid: Use -cheap style lighting for radiosity
  • +
  • -cheap: Abort vertex light calculations when white is reached
  • +
  • -compensate F: Lightmap compensate (darkening factor applied after everything else)
  • +
  • -cpma: CPMA vertex lighting mode
  • +
  • -custinfoparms: Read scripts/custinfoparms.txt
  • +
  • -dark: Darken lightmap seams
  • +
  • -debugaxis: Color the lightmaps according to the lightmap axis
  • +
  • -debugcluster: Color the lightmaps according to the index of the cluster
  • +
  • -debugdeluxe: Show deluxemaps on the lightmap
  • +
  • -debugnormals: Color the lightmaps according to the direction of the surface normal
  • +
  • -debugorigin: Color the lightmaps according to the origin of the luxels
  • +
  • -debugsurfaces, -debugsurface: Color the lightmaps according to the index of the surface
  • +
  • -debugunused: This option does nothing
  • +
  • -debug: Mark the lightmaps according to the cluster: unmapped clusters get yellow, occluded ones get pink, flooded ones get blue overlay color, otherwise red
  • +
  • -deluxemode 0: Use modelspace deluxemaps (DarkPlaces)
  • +
  • -deluxemode 1: Use tangentspace deluxemaps
  • +
  • -deluxe, -deluxemap: Enable deluxemapping (light direction maps)
  • +
  • -dirtdebug, -debugdirt: Store the dirtmaps as lightmaps for debugging
  • +
  • -dirtdepth: Dirtmapping depth
  • +
  • -dirtgain: Dirtmapping exponent
  • +
  • -dirtmode 0: Ordered direction dirtmapping
  • +
  • -dirtmode 1: Randomized direction dirtmapping
  • +
  • -dirtscale: Dirtmapping scaling factor
  • +
  • -dirty: Enable dirtmapping
  • +
  • -dump: Dump radiosity from -bounce into numbered MAP file prefabs
  • +
  • -export: Export lightmaps when compile finished (like -export mode)
  • +
  • -exposure F: Lightmap exposure to better support overbright spots
  • +
  • -external: Force external lightmaps even if at size of internal lightmaps
  • +
  • -extravisnudge: Broken feature to nudge the luxel origin to a better vis cluster
  • +
  • -extrawide: Deprecated alias for -super 2 -filter
  • +
  • -extra: Deprecated alias for -super 2
  • +
  • -fastbounce: Use -fast style lighting for radiosity
  • +
  • -faster: Use a faster falloff curve for lighting; also implies -fast
  • +
  • -fastgrid: Use -fast style lighting for the light grid
  • +
  • -fast: Ignore tiny light contributions
  • +
  • -filter: Lightmap filtering
  • +
  • -floodlight: Enable floodlight (zero-effort somewhat decent lighting)
  • +
  • -gamma F: Lightmap gamma
  • +
  • -gridambientscale F: Scaling factor for the light grid ambient components only
  • +
  • -gridscale F: Scaling factor for the light grid only
  • +
  • -keeplights: Keep light entities in the BSP file after compile
  • +
  • -lightmapdir directory: Directory to store external lightmaps (default: same as map name without extension)
  • +
  • -lightmapsize N: Size of lightmaps to generate (must be a power of two)
  • +
  • -lomem: Low memory but slower lighting mode
  • +
  • -lowquality: Low quality floodlight (appears to currently break floodlight)
  • +
  • -minsamplesize N: Sets minimum lightmap resolution in luxels/qu
  • +
  • -nocollapse: Do not collapse identical lightmaps
  • +
  • -nodeluxe, -nodeluxemap: Disable deluxemapping
  • +
  • -nogrid: Disable grid light calculation (makes all entities fullbright)
  • +
  • -nolightmapsearch: Do not optimize lightmap packing for GPU memory usage (as doing so costs fps)
  • +
  • -normalmap: Color the lightmaps according to the direction of the surface normal (TODO is this identical to -debugnormals?)
  • +
  • -nostyle, -nostyles: Disable support for light styles
  • +
  • -nosurf: Disable tracing against surfaces (only uses BSP nodes then)
  • +
  • -notrace: Disable shadow occlusion
  • +
  • -novertex: Disable vertex lighting
  • +
  • -patchshadows: Cast shadows from patches
  • +
  • -pointscale F, -point F: Scaling factor for point lights (light entities)
  • +
  • -q3: Use nonlinear falloff curve by default (like Q3A)
  • +
  • -samplescale F: Scales all lightmap resolutions
  • +
  • -samplesize N: Sets default lightmap resolution in luxels/qu
  • +
  • -samples N: Adaptive supersampling quality
  • +
  • -scale F: Scaling factor for all light types
  • +
  • -shadeangle A: Angle for phong shading
  • +
  • -shade: Enable phong shading at default shade angle
  • +
  • -skyscale F, -sky F: Scaling factor for sky and sun light
  • +
  • -smooth: Deprecated alias for -samples 2
  • +
  • -style, -styles: Enable support for light styles
  • +
  • -sunonly: Only compute sun light
  • +
  • -super N, -supersample N: Ordered grid supersampling quality
  • +
  • -thresh F: Triangle subdivision threshold
  • +
  • -trianglecheck: Broken check that should ensure luxels apply to the right triangle
  • +
  • -trisoup: Convert brush faces to triangle soup
  • +
  • -wolf: Use linear falloff curve by default (like W:ET)
  • +
+ + +

Analyzing BSP-like file structure

+ + +
    +
  • -analyze ... filename.bsp: Switch that enters this mode
  • +
  • -lumpswap: Swap byte order in the lumps
  • +
+ + +

Converting & Decompiling

+ + +
    +
  • -convert ... filename.bsp: Switch that enters this mode
  • +
  • -de number: Distance epsilon for the conversion
  • +
  • -format converter: Select the converter (available: map, ase, or game names)
  • +
  • -ne F: Normal epsilon for the conversion
  • +
  • -shadersasbitmap: (only for ase) use the shader names as *BITMAP key so they work as prefabs
  • +
+ + +

Exporting lightmaps

+ + +
    +
  • -export filename.bsp: Copies lightmaps from the BSP to filename/lightmap_0000.tga ff
  • +
+ + +

Fixing AAS checksum

+ + +
    +
  • -fixaas filename.bsp: Switch that enters this mode
  • +
+ + +

Get info about BSP file

+ + +
    +
  • -info filename.bsp: Switch that enters this mode
  • +
+ + +

Importing lightmaps

+ + +
    +
  • -import filename.bsp: Copies lightmaps from filename/lightmap_0000.tga ff into the BSP
  • +
+ + +

MiniMap

+ + +
    +
  • -minimap ... filename.bsp: Creates a minimap of the BSP, by default writes to ../gfx/filename_mini.tga
  • +
  • -black: Write the minimap as a black-on-transparency RGBA32 image
  • +
  • -boost F: Sets the contrast boost value (higher values make a brighter image); contrast boost is somewhat similar to gamma, but continuous even at zero
  • +
  • -border F: Sets the amount of border pixels relative to the total image size
  • +
  • -gray: Write the minimap as a white-on-black GRAY8 image
  • +
  • -keepaspect: Ensure the aspect ratio is kept (the minimap is then letterboxed to keep aspect)
  • +
  • -minmax xmin ymin zmin xmax ymax zmax: Forces specific map dimensions (note: the minimap actually uses these dimensions, scaled to the target size while keeping aspect with centering, and 1/64 of border appended to all sides)
  • +
  • -nokeepaspect: Do not ensure the aspect ratio is kept (makes it easier to use the image in your code, but looks bad together with sharpening)
  • +
  • -o filename.tga: Sets the output file name
  • +
  • -random N: Sets the randomized supersampling count (cannot be combined with -samples)
  • +
  • -samples N: Sets the ordered supersampling count (cannot be combined with -random)
  • +
  • -sharpen F: Sets the sharpening coefficient
  • +
  • -size N: Sets the width and height of the output image
  • +
  • -white: Write the minimap as a white-on-transparency RGBA32 image
  • +
+ + +

Scaling

+ + +
    +
  • -scale S filename.bsp: Scale uniformly
  • +
  • -scale SX SY SZ filename.bsp: Scale non-uniformly
  • +
  • -scale -tex S filename.bsp: Scale uniformly without texture lock
  • +
  • -scale -tex SX SY SZ filename.bsp: Scale non-uniformly without texture lock
  • +
+
+ + + + + + +

Экспортировать в + HTML + TXT +

+ + + + + + + + +
+
+
+ + + + +
+ + + diff --git a/docs/Complete_list_of_entity_keys.htm b/docs/Complete_list_of_entity_keys.htm new file mode 100644 index 00000000..e5351092 --- /dev/null +++ b/docs/Complete_list_of_entity_keys.htm @@ -0,0 +1,262 @@ + + + + +NetRadiant - Complete list of entity keys - Alientrap Development + + + + + + + + + + + + + + + + + +
+ + + + +
+ + +
+ +
+ + + + + + + + + +История +
+ + + + + +
+

Complete list of entity keys

+ + +
    +
  • On all entities +
      +
    • angle: shorthand for just setting the yaw angle
    • +
    • angles: angles vector
    • +
    • origin: origin vector
    • +
    • targetname: name of the entity so it can be referenced by target keys.
    • +
    +
  • +
  • On brush entities, classname func_group and classname misc_model: +
      +
    • _castShadows, _cs: sets whether the entity casts shadows
    • +
    • _celshader: CelShader to use
    • +
    • _lightmapsamplesize, _samplesize: sample size to use for surfaces of this entity
    • +
    • _receiveShadows, _rs: sets whether the entity receives shadows
    • +
    • _shadeangle, _smoothnormals, _sn, _smooth: largest angle between faces to allow to treat them part of the same nonplanar surface
    • +
    • lightmapscale, _lightmapscale, _ls: scaling factor for the lightmap resolution
    • +
    +
  • +
  • On brush entities and on classname func_group: +
      +
    • _indexmap, alphamap: file name of index map image for terrain blending
    • +
    • _layers, layers: number of layers of the index map image (encoded as brightness levels)
    • +
    • _offsets, offsets: space separated list of z offsets
    • +
    • _shader, shader: shader name prefix of the index map (when this is set to X, the shader names that are generated will be like textures/X_42 and textures/X_23to42
    • +
    +
  • +
  • On brush entities +
      +
    • _clone, _ins, _instance: _clonename field value of a brush entity to clone brushes from (can be used to use the same brush model multiple times)
    • +
    • _clonename: see _clone
    • +
    • _patchMeta, patchMeta: generate a triangle soup from patches in this entity
    • +
    • _patchQuality: quality multiplier for patches on this surface when _patchMeta is used
    • +
    • _patchSubdivide: absolute quality setting for patches on this surface when _patchMeta is used
    • +
    • max: override the maxs vector of this brush entity
    • +
    • min: override the mins vector of this brush entity
    • +
    +
  • +
  • On classname worldspawn +
      +
    • _ambient, ambient: amount of ambient light
    • +
    • _blocksize, blocksize, chopsize: block size for unconditional BSP subdivisions
    • +
    • _farplanedist, fogclip, distancecull: far plane distance for vis culling -- this must be the shortest distance at which nothing can be seen any more due to fog
    • +
    • _floodlight: a quintuple of values "r g b dist intensity" to set global floodlight parameters (good defaults are 240 240 255 1024 128)
    • +
    • _fog, fog: if set, the whole map is fogged using the given shader name
    • +
    • _foghull: must be set to a sky shader when _fog is used
    • +
    • _ignoreleaks, ignoreleaks: when set, no leak test is performed
    • +
    • _keepLights: if set, light entities are not stripped from the BSP file when compiling
    • +
    • _mingridlight: amount of minimum grid light
    • +
    • _minlight: amount of minimum light
    • +
    • _minvertexlight: amount of minimum vertex light
    • +
    • _noshadersun: if set, sun light from shaders is suppressed
    • +
    • _q3map_cmdline: written by q3map2; contains the command line the map was compiled with
    • +
    • _q3map_version: written by q3map2; contains the version of q3map2 the map was compiled with
    • +
    • _style42alphagen: |alphaGen|-like shader definition string for light style 442 (works the same way for all style numbers)
    • +
    • _style42rgbgen: |rgbGen|-like shader definition string for light style 42 (works the same way for all style numbers)
    • +
    • gridsize: resolution of the light grid
    • +
    +
  • +
  • On classname light and classname lightJunior +
      +
    • Note: lightJunior entities only affect the light grid.
    • +
    • _anglescale: scales angle attenuation
    • +
    • _color: color of the light
    • +
    • _deviance, _deviation, _jitter: position deviance of the samples of a regular light (distributes the light samples in a cube of side length 2*_deviance around the origin), or angle deviance of the sun light samples in radians
    • +
    • _filterradius, _filteradius, _filter: filter radius for this light, similar to -light -filter
    • +
    • _samples: number of samples to use to get soft shadows from a light
    • +
    • _sun: if 1, this light is an infinite sun light
    • +
    • fade: Fade factor of light attenuation of linear lights. Linear lights completely vanish at distance light/(fade * 8000), so if you want the light to vanish at distance X, specify light/(8000*X) here.
    • +
    • light: intensity factor (default: 300)
    • +
    • radius: radius of a spotlight at the target point (default: 64)
    • +
    • scale: intensity multiplier
    • +
    • spawnflags: 1 = linear attenuation (inverted in -wolf lighting mode)
    • +
    • spawnflags: 2 = no angle attenuation (inverted in -wolf lighting mode)
    • +
    • spawnflags: 32 = the light color is not normalized
    • +
    • spawnflags: 64 = force distance attenuation (why did vortex add this, this is always set...?)
    • +
    • target: target of a spotlight
    • +
    • targetname: when set, the light can be toggled in game by some engine provided way
    • +
    +
  • +
  • On classname light +
      +
    • _flare: when set, this light is a flare without a specified shader
    • +
    • _flareshader: shader for a flare surface generated by this light
    • +
    • _style, style: light style number
    • +
    • spawnflags: 16 = light does not affect the grid
    • +
    +
  • +
  • On classname advertisement (QuakeLive only) +
      +
    • Brushes/Patches: must be a single rectangular patch surface where the ad will be placed
    • +
    • cellId: identifier of the ad, must be used only once (??)
    • +
    +
  • +
  • On classname _decal +
      +
    • Brushes/Patches: must be a single rectangular patch surface
    • +
    • target: positional target to set the projection direction of the decal
    • +
    +
  • +
  • On classname misc_model +
      +
    • _castShadows, _cs: sets whether the entity casts shadows
    • +
    • _frame2: frame of second model to load
    • +
    • _frame: frame of model to load
    • +
    • _receiveShadows, _rs: sets whether the entity receives shadows
    • +
    • _remapXXX: XXX can be any string to allow multiple keys for this; contains a string of the form from;to, and any shader from in the model will be replaced by to; the special value * in from matches all shader names
    • +
    • model2: path name of second model to load
    • +
    • model: path name of model to load
    • +
    • modelscale: scaling factor for the model to include
    • +
    • modelscale_vec: non-uniform scaling vector for the model to include
    • +
    • spawnflags: 1 = in SoF2, append a _RMG_BSP suffix to shader names instead of _BSP
    • +
    • spawnflags: 2 = generate clipping brushes from the model
    • +
    • spawnflags: 4 = force this model through meta surface merging
    • +
    • spawnflags: 8 = when generating clipping planes, perform extrusion using the original normals from the model instead of per-triangle best axial normals
    • +
    • spawnflags: 16 = when generating clipping planes, perform extrusion using only up or down pointing normals (ideal for terrain)
    • +
    • spawnflags: 24 = when generating clipping planes, perform extrusion by distance zero (needs engine changes to support zero-volume clipping brushes)
    • +
    • spawnflags: 32 = turn vertex color from the model into alpha (for terrain blending)
    • +
    • spawnflags: 64 = do not let picomodel do surface normal smoothing
    • +
    +
  • +
  • On classname _skybox +
      +
    • To be placed inside a small otherwise hidden room; surfaces in it are scaled up, and visible through skybox surfaces
    • +
    • _scale: scaling factor or vector for the portal sky (default: 64)
    • +
  • +
+
+ + + + + + +

Экспортировать в + HTML + TXT +

+ + + + + + + + +
+
+
+ + + + +
+ + + diff --git a/docs/Complete_list_of_shader_keywords.htm b/docs/Complete_list_of_shader_keywords.htm new file mode 100644 index 00000000..2a3e0215 --- /dev/null +++ b/docs/Complete_list_of_shader_keywords.htm @@ -0,0 +1,254 @@ + + + + +NetRadiant - Complete list of shader keywords - Alientrap Development + + + + + + + + + + + + + + + + + +
+ + + + +
+ + +
+ +
+ + + + + + + + + +История +
+ + + + + +
+

Complete list of shader keywords

+ + +

Note: You can append a :q3map suffix to a shader name to make q3map2 use the shader but the engine ignore it.

+ + +

Note: This list does not contain all possible shader keywords, but just those that q3map2 uses for anything. More keywords are likely to be supported by your engine. Check the documentation or the source code of your engine on this.

+ + +

Inside shader stages

+ + +
    +
  • map texture: loads a texture and displays it repeating
  • +
  • clampMap texture: loads a texture and displays it non-repeating
  • +
  • animMap fps texture texture...: loads an animation and displays it repeating
  • +
  • clampAnimMap fps texture texture...: loads an animation and displays it non-repeating
  • +
  • mapComp texture: ???
  • +
  • mapNoComp texture: ???
  • +
+ + +

In the shader preamble:

+ + +
    +
  • cull none, cull disable, cull twosided: treat the surface as two sided for lighting
  • +
  • damageShader shadername: sets the given shader as damage shader (for SoF2 mods)
  • +
  • fogparms ...: marks the brush as a fog volume; otherwise handled by the engine
  • +
  • implicitBlend: ???
  • +
  • implicitMap: ???
  • +
  • implicitMask: ???
  • +
  • light shadername: sets the given shader as flare shader
  • +
  • polygonoffset: enable polygon offset
  • +
  • q3map_backShader shadername: sets the given shader as shader for back faces
  • +
  • q3map_backsplash percent distance: configures light backsplash (self-surfacelight)
  • +
  • q3map_baseShader shadername: inherit parameters from a shader
  • +
  • q3map_bounce F, q3map_bounceScale F: scales the intensity of radiosity
  • +
  • q3map_clipmodel: ???
  • +
  • q3map_cloneShader shadername: ???
  • +
  • q3map_colorGen: FIXME later
  • +
  • q3map_deprecateShader shadername: ???
  • +
  • q3map_flare shadername, q3map_flareShader shadername: sets the given shader as flare shader
  • +
  • q3map_floodLight r g b dist intensity power: overrides the global floodlight parameters
  • +
  • q3map_fogDir ( x y z ): sets the direction a fog shader fades from transparent to opaque
  • +
  • q3map_foliage path scale density odds invertalpha: ???
  • +
  • q3map_forceMeta: forces brush faces and/or triangle models to go through the metasurface pipeline
  • +
  • q3map_forceSunlight: ???
  • +
  • q3map_fur layers offset fade: ???
  • +
  • q3map_globaltexture: ???
  • +
  • q3map_indexed: ???
  • +
  • q3map_invert: inverts the direction from which the face is visible
  • +
  • q3map_lightRGB r g b: overrides the light color of the texture
  • +
  • q3map_lightStyle N: sets the light style (SoF2, JK2)
  • +
  • q3map_lightSubdivide N: subdivision interval for q3map_surfacelight
  • +
  • q3map_lightmapAxis axis: sets the lightmap axis to one of x, y, z (useful for terrain)
  • +
  • q3map_lightmapBrightness F, q3map_lightmapGamma F: overrides lightmap brightness
  • +
  • q3map_lightmapFilterRadius self other: ???
  • +
  • q3map_lightmapMergable: allows merging the lightmap with other surfaces on another plane
  • +
  • q3map_lightmapSampleOffset F: multiplies samplesize by a factor
  • +
  • q3map_lightmapSampleSize N: overrides samplesize
  • +
  • q3map_lightmapSize N: overrides the lightmap size (forces an external lightmap for this surface)
  • +
  • q3map_material materialname: ???
  • +
  • q3map_noFast: disable -fast style lighting for this surface
  • +
  • q3map_noVertexLight: turns off vertex lighting for this surface
  • +
  • q3map_noVertexShadows: ???
  • +
  • q3map_noclip: do not clip the surface by the BSP tree
  • +
  • q3map_nofog: ???
  • +
  • q3map_nonplanar: marks the surface as nonplanar for meta surface merging
  • +
  • q3map_notjunc: do not do t-junction elimination
  • +
  • q3map_offset F: ???
  • +
  • q3map_onlyVertexLighting: same as using surfaceparm pointlight
  • +
  • q3map_patchShadows: force shadowing from patches using this shader
  • +
  • q3map_remapShader shadername: ???
  • +
  • q3map_shadeAngle F: sets the shading angle for nonplanar surfaces
  • +
  • q3map_skyLight value iterations: sets the amount of sky light from this surface
  • +
  • q3map_splotchfix: ???
  • +
  • q3map_styleMarker2: ???
  • +
  • q3map_styleMarker: ???
  • +
  • q3map_sunext r g b intensity degrees elevation deviance samples: sets an unsharp sun for the map
  • +
  • q3map_sun r g b intensity degrees elevation, sun r g b intensity degrees elevation: sets a sharp sun for the map
  • +
  • q3map_surfacelight F: sets the amount of surface light from this surface
  • +
  • q3map_surfacemodel path density minscale maxscale minangle maxangle oriented: randomly place models on the surface
  • +
  • q3map_tcGen ivector ( sx sy sz ) ( tx ty tz ): same as q3map_tcGen vector but with inverted values
  • +
  • q3map_tcGen vector ( sx sy sz ) ( tx ty tz ): overrides texcoords based on world coordinates (for terrain)
  • +
  • q3map_tcMod rotate a: rotates the texture
  • +
  • q3map_tcMod scale s t: multiplies texcoords by factors
  • +
  • q3map_tcMod translate s t: translates texcoords by a vector
  • +
  • q3map_terrain: ???
  • +
  • q3map_textureSize width height: overrides the texture size for texcoords
  • +
  • q3map_vertexScale F: scales vertex lighting amount by a factor
  • +
  • q3map_vertexShadows: ???
  • +
  • qer_editorImage texture: sets the texture to show for radiant
  • +
  • qer_lightImage texture: sets the image to take the light color from
  • +
  • qer_normalImage texture: sets the normal map for bump mapping
  • +
  • skyparms outerimage cloudheight innerimage: loads a skybox
  • +
  • surfaceparm alphashadow: use the alpha channel of the shader image as shadow mask
  • +
  • surfaceparm areaportal: ???
  • +
  • surfaceparm botclip: ???
  • +
  • surfaceparm clusterportal: ???
  • +
  • surfaceparm detail: ignore this surface for vis
  • +
  • surfaceparm donotenter: ???
  • +
  • surfaceparm fog: ???
  • +
  • surfaceparm hint: use this surface as a hint to generate BSP splits
  • +
  • surfaceparm lava: Stef hates this brush
  • +
  • surfaceparm lightfilter: use the color channel of the shader image as shadow mask
  • +
  • surfaceparm monsterclip: monsters can't go through this brush, but shots can
  • +
  • surfaceparm nodraw: do not generate draw surfaces
  • +
  • surfaceparm nodrop: items can't be dropped on this brush
  • +
  • surfaceparm nolightmap, surfaceparm pointlight: do not lightmap this surface
  • +
  • surfaceparm nomarks: this surface is stain free
  • +
  • surfaceparm nonsolid: do not make this surface solid
  • +
  • surfaceparm origin: the center of this brush shall be the origin of this brush model
  • +
  • surfaceparm playerclip: players can't go through this brush, but shots can
  • +
  • surfaceparm sky: this surface shows the skybox
  • +
  • surfaceparm slime: this brush contains more poisonous stuff than dihydrogene monoxide
  • +
  • surfaceparm structural: use this surface for vis
  • +
  • surfaceparm trans: cast no shadows
  • +
  • surfaceparm trigger: this is a trigger brush (translucent and solid)
  • +
  • surfaceparm water: this brush contains dihydrogene monoxide
  • +
  • tessSize F, q3map_tessSize F: subdivides the polygons to ensure no parts are larger than the given size
  • +
+
+ + + + + + +

Экспортировать в + HTML + TXT +

+ + + + + + + + +
+
+
+ + + + +
+ + + diff --git a/docs/application.css b/docs/application.css new file mode 100644 index 00000000..0e359f8a --- /dev/null +++ b/docs/application.css @@ -0,0 +1,881 @@ +body { font-family: Verdana, sans-serif; font-size: 12px; color:#484848; margin: 0; padding: 0; min-width: 900px; } + +h1, h2, h3, h4 { font-family: "Trebuchet MS", Verdana, sans-serif;} +h1 {margin:0; padding:0; font-size: 24px;} +h2, .wiki h1 {font-size: 20px;padding: 2px 10px 1px 0px;margin: 0 0 10px 0; border-bottom: 1px solid #bbbbbb; color: #444;} +h3, .wiki h2 {font-size: 16px;padding: 2px 10px 1px 0px;margin: 0 0 10px 0; border-bottom: 1px solid #bbbbbb; color: #444;} +h4, .wiki h3 {font-size: 13px;padding: 2px 10px 1px 0px;margin-bottom: 5px; border-bottom: 1px dotted #bbbbbb; color: #444;} + +/***** Layout *****/ +#wrapper {background: white;} + +#top-menu {background: #55646D; color: #fff; height:2em; font-size: 0.8em; padding: 4px 2px 0px 6px;border-bottom:2px solid #333} +#top-menu ul {margin: 0; padding: 0;} +#top-menu li { + float:left; + list-style-type:none; + margin: 0; + padding: 0; + white-space:nowrap; +} +#top-menu a {color: #fff; margin-right: 8px; font-weight: bold;} +#top-menu #loggedas { float: right; margin-right: 0.5em; color: #fff; margin-top:2px; } + +#top-menu ul a { padding:2px 0 3px 20px; display:block; height:16px; } +#top-menu a.home { background:transparent url('house.png') 0 0 no-repeat; } +#top-menu a.my-page { background:transparent url('../images/my-page.png') 0 0 no-repeat; } +#top-menu a.projects { background:transparent url('projects.png') 0 0 no-repeat; } +#top-menu a.stuff-to-do { background:transparent url('../images/stuff_to_do.png') 0 0 no-repeat; } +#top-menu a.timesheet { background:transparent url('../images/timesheet.png') 0 0 no-repeat; } +#top-menu a.administration { background:transparent url('../images/administration.png') 0 0 no-repeat; } +#top-menu a.help { background:transparent url('help-top.png') 0 0 no-repeat; } +#top-menu a.logout { background:transparent url('../images/sign-out.png') 0 0 no-repeat; } +#top-menu a.my-account { background:transparent url('../images/my-account.png') 0 0 no-repeat; } + +#account {float:right;} + +#header {height:5.3em;margin:0;background:#547938 url('header_gradient.png') 0 0 repeat-x;color:#f8f8f8; padding: 4px 8px 0px 6px; position:relative;} +#header a {color:#f8f8f8;} +#header h1 { float:left; margin-top:5px; } +#header h1 a.ancestor { font-size: 80%; } +#quick-search {float:right;} + +#main-menu {position: absolute; bottom: 0px; left:6px; margin-right: -500px; } +#main-menu ul {margin: 0; padding: 0;} +#main-menu li { + float:left; + list-style-type:none; + margin: 0px 2px 0px 0px; + padding: 0px 0px 0px 0px; + white-space:nowrap; +} +#main-menu li a { + display: block; + color: #fff; + text-decoration: none; + font-weight: bold; + margin: 0; + padding: 4px 10px 4px 26px; + background-position:6px center; + background-repeat:no-repeat; +} +#main-menu li a:hover { + background:#8ACB58; + color:#fff; + background-position:6px center; + background-repeat:no-repeat; +} +#main-menu li a.selected, #main-menu li a.selected:hover { + background-color:#fff; + color:#555; + background-position:6px center; + background-repeat:no-repeat; +} + +/* Redmine core project-menu links */ +#main-menu li a.overview { background-image: url(document-text-image.png); } +#main-menu li a.activity { background-image: url(lightning.png); } +#main-menu li a.roadmap { background-image: url(../images/fugue/map-pin.png); } +#main-menu li a.issues { background-image: url(ticket.png); } +#main-menu li a.new-issue { background-image: url(../images/fugue/ticket--plus.png); } +#main-menu li a.news { background-image: url(newspaper.png); } +#main-menu li a.documents { background-image: url(documents-text.png); } +#main-menu li a.wiki { background-image: url(document-horizontal-text.png); } +#main-menu li a.boards { background-image: url(balloons.png); } +#main-menu li a.files { background-image: url(document-zipper.png); } +#main-menu li a.repository { background-image: url(safe.png); } +#main-menu li a.customers { background-image: url(../images/fugue/user-business.png); } +#main-menu li a.ezfaq { background-image: url(help-top.png); } +#main-menu li a.settings { background-image: url(../images/fugue/equalizer.png); } + +#main {background-color:#EEEEEE;} + +#sidebar{ float: right; width: 17%; position: relative; z-index: 9; min-height: 600px; padding: 0; margin: 0;} +* html #sidebar{ width: 17%; } +#sidebar h3{ font-size: 14px; margin-top:14px; color: #666; } +#sidebar hr{ width: 100%; margin: 0 auto; height: 1px; background: #ccc; border: 0; } +* html #sidebar hr{ width: 95%; position: relative; left: -6px; color: #ccc; } + +#content { width: 80%; background-color: #fff; margin: 0px; border-right: 1px solid #ddd; padding: 6px 10px 10px 10px; z-index: 10; } +* html #content{ width: 80%; padding-left: 0; margin-top: 0px; padding: 6px 10px 10px 10px;} +html>body #content { min-height: 600px; } +* html body #content { height: 600px; } /* IE */ + +#main.nosidebar #sidebar{ display: none; } +#main.nosidebar #content{ width: auto; border-right: 0; } + +#footer {clear: both; border-top: 1px solid #bbb; font-size: 0.9em; color: #aaa; padding: 5px; text-align:center; background:#fff;} + +#login-form table {margin-top:5em; padding:1em; margin-left: auto; margin-right: auto; border: 2px solid #FDBF3B; background-color:#FFEBC1; } +#login-form table td {padding: 6px;} +#login-form label {font-weight: bold;} +#login-form input#username, #login-form input#password { width: 300px; } + +input#openid_url { background: url(../images/openid-bg.gif) no-repeat; background-color: #fff; background-position: 0 50%; padding-left: 18px; } + +.clear:after{ content: "."; display: block; height: 0; clear: both; visibility: hidden; } + +/***** Links *****/ +a, a:link, a:visited{ color: #3F794B; text-decoration: none; } +a:hover, a:active{ color: #3A4F5B; text-decoration: underline;} +a img{ border: 0; } + +a.issue.closed, a.issue.closed:link, a.issue.closed:visited { text-decoration: line-through; } + +/***** Tables *****/ +table.list { border: 1px solid #49702A; border-collapse: collapse; width: 100%; margin-bottom: 4px; } +table.list th { background:#547938 url('header_gradient.png') 0 0 repeat-x; color:#D6E4CB; padding: 4px; white-space:nowrap; } +table.list th a { color:#fff; } +table.list th a:hover { color:#E0FFC9; } +table.list td { vertical-align: top; } +table.list td.id { width: 2%; text-align: center;} +table.list td.checkbox { width: 15px; padding: 0px;} +table.list.issues .status-1 .id { background:transparent url('../images/new.png') center 18px no-repeat; } +table.list.issues .status-2 .id { background:transparent url('../images/assigned.png') center 18px no-repeat; } +table.list.issues .status-3 .id { background:transparent url('../images/resolved.png') center 18px no-repeat; } +table.list.issues .status-4 .id { background:transparent url('../images/feedback.png') center 18px no-repeat; } +table.list.issues .status-5 .id { background:transparent url('../images/closed.png') center 18px no-repeat; } +table.list.issues .status-6 .id { background:transparent url('../images/rejected.png') center 18px no-repeat; } +table.list.issues .status-8 .id { background:transparent url('../images/waiting.png') center 18px no-repeat; } +table.list.issues .status-7 .id { background:transparent url('../images/wont_fix.png') center 18px no-repeat; } + +tr.issue { text-align: center; white-space: nowrap; } +tr.issue td.subject, tr.issue td.category, td.assigned_to { white-space: normal; } +tr.issue td.subject { text-align: left; } +tr.issue td.done_ratio { width:80px } +tr.issue td.done_ratio table.progress { margin-left:auto; margin-right: auto;} + +tr.priority-1.even { background-color:#F8FFAA; } +tr.priority-1.odd { background-color:#FAFFB8; } +tr.priority-2.even { background-color:#FFEF9A; } +tr.priority-2.odd { background-color:#FFF1A6; } +tr.priority-3.even { background-color:#FFD29A; } +tr.priority-3.odd { background-color:#FFD8A6; } +tr.priority-4.even { background-color:#FFB19A; } +tr.priority-4.odd { background-color:#FFBAA6; } +tr.priority-5.even { background-color:#FF9A9F; } +tr.priority-5.odd { background-color:#FFA6AA; } + +.status { text-align:left; padding-left:22px; } +tr.status-1 .status { background:transparent url('../images/new.png') 0 0 no-repeat; } +tr.status-2 .status { background:transparent url('../images/assigned.png') 0 0 no-repeat; } +tr.status-3 .status { background:transparent url('../images/resolved.png') 0 0 no-repeat; } +tr.status-4 .status { background:transparent url('../images/feedback.png') 0 0 no-repeat; } +tr.status-5 .status { background:transparent url('../images/closed.png') 0 0 no-repeat; } +tr.status-6 .status { background:transparent url('../images/rejected.png') 0 0 no-repeat; } +tr.status-8 .status { background:transparent url('../images/waiting.png') 0 0 no-repeat; } +tr.status-7 .status { background:transparent url('../images/wont_fix.png') 0 0 no-repeat; } + +tr.entry { border: 1px solid #f8f8f8; } +tr.entry td { white-space: nowrap; } +tr.entry td.filename { width: 30%; } +tr.entry td.size { text-align: right; font-size: 90%; } +tr.entry td.revision, tr.entry td.author { text-align: center; } +tr.entry td.age { text-align: right; } + +tr.entry span.expander {background-image: url(../images/bullet_toggle_plus.png); padding-left: 8px; margin-left: 0; cursor: pointer;} +tr.entry.open span.expander {background-image: url(../images/bullet_toggle_minus.png);} +tr.entry.file td.filename a { margin-left: 16px; } +tr span.expander {background-image: url(../images/bullet_toggle_plus.png); padding-left: 8px; margin-left: 0; cursor: pointer;} +tr.open span.expander {background-image: url(../images/bullet_toggle_minus.png);} + +tr.changeset td.author { text-align: center; width: 15%; } +tr.changeset td.committed_on { text-align: center; width: 15%; } + +table.files tr.file td { text-align: center; } +table.files tr.file td.filename { text-align: left; padding-left: 24px; } +table.files tr.file td.digest { font-size: 80%; } + +table.members td.roles, table.memberships td.roles { width: 45%; } + +tr.message { height: 2.6em; } +tr.message td.last_message { font-size: 80%; } +tr.message.locked td.subject a { background-image: url(../images/locked.png); } +tr.message.sticky td.subject a { background-image: url(../images/sticky.png); font-weight: bold; } + +tr.user td { width:13%; } +tr.user td.email { width:18%; } +tr.user td { white-space: nowrap; } +tr.user.locked, tr.user.registered { color: #aaa; } +tr.user.locked a, tr.user.registered a { color: #aaa; } + +tr.time-entry { text-align: center; white-space: nowrap; } +tr.time-entry td.subject, tr.time-entry td.comments { text-align: left; white-space: normal; } +td.hours { text-align: right; font-weight: bold; padding-right: 0.5em; } +td.hours .hours-dec { font-size: 0.9em; } + +table.plugins td { vertical-align: middle; } +table.plugins td.configure { text-align: right; padding-right: 1em; } +table.plugins span.name { font-weight: bold; display: block; margin-bottom: 6px; } +table.plugins span.description { display: block; font-size: 0.9em; } +table.plugins span.url { display: block; font-size: 0.9em; } + +table.list tbody tr.group td { padding: 0.8em 0 0.5em 0.3em; font-weight: bold; border-bottom: 1px solid #ccc; } +table.list tbody tr.group span.count { color: #aaa; font-size: 80%; } + +table.list tbody tr:hover { background-color:#ffffdd; } +table.list tbody tr.group:hover { background-color:inherit; } +table td {padding:2px;} +table p {margin:0;} +.odd {background-color:#f6f7f8;} +.even {background-color: #fff;} +a.sort { padding-right: 16px; background-position: 100% 50%; background-repeat: no-repeat; } +a.sort.asc { background-image: url(../images/sort_asc.png); } +a.sort.desc { background-image: url(../images/sort_desc.png); } + +table.attributes { width: 100% } +table.attributes th { vertical-align: top; text-align: left; } +table.attributes td { vertical-align: top; } + +td.center {text-align:center;} + +.highlight { background-color: #FCFD8D;} +.highlight.token-1 { background-color: #faa;} +.highlight.token-2 { background-color: #afa;} +.highlight.token-3 { background-color: #aaf;} + +.box{ +padding:6px; +margin-bottom: 10px; +background-color:#f6f6f6; +color:#505050; +line-height:1.5em; +border: 1px solid #e4e4e4; +} + +div.square { + border: 1px solid #999; + float: left; + margin: .3em .4em 0 .4em; + overflow: hidden; + width: .6em; height: .6em; +} +.contextual {float:right; white-space: nowrap; line-height:1.4em;margin-top:5px; padding-left: 10px; font-size:0.9em;} +.contextual input, .contextual select {font-size:0.9em;} +.message .contextual { margin-top: 0; } + +.splitcontentleft{float:left; width:49%;} +.splitcontentright{float:right; width:49%;} +form {display: inline;} +input, select {vertical-align: middle; margin-top: 1px; margin-bottom: 1px;} +fieldset {border: 1px solid #e4e4e4; margin:0;} +legend {color: #484848;} +hr { width: 100%; height: 1px; background: #ccc; border: 0;} +blockquote { font-style: italic; border-left: 3px solid #e0e0e0; padding-left: 0.6em; margin-left: 2.4em;} +blockquote blockquote { margin-left: 0;} +textarea.wiki-edit { width: 99%; } +li p {margin-top: 0;} +div.issue {background:#ffffdd; padding:6px; margin-bottom:6px;border: 1px solid #d7d7d7;} +p.breadcrumb { font-size: 0.9em; margin: 4px 0 4px 0;} +p.subtitle { font-size: 0.9em; margin: -6px 0 12px 0; font-style: italic; } +p.footnote { font-size: 0.9em; margin-top: 0px; margin-bottom: 0px; } + +fieldset.collapsible { border-width: 1px 0 0 0; font-size: 0.9em; } +fieldset.collapsible legend { padding-left: 16px; background: url(../images/arrow_expanded.png) no-repeat 0% 40%; cursor:pointer; } +fieldset.collapsible.collapsed legend { background-image: url(../images/arrow_collapsed.png); } + +fieldset#date-range p { margin: 2px 0 2px 0; } +fieldset#filters table { border-collapse: collapse; } +fieldset#filters table td { padding: 0; vertical-align: middle; } +fieldset#filters tr.filter { height: 2em; } +fieldset#filters td.add-filter { text-align: right; vertical-align: top; } +.buttons { font-size: 0.9em; margin-bottom: 1.4em; margin-top: 1em; } + +div#issue-changesets {float:right; width:45%; margin-left: 1em; margin-bottom: 1em; background: #fff; padding-left: 1em; font-size: 90%;} +div#issue-changesets .changeset { padding: 4px;} +div#issue-changesets .changeset { border-bottom: 1px solid #ddd; } +div#issue-changesets p { margin-top: 0; margin-bottom: 1em;} + +div#activity dl, #search-results { margin-left: 2em; } +div#activity dd, #search-results dd { margin-bottom: 1em; padding:4px; font-size: 0.9em; background-color:#eee; border:1px solid #ddd; border-top:0; } +div#activity dt, #search-results dt { margin-bottom: 0px; padding-left: 20px; line-height: 18px; background-position: 0 50%; background-repeat: no-repeat; } +div#activity dt.me .time { border-bottom: 1px solid #999; } +div#activity dt .time { color: #777; font-size: 80%; } +div#activity dd .description, #search-results dd .description { font-style: italic; } +div#activity span.project:after, #search-results span.project:after { content: " -"; } +div#activity dd span.description, #search-results dd span.description { display:block; } + +#search-results dd { margin-bottom: 1em; padding-left: 20px; margin-left:0px; } +div#search-results-counts {float:right;} +div#search-results-counts ul { margin-top: 0.5em; } +div#search-results-counts li { list-style-type:none; float: left; margin-left: 1em; } + +dt.me a { background:transparent url(../images/fav.png) right 0 no-repeat; padding-right:18px; } +dt.issue { background-image: url(../images/ticket.png); background-color:#fed; border:1px solid #edc; } +dt.issue-edit { background-image: url(../images/ticket_edit.png); background-color:#fdf; border:1px solid #ece; } +dt.issue-closed { background-image: url(../images/ticket_checked.png); background-color:#ddf; border:1px solid #cce; } +dt.issue-note { background-image: url(../images/ticket_note.png); background-color:#ffd; border:1px solid #eec; } +dt.changeset { background-image: url(../images/changeset.png); background-color:#dfd; border:1px solid #cec; } +dt.news { background-image: url(../images/news.png); } +dt.message { background-image: url(../images/message.png); } +dt.reply { background-image: url(../images/comments.png); } +dt.wiki-page { background-image: url(../images/wiki_edit.png); } +dt.attachment { background-image: url(attachment.png); } +dt.document { background-image: url(../images/document.png); } +dt.project { background-image: url(projects.png); } +dt.time-entry { background-image: url(../images/time.png); } + +#search-results dt.issue.closed { background-image: url(../images/ticket_checked.png); } + +div#roadmap fieldset.related-issues { margin-bottom: 1em; } +div#roadmap fieldset.related-issues ul { margin-top: 0.3em; margin-bottom: 0.3em; } +div#roadmap .wiki h1:first-child { display: none; } +div#roadmap .wiki h1 { font-size: 120%; } +div#roadmap .wiki h2 { font-size: 110%; } + +div#version-summary { float:right; width:380px; margin-left: 16px; margin-bottom: 16px; background-color: #fff; } +div#version-summary fieldset { margin-bottom: 1em; } +div#version-summary .total-hours { text-align: right; } + +table#time-report td.hours, table#time-report th.period, table#time-report th.total { text-align: right; padding-right: 0.5em; } +table#time-report tbody tr { font-style: italic; color: #777; } +table#time-report tbody tr.last-level { font-style: normal; color: #555; } +table#time-report tbody tr.total { font-style: normal; font-weight: bold; color: #555; background-color:#EEEEEE; } +table#time-report .hours-dec { font-size: 0.9em; } + +form#issue-form .attributes { margin-bottom: 8px; } +form#issue-form .attributes p { padding-top: 1px; padding-bottom: 2px; } +form#issue-form .attributes select { min-width: 30%; } + +ul.projects { margin: 0; padding-left: 1em; } +ul.projects.root { margin: 0; padding: 0; } +ul.projects ul { border-left: 3px solid #e0e0e0; } +ul.projects li { list-style-type:none; } +ul.projects li.root { margin-bottom: 1em; } +ul.projects li.child { margin-top: 1em;} +ul.projects div.root a.project { font-family: "Trebuchet MS", Verdana, sans-serif; font-weight: bold; font-size: 16px; margin: 0 0 10px 0; } +.my-project { padding-left: 18px; background: url(../images/fav.png) no-repeat 0 50%; } + +#tracker_project_ids ul { margin: 0; padding-left: 1em; } +#tracker_project_ids li { list-style-type:none; } + +ul.properties {padding:0; font-size: 0.9em; color: #777;} +ul.properties li {list-style-type:none;} +ul.properties li span {font-style:italic;} + +.total-hours { font-size: 110%; font-weight: bold; } +.total-hours span.hours-int { font-size: 120%; } + +.autoscroll {overflow-x: auto; padding:1px; margin-bottom: 1.2em;} +#user_firstname, #user_lastname, #user_mail, #my_account_form select { width: 90%; } + +.pagination {font-size: 90%} +p.pagination {margin-top:8px;} + +/***** Tabular forms ******/ +.tabular p{ +margin: 0; +padding: 5px 0 8px 0; +padding-left: 180px; /*width of left column containing the label elements*/ +height: 1%; +clear:left; +} + +html>body .tabular p {overflow:hidden;} + +.tabular label{ +font-weight: bold; +float: left; +text-align: right; +margin-left: -180px; /*width of left column*/ +width: 175px; /*width of labels. Should be smaller than left column to create some right +margin*/ +} + +.tabular label.floating{ +font-weight: normal; +margin-left: 0px; +text-align: left; +width: 270px; +} + +.tabular label.block{ +font-weight: normal; +margin-left: 0px; +text-align: left; +float: none; +display: block; +width: auto; +} + +input#time_entry_comments { width: 90%;} + +#preview fieldset {margin-top: 1em; background: url(../images/draft.png)} + +.tabular.settings p{ padding-left: 300px; } +.tabular.settings label{ margin-left: -300px; width: 295px; } + +.required {color: #bb0000;} +.summary {font-style: italic;} + +#attachments_fields input[type=text] {margin-left: 8px; } + +div.attachments { margin-top: 12px; } +div.attachments p { margin:4px 0 2px 0; } +div.attachments img { vertical-align: middle; } +div.attachments span.author { font-size: 0.9em; color: #888; } + +p.other-formats { text-align: right; font-size:0.9em; color: #666; } +.other-formats span + span:before { content: "| "; } + +a.atom { background: url(../images/feed.png) no-repeat 1px 50%; padding: 2px 0px 3px 16px; } + +/* Project members tab */ +div#tab-content-members .splitcontentleft, div#tab-content-memberships .splitcontentleft, div#tab-content-users .splitcontentleft { width: 64% } +div#tab-content-members .splitcontentright, div#tab-content-memberships .splitcontentright, div#tab-content-users .splitcontentright { width: 34% } +div#tab-content-members fieldset, div#tab-content-memberships fieldset, div#tab-content-users fieldset { padding:1em; margin-bottom: 1em; } +div#tab-content-members fieldset legend, div#tab-content-memberships fieldset legend, div#tab-content-users fieldset legend { font-weight: bold; } +div#tab-content-members fieldset label, div#tab-content-memberships fieldset label, div#tab-content-users fieldset label { display: block; } +div#tab-content-members fieldset div, div#tab-content-users fieldset div { max-height: 400px; overflow:auto; } + +table.members td.group { padding-left: 20px; background: url(../images/users.png) no-repeat 0% 0%; } + +* html div#tab-content-members fieldset div { height: 450px; } + +/***** Flash & error messages ****/ +#errorExplanation, div.flash, .nodata, .warning { + padding: 4px 4px 4px 30px; + margin-bottom: 12px; + font-size: 1.1em; + border: 2px solid; +} + +div.flash {margin-top: 8px;} + +div.flash.error, #errorExplanation { + background: url(../images/false.png) 8px 5px no-repeat; + background-color: #ffe3e3; + border-color: #dd0000; + color: #550000; +} + +div.flash.notice { + background: url(../images/true.png) 8px 5px no-repeat; + background-color: #dfffdf; + border-color: #9fcf9f; + color: #005f00; +} + +div.flash.warning { + background: url(../images/warning.png) 8px 5px no-repeat; + background-color: #FFEBC1; + border-color: #FDBF3B; + color: #A6750C; + text-align: left; +} + +.nodata, .warning { + text-align: center; + background-color: #FFEBC1; + border-color: #FDBF3B; + color: #A6750C; +} + +#errorExplanation ul { font-size: 0.9em;} +#errorExplanation h2, #errorExplanation p { display: none; } + +/***** Ajax indicator ******/ +#ajax-indicator { +position: absolute; /* fixed not supported by IE */ +background-color:#eee; +border: 1px solid #bbb; +top:35%; +left:40%; +width:20%; +font-weight:bold; +text-align:center; +padding:0.6em; +z-index:100; +filter:alpha(opacity=50); +opacity: 0.5; +} + +html>body #ajax-indicator { position: fixed; } + +#ajax-indicator span { +background-position: 0% 40%; +background-repeat: no-repeat; +background-image: url(../images/loading.gif); +padding-left: 26px; +vertical-align: bottom; +} + +/***** Calendar *****/ +table.cal {border-collapse: collapse; width: 100%; margin: 0px 0 6px 0;border: 1px solid #d7d7d7;} +table.cal thead th {width: 14%;} +table.cal tbody tr {height: 100px;} +table.cal th { background-color:#EEEEEE; padding: 4px; } +table.cal td {border: 1px solid #d7d7d7; vertical-align: top; font-size: 0.9em;} +table.cal td p.day-num {font-size: 1.1em; text-align:right;} +table.cal td.odd p.day-num {color: #bbb;} +table.cal td.today {background:#ffffdd;} +table.cal td.today p.day-num {font-weight: bold;} + +/***** Tooltips ******/ +.tooltip{position:relative;z-index:24;} +.tooltip:hover{z-index:25;color:#000;} +.tooltip span.tip{display: none; text-align:left;} + +div.tooltip:hover span.tip{ +display:block; +position:absolute; +top:12px; left:24px; width:270px; +border:1px solid #555; +background-color:#fff; +padding: 4px; +font-size: 0.8em; +color:#505050; +} + +/***** Progress bar *****/ +table.progress { + border: 1px solid #D7D7D7; + border-collapse: collapse; + border-spacing: 0pt; + empty-cells: show; + text-align: center; + float:left; + margin: 1px 6px 1px 0px; +} + +table.progress td { height: 0.9em; } +table.progress td.todo { border:1px solid #aaa } +table.progress td.closed { background: #648749 none repeat scroll 0%; } +table.progress td.done { background: #DEF0DE none repeat scroll 0%; } +table.progress td.open { background: #FFF none repeat scroll 0%; } +p.pourcent {font-size: 80%;} +p.progress-info {clear: left; font-style: italic; font-size: 80%;} + +/***** Tabs *****/ +#content .tabs {height: 2.6em; border-bottom: 1px solid #bbbbbb; margin-bottom:1.2em; position:relative;} +#content .tabs ul {margin:0; position:absolute; bottom:-2px; padding-left:1em;} +#content .tabs>ul { bottom:-1px; } /* others */ +#content .tabs ul li { +float:left; +list-style-type:none; +white-space:nowrap; +margin-right:8px; +background:#fff; +} +#content .tabs ul li a{ +display:block; +font-size: 0.9em; +text-decoration:none; +line-height:1.3em; +padding:4px 6px 4px 6px; +border: 1px solid #ccc; +border-bottom: 1px solid #bbbbbb; +background-color: #eeeeee; +color:#777; +font-weight:bold; +} + +#content .tabs ul li a:hover { +background-color: #ffffdd; +text-decoration:none; +} + +#content .tabs ul li a.selected { +background-color: #fff; +border: 1px solid #bbbbbb; +border-bottom: 1px solid #fff; +} + +#content .tabs ul li a.selected:hover { +background-color: #fff; +} + +/***** Auto-complete *****/ +div.autocomplete { + position:absolute; + width:250px; + background-color:white; + margin:0; + padding:0; +} +div.autocomplete ul { + list-style-type:none; + margin:0; + padding:0; +} +div.autocomplete ul li.selected { background-color: #ffb;} +div.autocomplete ul li { + list-style-type:none; + display:block; + margin:0; + padding:2px; + cursor:pointer; + font-size: 90%; + border-bottom: 1px solid #ccc; + border-left: 1px solid #ccc; + border-right: 1px solid #ccc; +} +div.autocomplete ul li span.informal { + font-size: 80%; + color: #aaa; +} + +/***** Diff *****/ +.diff_out { background: #fcc; } +.diff_in { background: #cfc; } + +/***** Wiki *****/ +div.wiki table { + border: 1px solid #505050; + border-collapse: collapse; + margin-bottom: 1em; +} + +div.wiki table, div.wiki td, div.wiki th { + border: 1px solid #bbb; + padding: 4px; +} + +div.wiki .external { + background-position: 0% 60%; + background-repeat: no-repeat; + padding-left: 12px; + background-image: url(external.png); +} + +div.wiki a.new { + color: #b73535; +} + +div.wiki pre { + margin: 1em 1em 1em 1.6em; + padding: 2px; + background-color: #fafafa; + border: 1px solid #dadada; + width:95%; + overflow-x: auto; +} + +div.wiki ul.toc { + background-color: #ffffdd; + border: 1px solid #e4e4e4; + padding: 4px; + line-height: 1.2em; + margin-bottom: 12px; + margin-right: 12px; + margin-left: 0; + display: table +} +* html div.wiki ul.toc { width: 50%; } /* IE6 doesn't autosize div */ + +div.wiki ul.toc.right { float: right; margin-left: 12px; margin-right: 0; width: auto; } +div.wiki ul.toc.left { float: left; margin-right: 12px; margin-left: 0; width: auto; } +div.wiki ul.toc li { list-style-type:none;} +div.wiki ul.toc li.heading2 { margin-left: 6px; } +div.wiki ul.toc li.heading3 { margin-left: 12px; font-size: 0.8em; } + +div.wiki ul.toc a { + font-size: 0.9em; + font-weight: normal; + text-decoration: none; + color: #606060; +} +div.wiki ul.toc a:hover { color: #c61a1a; text-decoration: underline;} + +a.wiki-anchor { display: none; margin-left: 6px; text-decoration: none; } +a.wiki-anchor:hover { color: #aaa !important; text-decoration: none; } +h1:hover a.wiki-anchor, h2:hover a.wiki-anchor, h3:hover a.wiki-anchor { display: inline; color: #ddd; } + +/***** My page layout *****/ +.block-receiver { +border:1px dashed #c0c0c0; +margin-bottom: 20px; +padding: 15px 0 15px 0; +} + +.mypage-box { +margin:0 0 20px 0; +color:#505050; +line-height:1.5em; +} + +.handle { +cursor: move; +} + +a.close-icon { +display:block; +margin-top:3px; +overflow:hidden; +width:12px; +height:12px; +background-repeat: no-repeat; +cursor:pointer; +background-image:url('../images/close.png'); +} + +a.close-icon:hover { +background-image:url('../images/close_hl.png'); +} + +/***** Gantt chart *****/ +.gantt_hdr { + position:absolute; + top:0; + height:16px; + border-top: 1px solid #c0c0c0; + border-bottom: 1px solid #c0c0c0; + border-right: 1px solid #c0c0c0; + text-align: center; + overflow: hidden; +} + +.task { + position: absolute; + height:8px; + font-size:0.8em; + color:#888; + padding:0; + margin:0; + line-height:0.8em; +} + +.task_late { background:#f66 url(../images/task_late.png); border: 1px solid #f66; } +.task_done { background:#66f url(../images/task_done.png); border: 1px solid #66f; } +.task_todo { background:#aaa url(../images/task_todo.png); border: 1px solid #aaa; } +.milestone { background-image:url(../images/milestone.png); background-repeat: no-repeat; border: 0; } + +/***** Icons *****/ +.icon { +background-position: 0% 40%; +background-repeat: no-repeat; +padding-left: 20px; +padding-top: 2px; +padding-bottom: 3px; +} + +.icon22 { +background-position: 0% 40%; +background-repeat: no-repeat; +padding-left: 26px; +line-height: 22px; +vertical-align: middle; +} + +.icon-add { background-image: url(../images/add.png); } +.icon-edit { background-image: url(../images/edit.png); } +.icon-copy { background-image: url(../images/copy.png); } +.icon-del { background-image: url(../images/delete.png); } +.icon-move { background-image: url(../images/move.png); } +.icon-save { background-image: url(../images/save.png); } +.icon-cancel { background-image: url(../images/cancel.png); } +.icon-file { background-image: url(../images/file.png); } +.icon-folder { background-image: url(../images/folder.png); } +.open .icon-folder { background-image: url(../images/folder_open.png); } +.icon-package { background-image: url(../images/package.png); } +.icon-home { background-image: url(../images/home.png); } +.icon-user { background-image: url(../images/user.png); } +.icon-mypage { background-image: url(../images/user_page.png); } +.icon-admin { background-image: url(../images/admin.png); } +.icon-projects { background-image: url(projects.png); } +.icon-help { background-image: url(../images/help.png); } +.icon-attachment { background-image: url(attachment.png); } +.icon-index { background-image: url(../images/index.png); } +.icon-history { background-image: url(history.png); } +.icon-time { background-image: url(../images/time.png); } +.icon-time-add { background-image: url(../images/time_add.png); } +.icon-stats { background-image: url(../images/stats.png); } +.icon-warning { background-image: url(../images/warning.png); } +.icon-fav { background-image: url(../images/fav.png); } +.icon-fav-off { background-image: url(../images/fav_off.png); } +.icon-reload { background-image: url(../images/reload.png); } +.icon-lock { background-image: url(../images/locked.png); } +.icon-unlock { background-image: url(../images/unlock.png); } +.icon-checked { background-image: url(../images/true.png); } +.icon-details { background-image: url(../images/zoom_in.png); } +.icon-report { background-image: url(../images/report.png); } +.icon-comment { background-image: url(../images/comment.png); } + +.icon-file { background-image: url(../images/files/default.png); } +.icon-file.text-plain { background-image: url(../images/files/text.png); } +.icon-file.text-x-c { background-image: url(../images/files/c.png); } +.icon-file.text-x-csharp { background-image: url(../images/files/csharp.png); } +.icon-file.text-x-php { background-image: url(../images/files/php.png); } +.icon-file.text-x-ruby { background-image: url(../images/files/ruby.png); } +.icon-file.text-xml { background-image: url(../images/files/xml.png); } +.icon-file.image-gif { background-image: url(../images/files/image.png); } +.icon-file.image-jpeg { background-image: url(../images/files/image.png); } +.icon-file.image-png { background-image: url(../images/files/image.png); } +.icon-file.image-tiff { background-image: url(../images/files/image.png); } +.icon-file.application-pdf { background-image: url(../images/files/pdf.png); } +.icon-file.application-zip { background-image: url(../images/files/zip.png); } +.icon-file.application-x-gzip { background-image: url(../images/files/zip.png); } + +.icon22-projects { background-image: url(../images/22x22/projects.png); } +.icon22-users { background-image: url(../images/22x22/users.png); } +.icon22-groups { background-image: url(../images/22x22/groups.png); } +.icon22-tracker { background-image: url(../images/22x22/tracker.png); } +.icon22-role { background-image: url(../images/22x22/role.png); } +.icon22-workflow { background-image: url(../images/22x22/workflow.png); } +.icon22-options { background-image: url(../images/22x22/options.png); } +.icon22-notifications { background-image: url(../images/22x22/notifications.png); } +.icon22-system_notification { background-image: url(../images/22x22/system_notification.png); } +.icon22-authent { background-image: url(../images/22x22/authent.png); } +.icon22-info { background-image: url(../images/22x22/info.png); } +.icon22-comment { background-image: url(../images/22x22/comment.png); } +.icon22-package { background-image: url(../images/22x22/package.png); } +.icon22-settings { background-image: url(../images/22x22/settings.png); } +.icon22-plugin { background-image: url(../images/22x22/plugin.png); } + +img.gravatar { + padding: 2px; + border: solid 1px #d5d5d5; + background: #fff; +} + +div.issue img.gravatar { + float: right; + margin: 0 0 0 1em; + padding: 5px; +} + +div.issue table img.gravatar { + height: 14px; + width: 14px; + padding: 2px; + float: left; + margin: 0 0.5em 0 0; +} + +#history img.gravatar { + padding: 3px; + margin: 0 1.5em 1em 0; + float: left; +} + +td.username img.gravatar { + float: left; + margin: 0 1em 0 0; +} + +#activity dt img.gravatar { + float: left; + margin: 0 1em 1em 0; +} + +#activity dt, +.journal { + clear: left; +} + +.gravatar-margin { + margin-left: 40px; +} + +h2 img { vertical-align:middle; } + + +/***** Media print specific styles *****/ +@media print { + #top-menu, #header, #main-menu, #sidebar, #footer, .contextual, .other-formats { display:none; } + #main { background: #fff; } + #content { width: 99%; margin: 0; padding: 0; border: 0; background: #fff; overflow: visible !important;} + #wiki_add_attachment { display:none; } +} + +@import "ui-lightness/jquery-ui-1.7.2.custom.css"; diff --git a/docs/application.js b/docs/application.js new file mode 100644 index 00000000..cdd26f95 --- /dev/null +++ b/docs/application.js @@ -0,0 +1,209 @@ +/* redMine - project management software + Copyright (C) 2006-2008 Jean-Philippe Lang */ + +function checkAll (id, checked) { + var els = Element.descendants(id); + for (var i = 0; i < els.length; i++) { + if (els[i].disabled==false) { + els[i].checked = checked; + } + } +} + +function toggleCheckboxesBySelector(selector) { + boxes = $$(selector); + var all_checked = true; + for (i = 0; i < boxes.length; i++) { if (boxes[i].checked == false) { all_checked = false; } } + for (i = 0; i < boxes.length; i++) { boxes[i].checked = !all_checked; } +} + +function showAndScrollTo(id, focus) { + Element.show(id); + if (focus!=null) { Form.Element.focus(focus); } + Element.scrollTo(id); +} + +function toggleRowGroup(el) { + var tr = Element.up(el, 'tr'); + var n = Element.next(tr); + tr.toggleClassName('open'); + while (n != undefined && !n.hasClassName('group')) { + Element.toggle(n); + n = Element.next(n); + } +} + +function toggleFieldset(el) { + var fieldset = Element.up(el, 'fieldset'); + fieldset.toggleClassName('collapsed'); + Effect.toggle(fieldset.down('div'), 'slide', {duration:0.2}); +} + +var fileFieldCount = 1; + +function addFileField() { + if (fileFieldCount >= 10) return false + fileFieldCount++; + var f = document.createElement("input"); + f.type = "file"; + f.name = "attachments[" + fileFieldCount + "][file]"; + f.size = 30; + var d = document.createElement("input"); + d.type = "text"; + d.name = "attachments[" + fileFieldCount + "][description]"; + d.size = 60; + + p = document.getElementById("attachments_fields"); + p.appendChild(document.createElement("br")); + p.appendChild(f); + p.appendChild(d); +} + +function showTab(name) { + var f = $$('div#content .tab-content'); + for(var i=0; i0) { + lis[i-1].show(); + } +} + +function displayTabsButtons() { + var lis; + var tabsWidth = 0; + var i; + $$('div.tabs').each(function(el) { + lis = el.down('ul').childElements(); + for (i=0; i 0) { + Element.show('ajax-indicator'); + } + }, + onComplete: function(){ + if ($('ajax-indicator') && Ajax.activeRequestCount == 0) { + Element.hide('ajax-indicator'); + } + } +}); diff --git a/docs/attachment.png b/docs/attachment.png new file mode 100644 index 0000000000000000000000000000000000000000..b7ce3c445ec04fa8d16a939340d070752937354a GIT binary patch literal 995 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJdx@v7EBg%=9syGYj%hhlfI^%F z9+AZi3|t>Tn9*sC$qb+%OS+@4BLl<6e(pbstPBjy3;{kNu0cUT$;rvp)zw?KZasGF z*tv7(u3fu!@7}%t3=IEA!Dt8!_Yg2}%9RCWn35pBV3=PSg8eImfP#UZE{-7;w|dVz zavd<>aJg9M!}h-Z(YA|AG8YDv|5t44xc)%e!;WE6F>}W~^9F%yc@l3oabC?}owatW wV9zVYH`jy8ny#$1%Ac_MbKAL}A7jokHqT>Qc4xX;3ea!{Pgg&ebxsLQ09njHQvd(} literal 0 HcmV?d00001 diff --git a/docs/balloons.png b/docs/balloons.png new file mode 100644 index 0000000000000000000000000000000000000000..89efe42f0c95392ef03d190b3d527aac6ad2c239 GIT binary patch literal 715 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 literal 0 HcmV?d00001 diff --git a/docs/controls.js b/docs/controls.js new file mode 100644 index 00000000..e1888523 --- /dev/null +++ b/docs/controls.js @@ -0,0 +1,963 @@ +// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// (c) 2005-2008 Ivan Krstic (http://blogs.law.harvard.edu/ivan) +// (c) 2005-2008 Jon Tirsen (http://www.tirsen.com) +// Contributors: +// Richard Livsey +// Rahul Bhargava +// Rob Wills +// +// script.aculo.us is freely distributable under the terms of an MIT-style license. +// For details, see the script.aculo.us web site: http://script.aculo.us/ + +// Autocompleter.Base handles all the autocompletion functionality +// that's independent of the data source for autocompletion. This +// includes drawing the autocompletion menu, observing keyboard +// and mouse events, and similar. +// +// Specific autocompleters need to provide, at the very least, +// a getUpdatedChoices function that will be invoked every time +// the text inside the monitored textbox changes. This method +// should get the text for which to provide autocompletion by +// invoking this.getToken(), NOT by directly accessing +// this.element.value. This is to allow incremental tokenized +// autocompletion. Specific auto-completion logic (AJAX, etc) +// belongs in getUpdatedChoices. +// +// Tokenized incremental autocompletion is enabled automatically +// when an autocompleter is instantiated with the 'tokens' option +// in the options parameter, e.g.: +// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' }); +// will incrementally autocomplete with a comma as the token. +// Additionally, ',' in the above example can be replaced with +// a token array, e.g. { tokens: [',', '\n'] } which +// enables autocompletion on multiple tokens. This is most +// useful when one of the tokens is \n (a newline), as it +// allows smart autocompletion after linebreaks. + +if(typeof Effect == 'undefined') + throw("controls.js requires including script.aculo.us' effects.js library"); + +var Autocompleter = { }; +Autocompleter.Base = Class.create({ + baseInitialize: function(element, update, options) { + element = $(element); + this.element = element; + this.update = $(update); + this.hasFocus = false; + this.changed = false; + this.active = false; + this.index = 0; + this.entryCount = 0; + this.oldElementValue = this.element.value; + + if(this.setOptions) + this.setOptions(options); + else + this.options = options || { }; + + this.options.paramName = this.options.paramName || this.element.name; + this.options.tokens = this.options.tokens || []; + this.options.frequency = this.options.frequency || 0.4; + this.options.minChars = this.options.minChars || 1; + this.options.onShow = this.options.onShow || + function(element, update){ + if(!update.style.position || update.style.position=='absolute') { + update.style.position = 'absolute'; + Position.clone(element, update, { + setHeight: false, + offsetTop: element.offsetHeight + }); + } + Effect.Appear(update,{duration:0.15}); + }; + this.options.onHide = this.options.onHide || + function(element, update){ new Effect.Fade(update,{duration:0.15}) }; + + if(typeof(this.options.tokens) == 'string') + this.options.tokens = new Array(this.options.tokens); + // Force carriage returns as token delimiters anyway + if (!this.options.tokens.include('\n')) + this.options.tokens.push('\n'); + + this.observer = null; + + this.element.setAttribute('autocomplete','off'); + + Element.hide(this.update); + + Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this)); + Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this)); + }, + + show: function() { + if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update); + if(!this.iefix && + (Prototype.Browser.IE) && + (Element.getStyle(this.update, 'position')=='absolute')) { + new Insertion.After(this.update, + ''); + this.iefix = $(this.update.id+'_iefix'); + } + if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50); + }, + + fixIEOverlapping: function() { + Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)}); + this.iefix.style.zIndex = 1; + this.update.style.zIndex = 2; + Element.show(this.iefix); + }, + + hide: function() { + this.stopIndicator(); + if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update); + if(this.iefix) Element.hide(this.iefix); + }, + + startIndicator: function() { + if(this.options.indicator) Element.show(this.options.indicator); + }, + + stopIndicator: function() { + if(this.options.indicator) Element.hide(this.options.indicator); + }, + + onKeyPress: function(event) { + if(this.active) + switch(event.keyCode) { + case Event.KEY_TAB: + case Event.KEY_RETURN: + this.selectEntry(); + Event.stop(event); + case Event.KEY_ESC: + this.hide(); + this.active = false; + Event.stop(event); + return; + case Event.KEY_LEFT: + case Event.KEY_RIGHT: + return; + case Event.KEY_UP: + this.markPrevious(); + this.render(); + Event.stop(event); + return; + case Event.KEY_DOWN: + this.markNext(); + this.render(); + Event.stop(event); + return; + } + else + if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || + (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return; + + this.changed = true; + this.hasFocus = true; + + if(this.observer) clearTimeout(this.observer); + this.observer = + setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000); + }, + + activate: function() { + this.changed = false; + this.hasFocus = true; + this.getUpdatedChoices(); + }, + + onHover: function(event) { + var element = Event.findElement(event, 'LI'); + if(this.index != element.autocompleteIndex) + { + this.index = element.autocompleteIndex; + this.render(); + } + Event.stop(event); + }, + + onClick: function(event) { + var element = Event.findElement(event, 'LI'); + this.index = element.autocompleteIndex; + this.selectEntry(); + this.hide(); + }, + + onBlur: function(event) { + // needed to make click events working + setTimeout(this.hide.bind(this), 250); + this.hasFocus = false; + this.active = false; + }, + + render: function() { + if(this.entryCount > 0) { + for (var i = 0; i < this.entryCount; i++) + this.index==i ? + Element.addClassName(this.getEntry(i),"selected") : + Element.removeClassName(this.getEntry(i),"selected"); + if(this.hasFocus) { + this.show(); + this.active = true; + } + } else { + this.active = false; + this.hide(); + } + }, + + markPrevious: function() { + if(this.index > 0) this.index--; + else this.index = this.entryCount-1; + this.getEntry(this.index).scrollIntoView(true); + }, + + markNext: function() { + if(this.index < this.entryCount-1) this.index++; + else this.index = 0; + this.getEntry(this.index).scrollIntoView(false); + }, + + getEntry: function(index) { + return this.update.firstChild.childNodes[index]; + }, + + getCurrentEntry: function() { + return this.getEntry(this.index); + }, + + selectEntry: function() { + this.active = false; + this.updateElement(this.getCurrentEntry()); + }, + + updateElement: function(selectedElement) { + if (this.options.updateElement) { + this.options.updateElement(selectedElement); + return; + } + var value = ''; + if (this.options.select) { + var nodes = $(selectedElement).select('.' + this.options.select) || []; + if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select); + } else + value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal'); + + var bounds = this.getTokenBounds(); + if (bounds[0] != -1) { + var newValue = this.element.value.substr(0, bounds[0]); + var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/); + if (whitespace) + newValue += whitespace[0]; + this.element.value = newValue + value + this.element.value.substr(bounds[1]); + } else { + this.element.value = value; + } + this.oldElementValue = this.element.value; + this.element.focus(); + + if (this.options.afterUpdateElement) + this.options.afterUpdateElement(this.element, selectedElement); + }, + + updateChoices: function(choices) { + if(!this.changed && this.hasFocus) { + this.update.innerHTML = choices; + Element.cleanWhitespace(this.update); + Element.cleanWhitespace(this.update.down()); + + if(this.update.firstChild && this.update.down().childNodes) { + this.entryCount = + this.update.down().childNodes.length; + for (var i = 0; i < this.entryCount; i++) { + var entry = this.getEntry(i); + entry.autocompleteIndex = i; + this.addObservers(entry); + } + } else { + this.entryCount = 0; + } + + this.stopIndicator(); + this.index = 0; + + if(this.entryCount==1 && this.options.autoSelect) { + this.selectEntry(); + this.hide(); + } else { + this.render(); + } + } + }, + + addObservers: function(element) { + Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this)); + Event.observe(element, "click", this.onClick.bindAsEventListener(this)); + }, + + onObserverEvent: function() { + this.changed = false; + this.tokenBounds = null; + if(this.getToken().length>=this.options.minChars) { + this.getUpdatedChoices(); + } else { + this.active = false; + this.hide(); + } + this.oldElementValue = this.element.value; + }, + + getToken: function() { + var bounds = this.getTokenBounds(); + return this.element.value.substring(bounds[0], bounds[1]).strip(); + }, + + getTokenBounds: function() { + if (null != this.tokenBounds) return this.tokenBounds; + var value = this.element.value; + if (value.strip().empty()) return [-1, 0]; + var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue); + var offset = (diff == this.oldElementValue.length ? 1 : 0); + var prevTokenPos = -1, nextTokenPos = value.length; + var tp; + for (var index = 0, l = this.options.tokens.length; index < l; ++index) { + tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1); + if (tp > prevTokenPos) prevTokenPos = tp; + tp = value.indexOf(this.options.tokens[index], diff + offset); + if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp; + } + return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]); + } +}); + +Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) { + var boundary = Math.min(newS.length, oldS.length); + for (var index = 0; index < boundary; ++index) + if (newS[index] != oldS[index]) + return index; + return boundary; +}; + +Ajax.Autocompleter = Class.create(Autocompleter.Base, { + initialize: function(element, update, url, options) { + this.baseInitialize(element, update, options); + this.options.asynchronous = true; + this.options.onComplete = this.onComplete.bind(this); + this.options.defaultParams = this.options.parameters || null; + this.url = url; + }, + + getUpdatedChoices: function() { + this.startIndicator(); + + var entry = encodeURIComponent(this.options.paramName) + '=' + + encodeURIComponent(this.getToken()); + + this.options.parameters = this.options.callback ? + this.options.callback(this.element, entry) : entry; + + if(this.options.defaultParams) + this.options.parameters += '&' + this.options.defaultParams; + + new Ajax.Request(this.url, this.options); + }, + + onComplete: function(request) { + this.updateChoices(request.responseText); + } +}); + +// The local array autocompleter. Used when you'd prefer to +// inject an array of autocompletion options into the page, rather +// than sending out Ajax queries, which can be quite slow sometimes. +// +// The constructor takes four parameters. The first two are, as usual, +// the id of the monitored textbox, and id of the autocompletion menu. +// The third is the array you want to autocomplete from, and the fourth +// is the options block. +// +// Extra local autocompletion options: +// - choices - How many autocompletion choices to offer +// +// - partialSearch - If false, the autocompleter will match entered +// text only at the beginning of strings in the +// autocomplete array. Defaults to true, which will +// match text at the beginning of any *word* in the +// strings in the autocomplete array. If you want to +// search anywhere in the string, additionally set +// the option fullSearch to true (default: off). +// +// - fullSsearch - Search anywhere in autocomplete array strings. +// +// - partialChars - How many characters to enter before triggering +// a partial match (unlike minChars, which defines +// how many characters are required to do any match +// at all). Defaults to 2. +// +// - ignoreCase - Whether to ignore case when autocompleting. +// Defaults to true. +// +// It's possible to pass in a custom function as the 'selector' +// option, if you prefer to write your own autocompletion logic. +// In that case, the other options above will not apply unless +// you support them. + +Autocompleter.Local = Class.create(Autocompleter.Base, { + initialize: function(element, update, array, options) { + this.baseInitialize(element, update, options); + this.options.array = array; + }, + + getUpdatedChoices: function() { + this.updateChoices(this.options.selector(this)); + }, + + setOptions: function(options) { + this.options = Object.extend({ + choices: 10, + partialSearch: true, + partialChars: 2, + ignoreCase: true, + fullSearch: false, + selector: function(instance) { + var ret = []; // Beginning matches + var partial = []; // Inside matches + var entry = instance.getToken(); + var count = 0; + + for (var i = 0; i < instance.options.array.length && + ret.length < instance.options.choices ; i++) { + + var elem = instance.options.array[i]; + var foundPos = instance.options.ignoreCase ? + elem.toLowerCase().indexOf(entry.toLowerCase()) : + elem.indexOf(entry); + + while (foundPos != -1) { + if (foundPos == 0 && elem.length != entry.length) { + ret.push("
  • " + elem.substr(0, entry.length) + "" + + elem.substr(entry.length) + "
  • "); + break; + } else if (entry.length >= instance.options.partialChars && + instance.options.partialSearch && foundPos != -1) { + if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) { + partial.push("
  • " + elem.substr(0, foundPos) + "" + + elem.substr(foundPos, entry.length) + "" + elem.substr( + foundPos + entry.length) + "
  • "); + break; + } + } + + foundPos = instance.options.ignoreCase ? + elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : + elem.indexOf(entry, foundPos + 1); + + } + } + if (partial.length) + ret = ret.concat(partial.slice(0, instance.options.choices - ret.length)); + return "
      " + ret.join('') + "
    "; + } + }, options || { }); + } +}); + +// AJAX in-place editor and collection editor +// Full rewrite by Christophe Porteneuve (April 2007). + +// Use this if you notice weird scrolling problems on some browsers, +// the DOM might be a bit confused when this gets called so do this +// waits 1 ms (with setTimeout) until it does the activation +Field.scrollFreeActivate = function(field) { + setTimeout(function() { + Field.activate(field); + }, 1); +}; + +Ajax.InPlaceEditor = Class.create({ + initialize: function(element, url, options) { + this.url = url; + this.element = element = $(element); + this.prepareOptions(); + this._controls = { }; + arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!! + Object.extend(this.options, options || { }); + if (!this.options.formId && this.element.id) { + this.options.formId = this.element.id + '-inplaceeditor'; + if ($(this.options.formId)) + this.options.formId = ''; + } + if (this.options.externalControl) + this.options.externalControl = $(this.options.externalControl); + if (!this.options.externalControl) + this.options.externalControlOnly = false; + this._originalBackground = this.element.getStyle('background-color') || 'transparent'; + this.element.title = this.options.clickToEditText; + this._boundCancelHandler = this.handleFormCancellation.bind(this); + this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this); + this._boundFailureHandler = this.handleAJAXFailure.bind(this); + this._boundSubmitHandler = this.handleFormSubmission.bind(this); + this._boundWrapperHandler = this.wrapUp.bind(this); + this.registerListeners(); + }, + checkForEscapeOrReturn: function(e) { + if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return; + if (Event.KEY_ESC == e.keyCode) + this.handleFormCancellation(e); + else if (Event.KEY_RETURN == e.keyCode) + this.handleFormSubmission(e); + }, + createControl: function(mode, handler, extraClasses) { + var control = this.options[mode + 'Control']; + var text = this.options[mode + 'Text']; + if ('button' == control) { + var btn = document.createElement('input'); + btn.type = 'submit'; + btn.value = text; + btn.className = 'editor_' + mode + '_button'; + if ('cancel' == mode) + btn.onclick = this._boundCancelHandler; + this._form.appendChild(btn); + this._controls[mode] = btn; + } else if ('link' == control) { + var link = document.createElement('a'); + link.href = '#'; + link.appendChild(document.createTextNode(text)); + link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler; + link.className = 'editor_' + mode + '_link'; + if (extraClasses) + link.className += ' ' + extraClasses; + this._form.appendChild(link); + this._controls[mode] = link; + } + }, + createEditField: function() { + var text = (this.options.loadTextURL ? this.options.loadingText : this.getText()); + var fld; + if (1 >= this.options.rows && !/\r|\n/.test(this.getText())) { + fld = document.createElement('input'); + fld.type = 'text'; + var size = this.options.size || this.options.cols || 0; + if (0 < size) fld.size = size; + } else { + fld = document.createElement('textarea'); + fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.options.rows); + fld.cols = this.options.cols || 40; + } + fld.name = this.options.paramName; + fld.value = text; // No HTML breaks conversion anymore + fld.className = 'editor_field'; + if (this.options.submitOnBlur) + fld.onblur = this._boundSubmitHandler; + this._controls.editor = fld; + if (this.options.loadTextURL) + this.loadExternalText(); + this._form.appendChild(this._controls.editor); + }, + createForm: function() { + var ipe = this; + function addText(mode, condition) { + var text = ipe.options['text' + mode + 'Controls']; + if (!text || condition === false) return; + ipe._form.appendChild(document.createTextNode(text)); + }; + this._form = $(document.createElement('form')); + this._form.id = this.options.formId; + this._form.addClassName(this.options.formClassName); + this._form.onsubmit = this._boundSubmitHandler; + this.createEditField(); + if ('textarea' == this._controls.editor.tagName.toLowerCase()) + this._form.appendChild(document.createElement('br')); + if (this.options.onFormCustomization) + this.options.onFormCustomization(this, this._form); + addText('Before', this.options.okControl || this.options.cancelControl); + this.createControl('ok', this._boundSubmitHandler); + addText('Between', this.options.okControl && this.options.cancelControl); + this.createControl('cancel', this._boundCancelHandler, 'editor_cancel'); + addText('After', this.options.okControl || this.options.cancelControl); + }, + destroy: function() { + if (this._oldInnerHTML) + this.element.innerHTML = this._oldInnerHTML; + this.leaveEditMode(); + this.unregisterListeners(); + }, + enterEditMode: function(e) { + if (this._saving || this._editing) return; + this._editing = true; + this.triggerCallback('onEnterEditMode'); + if (this.options.externalControl) + this.options.externalControl.hide(); + this.element.hide(); + this.createForm(); + this.element.parentNode.insertBefore(this._form, this.element); + if (!this.options.loadTextURL) + this.postProcessEditField(); + if (e) Event.stop(e); + }, + enterHover: function(e) { + if (this.options.hoverClassName) + this.element.addClassName(this.options.hoverClassName); + if (this._saving) return; + this.triggerCallback('onEnterHover'); + }, + getText: function() { + return this.element.innerHTML.unescapeHTML(); + }, + handleAJAXFailure: function(transport) { + this.triggerCallback('onFailure', transport); + if (this._oldInnerHTML) { + this.element.innerHTML = this._oldInnerHTML; + this._oldInnerHTML = null; + } + }, + handleFormCancellation: function(e) { + this.wrapUp(); + if (e) Event.stop(e); + }, + handleFormSubmission: function(e) { + var form = this._form; + var value = $F(this._controls.editor); + this.prepareSubmission(); + var params = this.options.callback(form, value) || ''; + if (Object.isString(params)) + params = params.toQueryParams(); + params.editorId = this.element.id; + if (this.options.htmlResponse) { + var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions); + Object.extend(options, { + parameters: params, + onComplete: this._boundWrapperHandler, + onFailure: this._boundFailureHandler + }); + new Ajax.Updater({ success: this.element }, this.url, options); + } else { + var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); + Object.extend(options, { + parameters: params, + onComplete: this._boundWrapperHandler, + onFailure: this._boundFailureHandler + }); + new Ajax.Request(this.url, options); + } + if (e) Event.stop(e); + }, + leaveEditMode: function() { + this.element.removeClassName(this.options.savingClassName); + this.removeForm(); + this.leaveHover(); + this.element.style.backgroundColor = this._originalBackground; + this.element.show(); + if (this.options.externalControl) + this.options.externalControl.show(); + this._saving = false; + this._editing = false; + this._oldInnerHTML = null; + this.triggerCallback('onLeaveEditMode'); + }, + leaveHover: function(e) { + if (this.options.hoverClassName) + this.element.removeClassName(this.options.hoverClassName); + if (this._saving) return; + this.triggerCallback('onLeaveHover'); + }, + loadExternalText: function() { + this._form.addClassName(this.options.loadingClassName); + this._controls.editor.disabled = true; + var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); + Object.extend(options, { + parameters: 'editorId=' + encodeURIComponent(this.element.id), + onComplete: Prototype.emptyFunction, + onSuccess: function(transport) { + this._form.removeClassName(this.options.loadingClassName); + var text = transport.responseText; + if (this.options.stripLoadedTextTags) + text = text.stripTags(); + this._controls.editor.value = text; + this._controls.editor.disabled = false; + this.postProcessEditField(); + }.bind(this), + onFailure: this._boundFailureHandler + }); + new Ajax.Request(this.options.loadTextURL, options); + }, + postProcessEditField: function() { + var fpc = this.options.fieldPostCreation; + if (fpc) + $(this._controls.editor)['focus' == fpc ? 'focus' : 'activate'](); + }, + prepareOptions: function() { + this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions); + Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks); + [this._extraDefaultOptions].flatten().compact().each(function(defs) { + Object.extend(this.options, defs); + }.bind(this)); + }, + prepareSubmission: function() { + this._saving = true; + this.removeForm(); + this.leaveHover(); + this.showSaving(); + }, + registerListeners: function() { + this._listeners = { }; + var listener; + $H(Ajax.InPlaceEditor.Listeners).each(function(pair) { + listener = this[pair.value].bind(this); + this._listeners[pair.key] = listener; + if (!this.options.externalControlOnly) + this.element.observe(pair.key, listener); + if (this.options.externalControl) + this.options.externalControl.observe(pair.key, listener); + }.bind(this)); + }, + removeForm: function() { + if (!this._form) return; + this._form.remove(); + this._form = null; + this._controls = { }; + }, + showSaving: function() { + this._oldInnerHTML = this.element.innerHTML; + this.element.innerHTML = this.options.savingText; + this.element.addClassName(this.options.savingClassName); + this.element.style.backgroundColor = this._originalBackground; + this.element.show(); + }, + triggerCallback: function(cbName, arg) { + if ('function' == typeof this.options[cbName]) { + this.options[cbName](this, arg); + } + }, + unregisterListeners: function() { + $H(this._listeners).each(function(pair) { + if (!this.options.externalControlOnly) + this.element.stopObserving(pair.key, pair.value); + if (this.options.externalControl) + this.options.externalControl.stopObserving(pair.key, pair.value); + }.bind(this)); + }, + wrapUp: function(transport) { + this.leaveEditMode(); + // Can't use triggerCallback due to backward compatibility: requires + // binding + direct element + this._boundComplete(transport, this.element); + } +}); + +Object.extend(Ajax.InPlaceEditor.prototype, { + dispose: Ajax.InPlaceEditor.prototype.destroy +}); + +Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, { + initialize: function($super, element, url, options) { + this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions; + $super(element, url, options); + }, + + createEditField: function() { + var list = document.createElement('select'); + list.name = this.options.paramName; + list.size = 1; + this._controls.editor = list; + this._collection = this.options.collection || []; + if (this.options.loadCollectionURL) + this.loadCollection(); + else + this.checkForExternalText(); + this._form.appendChild(this._controls.editor); + }, + + loadCollection: function() { + this._form.addClassName(this.options.loadingClassName); + this.showLoadingText(this.options.loadingCollectionText); + var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); + Object.extend(options, { + parameters: 'editorId=' + encodeURIComponent(this.element.id), + onComplete: Prototype.emptyFunction, + onSuccess: function(transport) { + var js = transport.responseText.strip(); + if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check + throw('Server returned an invalid collection representation.'); + this._collection = eval(js); + this.checkForExternalText(); + }.bind(this), + onFailure: this.onFailure + }); + new Ajax.Request(this.options.loadCollectionURL, options); + }, + + showLoadingText: function(text) { + this._controls.editor.disabled = true; + var tempOption = this._controls.editor.firstChild; + if (!tempOption) { + tempOption = document.createElement('option'); + tempOption.value = ''; + this._controls.editor.appendChild(tempOption); + tempOption.selected = true; + } + tempOption.update((text || '').stripScripts().stripTags()); + }, + + checkForExternalText: function() { + this._text = this.getText(); + if (this.options.loadTextURL) + this.loadExternalText(); + else + this.buildOptionList(); + }, + + loadExternalText: function() { + this.showLoadingText(this.options.loadingText); + var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); + Object.extend(options, { + parameters: 'editorId=' + encodeURIComponent(this.element.id), + onComplete: Prototype.emptyFunction, + onSuccess: function(transport) { + this._text = transport.responseText.strip(); + this.buildOptionList(); + }.bind(this), + onFailure: this.onFailure + }); + new Ajax.Request(this.options.loadTextURL, options); + }, + + buildOptionList: function() { + this._form.removeClassName(this.options.loadingClassName); + this._collection = this._collection.map(function(entry) { + return 2 === entry.length ? entry : [entry, entry].flatten(); + }); + var marker = ('value' in this.options) ? this.options.value : this._text; + var textFound = this._collection.any(function(entry) { + return entry[0] == marker; + }.bind(this)); + this._controls.editor.update(''); + var option; + this._collection.each(function(entry, index) { + option = document.createElement('option'); + option.value = entry[0]; + option.selected = textFound ? entry[0] == marker : 0 == index; + option.appendChild(document.createTextNode(entry[1])); + this._controls.editor.appendChild(option); + }.bind(this)); + this._controls.editor.disabled = false; + Field.scrollFreeActivate(this._controls.editor); + } +}); + +//**** DEPRECATION LAYER FOR InPlace[Collection]Editor! **** +//**** This only exists for a while, in order to let **** +//**** users adapt to the new API. Read up on the new **** +//**** API and convert your code to it ASAP! **** + +Ajax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) { + if (!options) return; + function fallback(name, expr) { + if (name in options || expr === undefined) return; + options[name] = expr; + }; + fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' : + options.cancelLink == options.cancelButton == false ? false : undefined))); + fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' : + options.okLink == options.okButton == false ? false : undefined))); + fallback('highlightColor', options.highlightcolor); + fallback('highlightEndColor', options.highlightendcolor); +}; + +Object.extend(Ajax.InPlaceEditor, { + DefaultOptions: { + ajaxOptions: { }, + autoRows: 3, // Use when multi-line w/ rows == 1 + cancelControl: 'link', // 'link'|'button'|false + cancelText: 'cancel', + clickToEditText: 'Click to edit', + externalControl: null, // id|elt + externalControlOnly: false, + fieldPostCreation: 'activate', // 'activate'|'focus'|false + formClassName: 'inplaceeditor-form', + formId: null, // id|elt + highlightColor: '#ffff99', + highlightEndColor: '#ffffff', + hoverClassName: '', + htmlResponse: true, + loadingClassName: 'inplaceeditor-loading', + loadingText: 'Loading...', + okControl: 'button', // 'link'|'button'|false + okText: 'ok', + paramName: 'value', + rows: 1, // If 1 and multi-line, uses autoRows + savingClassName: 'inplaceeditor-saving', + savingText: 'Saving...', + size: 0, + stripLoadedTextTags: false, + submitOnBlur: false, + textAfterControls: '', + textBeforeControls: '', + textBetweenControls: '' + }, + DefaultCallbacks: { + callback: function(form) { + return Form.serialize(form); + }, + onComplete: function(transport, element) { + // For backward compatibility, this one is bound to the IPE, and passes + // the element directly. It was too often customized, so we don't break it. + new Effect.Highlight(element, { + startcolor: this.options.highlightColor, keepBackgroundImage: true }); + }, + onEnterEditMode: null, + onEnterHover: function(ipe) { + ipe.element.style.backgroundColor = ipe.options.highlightColor; + if (ipe._effect) + ipe._effect.cancel(); + }, + onFailure: function(transport, ipe) { + alert('Error communication with the server: ' + transport.responseText.stripTags()); + }, + onFormCustomization: null, // Takes the IPE and its generated form, after editor, before controls. + onLeaveEditMode: null, + onLeaveHover: function(ipe) { + ipe._effect = new Effect.Highlight(ipe.element, { + startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor, + restorecolor: ipe._originalBackground, keepBackgroundImage: true + }); + } + }, + Listeners: { + click: 'enterEditMode', + keydown: 'checkForEscapeOrReturn', + mouseover: 'enterHover', + mouseout: 'leaveHover' + } +}); + +Ajax.InPlaceCollectionEditor.DefaultOptions = { + loadingCollectionText: 'Loading options...' +}; + +// Delayed observer, like Form.Element.Observer, +// but waits for delay after last key input +// Ideal for live-search fields + +Form.Element.DelayedObserver = Class.create({ + initialize: function(element, delay, callback) { + this.delay = delay || 0.5; + this.element = $(element); + this.callback = callback; + this.timer = null; + this.lastValue = $F(this.element); + Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this)); + }, + delayedListener: function(event) { + if(this.lastValue == $F(this.element)) return; + if(this.timer) clearTimeout(this.timer); + this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000); + this.lastValue = $F(this.element); + }, + onTimerEvent: function() { + this.timer = null; + this.callback(this.element, $F(this.element)); + } +}); diff --git a/docs/document-horizontal-text.png b/docs/document-horizontal-text.png new file mode 100644 index 0000000000000000000000000000000000000000..f1a100aa716caf4a4e7459aa0f2884cea60a4347 GIT binary patch literal 529 zcmV+s0`C2ZP)0jAaF?7VGRfqdo89^L zo10w`h9Mkd5{@)=)9+s+o*h{C=KKDa?|GmwIDZ}tMumk9V^gK^WAwWtA zj0sO^8e~!_(3Jv(>jHC@Wx>OE485zX#}CuIRd|{tE$^lY#bS{+P&*Va77M6Ws~}PS zX{7>y>bl)7w__LvyiTXfJpnQ|n@wIQm&=SqLUDlY<XgC$BRX>)c#|t7F z9}$TN>8=O3naYy~(AcR~oU~eZGMBsf&pQYg{42l!LT(ve Tk~Lv$00000NkvXXu0mjfj8fj! literal 0 HcmV?d00001 diff --git a/docs/document-text-image.png b/docs/document-text-image.png new file mode 100644 index 0000000000000000000000000000000000000000..56c4f0bfc7e23fd06f37b63b8f613354d3bc44a5 GIT binary patch literal 647 zcmV;20(kw2P)^@RCwBqQcY_UQ4~G%WeggUSv2*7 zfFTh}Vpi>CQjUUH^nF6c>U!5!-bkJJ*5~ltLC=wc}1`XaWkQ)MOeb$&^g; z;=R|*WP~a{xbxn;_s+TJ-21fW>+9g5Y1%WIZsOnLlTxao#$!w-6MU`LVe*0p&9`(q8lb1rEW&}Ee>jwuS zJCn&^Fc@H=SQL_KwJQ6mR0;tGhMNC39Nmu=g|Rm=KAi7d(P%mN+6Ei{`= z$=+_ah2(fz$aFdl-}h0eR6v+Z3B+36I`2{ zi@Nan@KF4^t}8P?AnpJYa1HX=Y-FK-b_TPtw};&FGLp7!^5&Dp#VE5Vp>Bl#p#pm1 zrcVjJ?eBx7Bx#ZDx^5y?P3oyG1vF^pFzI=lrU`>)zRMWKa@Hh8_UGy8`So)775-x= h-#=#zm*bZJ0|19sO*yhc-QxfN002ovPDHLkV1oGJG#&r| literal 0 HcmV?d00001 diff --git a/docs/document-zipper.png b/docs/document-zipper.png new file mode 100644 index 0000000000000000000000000000000000000000..6d6333afc059e4866b2a6191662ab1c599d23292 GIT binary patch literal 646 zcmV;10(t$3P)t8dlN(uy!IdDAQ~IVACN-5YVedo&+Ue&XwUA!f>?-dmFjsWsHoEo9RXvhUZ}r zKx>WDqa!%{!a?KMPN}r@hZcK;Pt__`mX^?LHt~9K5uWE^$93QGUWD(=c|J1(#cB~O zQb*A12{M@s(&@AawA*cTIvsp0m$xYI6>*;#ODZ681dF8=JrlMqi!2b8hgPeF_q)5u zPfdL|IX+(aMvfQ`?{37HtV9Mw0*+G1=kro`DwV=#&--a|9Dg8SS@#5LwVI3%90%}y z_*5bXajZcJv=E_R0)uORIX5Rsj}i$HVJEx^=Ng{FkD(GM3A7V8vAVe_qv;8U{ZBv* zN{9&<2c&`**{n>~>-C|=+BEwA5s)58AL!%j;h~UM*4Gh6cZEf45+_cq2hq6``VLqm zo6Dh4D4;?DrY{UBfpQIRxCAECZk)Kkwua76TYU#K=IGqn z!MoK}X>8mD=Q4-6>ipmUt*a|^Z*LuQ>%>8$Ar8uw3e4#kan5B9b2*ZPCr_uR%Q_a+ zIPJQwE*6WLelATvnf~qj`YsGL=Q4-6Eclo(mYkTFRmtQCVsUqNWPZuh2p gaq)#-fBY3-0Fu)?c2Bz)mjD0&07*qoM6N<$f{v~nnE(I) literal 0 HcmV?d00001 diff --git a/docs/documents-text.png b/docs/documents-text.png new file mode 100644 index 0000000000000000000000000000000000000000..7537994da49e10f1c253edeb4eafe2e434f04a33 GIT binary patch literal 675 zcmV;U0$lxxP)r&{-g-Dm~%%&|BQV=bagru9IG(k!sY9?t+ z(oQBb<9Tl~woIiL?%eq}=ef^0+>!2^COw#&BMjg7#VSeCGhQa?|6|8>-S3|3ig*=( zU~nSwN8H|ezr4I|_xsfIJaYJ*$HB#7k=|`?zC5|Wh9s|5yk9z0l z)ai7n-EPx|m6dh={+L7WB3^;PP#Ja1^8GBknMuQVSU5Z+MNtGu8y%(6!2#`+%QQ7U z{`BM4*3VzHT3+>dr|Xn|)M}JYr6?MS(3AOjLX4(ql-u8LB2o1QHD4$L;y5A@$}bic z=xr`Xt&$h|trrzN17Xa230KQbKRWb~N>h(GuaSs5)%x1Hs zax-rxlj2$7&73em3K?MahY%=tyIqlpt&n*IfD$T|is%6hV1i^M-kF#nJsK5fgkNGe za%_75Z~_Cqq`7g}H87ae^?%A(QHBo)&!%OK{4SL)0fq(uzydp`*dkDVy}J78)zZ?} z;KX{}ZtJYkm{OZ3C!~#y34=#7GvWXN;-Y^1TtnrEqI~&NF0X}7{+W7|&&O|@=1A}} zAs7Gv1#GC??sRM<@N8Trs5}kp__(D{PrspS29$Y8gaLmA7yxViVQW_88$tj8002ov JPDHLkV1lk@IJp1- literal 0 HcmV?d00001 diff --git a/docs/dotproduct-small.jpg b/docs/dotproduct-small.jpg new file mode 100644 index 0000000000000000000000000000000000000000..815c0198efaac50c6c31f498ac0854b84555efc8 GIT binary patch literal 26565 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 Kl!YdH*Z0) + drop = Droppables.findDeepestChild(affected); + + if(this.last_active && this.last_active != drop) this.deactivate(this.last_active); + if (drop) { + Position.within(drop.element, point[0], point[1]); + if(drop.onHover) + drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element)); + + if (drop != this.last_active) Droppables.activate(drop); + } + }, + + fire: function(event, element) { + if(!this.last_active) return; + Position.prepare(); + + if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active)) + if (this.last_active.onDrop) { + this.last_active.onDrop(element, this.last_active.element, event); + return true; + } + }, + + reset: function() { + if(this.last_active) + this.deactivate(this.last_active); + } +}; + +var Draggables = { + drags: [], + observers: [], + + register: function(draggable) { + if(this.drags.length == 0) { + this.eventMouseUp = this.endDrag.bindAsEventListener(this); + this.eventMouseMove = this.updateDrag.bindAsEventListener(this); + this.eventKeypress = this.keyPress.bindAsEventListener(this); + + Event.observe(document, "mouseup", this.eventMouseUp); + Event.observe(document, "mousemove", this.eventMouseMove); + Event.observe(document, "keypress", this.eventKeypress); + } + this.drags.push(draggable); + }, + + unregister: function(draggable) { + this.drags = this.drags.reject(function(d) { return d==draggable }); + if(this.drags.length == 0) { + Event.stopObserving(document, "mouseup", this.eventMouseUp); + Event.stopObserving(document, "mousemove", this.eventMouseMove); + Event.stopObserving(document, "keypress", this.eventKeypress); + } + }, + + activate: function(draggable) { + if(draggable.options.delay) { + this._timeout = setTimeout(function() { + Draggables._timeout = null; + window.focus(); + Draggables.activeDraggable = draggable; + }.bind(this), draggable.options.delay); + } else { + window.focus(); // allows keypress events if window isn't currently focused, fails for Safari + this.activeDraggable = draggable; + } + }, + + deactivate: function() { + this.activeDraggable = null; + }, + + updateDrag: function(event) { + if(!this.activeDraggable) return; + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + // Mozilla-based browsers fire successive mousemove events with + // the same coordinates, prevent needless redrawing (moz bug?) + if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return; + this._lastPointer = pointer; + + this.activeDraggable.updateDrag(event, pointer); + }, + + endDrag: function(event) { + if(this._timeout) { + clearTimeout(this._timeout); + this._timeout = null; + } + if(!this.activeDraggable) return; + this._lastPointer = null; + this.activeDraggable.endDrag(event); + this.activeDraggable = null; + }, + + keyPress: function(event) { + if(this.activeDraggable) + this.activeDraggable.keyPress(event); + }, + + addObserver: function(observer) { + this.observers.push(observer); + this._cacheObserverCallbacks(); + }, + + removeObserver: function(element) { // element instead of observer fixes mem leaks + this.observers = this.observers.reject( function(o) { return o.element==element }); + this._cacheObserverCallbacks(); + }, + + notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag' + if(this[eventName+'Count'] > 0) + this.observers.each( function(o) { + if(o[eventName]) o[eventName](eventName, draggable, event); + }); + if(draggable.options[eventName]) draggable.options[eventName](draggable, event); + }, + + _cacheObserverCallbacks: function() { + ['onStart','onEnd','onDrag'].each( function(eventName) { + Draggables[eventName+'Count'] = Draggables.observers.select( + function(o) { return o[eventName]; } + ).length; + }); + } +}; + +/*--------------------------------------------------------------------------*/ + +var Draggable = Class.create({ + initialize: function(element) { + var defaults = { + handle: false, + reverteffect: function(element, top_offset, left_offset) { + var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02; + new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur, + queue: {scope:'_draggable', position:'end'} + }); + }, + endeffect: function(element) { + var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0; + new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity, + queue: {scope:'_draggable', position:'end'}, + afterFinish: function(){ + Draggable._dragging[element] = false + } + }); + }, + zindex: 1000, + revert: false, + quiet: false, + scroll: false, + scrollSensitivity: 20, + scrollSpeed: 15, + snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] } + delay: 0 + }; + + if(!arguments[1] || Object.isUndefined(arguments[1].endeffect)) + Object.extend(defaults, { + starteffect: function(element) { + element._opacity = Element.getOpacity(element); + Draggable._dragging[element] = true; + new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7}); + } + }); + + var options = Object.extend(defaults, arguments[1] || { }); + + this.element = $(element); + + if(options.handle && Object.isString(options.handle)) + this.handle = this.element.down('.'+options.handle, 0); + + if(!this.handle) this.handle = $(options.handle); + if(!this.handle) this.handle = this.element; + + if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) { + options.scroll = $(options.scroll); + this._isScrollChild = Element.childOf(this.element, options.scroll); + } + + Element.makePositioned(this.element); // fix IE + + this.options = options; + this.dragging = false; + + this.eventMouseDown = this.initDrag.bindAsEventListener(this); + Event.observe(this.handle, "mousedown", this.eventMouseDown); + + Draggables.register(this); + }, + + destroy: function() { + Event.stopObserving(this.handle, "mousedown", this.eventMouseDown); + Draggables.unregister(this); + }, + + currentDelta: function() { + return([ + parseInt(Element.getStyle(this.element,'left') || '0'), + parseInt(Element.getStyle(this.element,'top') || '0')]); + }, + + initDrag: function(event) { + if(!Object.isUndefined(Draggable._dragging[this.element]) && + Draggable._dragging[this.element]) return; + if(Event.isLeftClick(event)) { + // abort on form elements, fixes a Firefox issue + var src = Event.element(event); + if((tag_name = src.tagName.toUpperCase()) && ( + tag_name=='INPUT' || + tag_name=='SELECT' || + tag_name=='OPTION' || + tag_name=='BUTTON' || + tag_name=='TEXTAREA')) return; + + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + var pos = Position.cumulativeOffset(this.element); + this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) }); + + Draggables.activate(this); + Event.stop(event); + } + }, + + startDrag: function(event) { + this.dragging = true; + if(!this.delta) + this.delta = this.currentDelta(); + + if(this.options.zindex) { + this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0); + this.element.style.zIndex = this.options.zindex; + } + + if(this.options.ghosting) { + this._clone = this.element.cloneNode(true); + this._originallyAbsolute = (this.element.getStyle('position') == 'absolute'); + if (!this._originallyAbsolute) + Position.absolutize(this.element); + this.element.parentNode.insertBefore(this._clone, this.element); + } + + if(this.options.scroll) { + if (this.options.scroll == window) { + var where = this._getWindowScroll(this.options.scroll); + this.originalScrollLeft = where.left; + this.originalScrollTop = where.top; + } else { + this.originalScrollLeft = this.options.scroll.scrollLeft; + this.originalScrollTop = this.options.scroll.scrollTop; + } + } + + Draggables.notify('onStart', this, event); + + if(this.options.starteffect) this.options.starteffect(this.element); + }, + + updateDrag: function(event, pointer) { + if(!this.dragging) this.startDrag(event); + + if(!this.options.quiet){ + Position.prepare(); + Droppables.show(pointer, this.element); + } + + Draggables.notify('onDrag', this, event); + + this.draw(pointer); + if(this.options.change) this.options.change(this); + + if(this.options.scroll) { + this.stopScrolling(); + + var p; + if (this.options.scroll == window) { + with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; } + } else { + p = Position.page(this.options.scroll); + p[0] += this.options.scroll.scrollLeft + Position.deltaX; + p[1] += this.options.scroll.scrollTop + Position.deltaY; + p.push(p[0]+this.options.scroll.offsetWidth); + p.push(p[1]+this.options.scroll.offsetHeight); + } + var speed = [0,0]; + if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity); + if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity); + if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity); + if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity); + this.startScrolling(speed); + } + + // fix AppleWebKit rendering + if(Prototype.Browser.WebKit) window.scrollBy(0,0); + + Event.stop(event); + }, + + finishDrag: function(event, success) { + this.dragging = false; + + if(this.options.quiet){ + Position.prepare(); + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + Droppables.show(pointer, this.element); + } + + if(this.options.ghosting) { + if (!this._originallyAbsolute) + Position.relativize(this.element); + delete this._originallyAbsolute; + Element.remove(this._clone); + this._clone = null; + } + + var dropped = false; + if(success) { + dropped = Droppables.fire(event, this.element); + if (!dropped) dropped = false; + } + if(dropped && this.options.onDropped) this.options.onDropped(this.element); + Draggables.notify('onEnd', this, event); + + var revert = this.options.revert; + if(revert && Object.isFunction(revert)) revert = revert(this.element); + + var d = this.currentDelta(); + if(revert && this.options.reverteffect) { + if (dropped == 0 || revert != 'failure') + this.options.reverteffect(this.element, + d[1]-this.delta[1], d[0]-this.delta[0]); + } else { + this.delta = d; + } + + if(this.options.zindex) + this.element.style.zIndex = this.originalZ; + + if(this.options.endeffect) + this.options.endeffect(this.element); + + Draggables.deactivate(this); + Droppables.reset(); + }, + + keyPress: function(event) { + if(event.keyCode!=Event.KEY_ESC) return; + this.finishDrag(event, false); + Event.stop(event); + }, + + endDrag: function(event) { + if(!this.dragging) return; + this.stopScrolling(); + this.finishDrag(event, true); + Event.stop(event); + }, + + draw: function(point) { + var pos = Position.cumulativeOffset(this.element); + if(this.options.ghosting) { + var r = Position.realOffset(this.element); + pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY; + } + + var d = this.currentDelta(); + pos[0] -= d[0]; pos[1] -= d[1]; + + if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) { + pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft; + pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop; + } + + var p = [0,1].map(function(i){ + return (point[i]-pos[i]-this.offset[i]) + }.bind(this)); + + if(this.options.snap) { + if(Object.isFunction(this.options.snap)) { + p = this.options.snap(p[0],p[1],this); + } else { + if(Object.isArray(this.options.snap)) { + p = p.map( function(v, i) { + return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this)); + } else { + p = p.map( function(v) { + return (v/this.options.snap).round()*this.options.snap }.bind(this)); + } + }} + + var style = this.element.style; + if((!this.options.constraint) || (this.options.constraint=='horizontal')) + style.left = p[0] + "px"; + if((!this.options.constraint) || (this.options.constraint=='vertical')) + style.top = p[1] + "px"; + + if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering + }, + + stopScrolling: function() { + if(this.scrollInterval) { + clearInterval(this.scrollInterval); + this.scrollInterval = null; + Draggables._lastScrollPointer = null; + } + }, + + startScrolling: function(speed) { + if(!(speed[0] || speed[1])) return; + this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed]; + this.lastScrolled = new Date(); + this.scrollInterval = setInterval(this.scroll.bind(this), 10); + }, + + scroll: function() { + var current = new Date(); + var delta = current - this.lastScrolled; + this.lastScrolled = current; + if(this.options.scroll == window) { + with (this._getWindowScroll(this.options.scroll)) { + if (this.scrollSpeed[0] || this.scrollSpeed[1]) { + var d = delta / 1000; + this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] ); + } + } + } else { + this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000; + this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000; + } + + Position.prepare(); + Droppables.show(Draggables._lastPointer, this.element); + Draggables.notify('onDrag', this); + if (this._isScrollChild) { + Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer); + Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000; + Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000; + if (Draggables._lastScrollPointer[0] < 0) + Draggables._lastScrollPointer[0] = 0; + if (Draggables._lastScrollPointer[1] < 0) + Draggables._lastScrollPointer[1] = 0; + this.draw(Draggables._lastScrollPointer); + } + + if(this.options.change) this.options.change(this); + }, + + _getWindowScroll: function(w) { + var T, L, W, H; + with (w.document) { + if (w.document.documentElement && documentElement.scrollTop) { + T = documentElement.scrollTop; + L = documentElement.scrollLeft; + } else if (w.document.body) { + T = body.scrollTop; + L = body.scrollLeft; + } + if (w.innerWidth) { + W = w.innerWidth; + H = w.innerHeight; + } else if (w.document.documentElement && documentElement.clientWidth) { + W = documentElement.clientWidth; + H = documentElement.clientHeight; + } else { + W = body.offsetWidth; + H = body.offsetHeight; + } + } + return { top: T, left: L, width: W, height: H }; + } +}); + +Draggable._dragging = { }; + +/*--------------------------------------------------------------------------*/ + +var SortableObserver = Class.create({ + initialize: function(element, observer) { + this.element = $(element); + this.observer = observer; + this.lastValue = Sortable.serialize(this.element); + }, + + onStart: function() { + this.lastValue = Sortable.serialize(this.element); + }, + + onEnd: function() { + Sortable.unmark(); + if(this.lastValue != Sortable.serialize(this.element)) + this.observer(this.element) + } +}); + +var Sortable = { + SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/, + + sortables: { }, + + _findRootElement: function(element) { + while (element.tagName.toUpperCase() != "BODY") { + if(element.id && Sortable.sortables[element.id]) return element; + element = element.parentNode; + } + }, + + options: function(element) { + element = Sortable._findRootElement($(element)); + if(!element) return; + return Sortable.sortables[element.id]; + }, + + destroy: function(element){ + element = $(element); + var s = Sortable.sortables[element.id]; + + if(s) { + Draggables.removeObserver(s.element); + s.droppables.each(function(d){ Droppables.remove(d) }); + s.draggables.invoke('destroy'); + + delete Sortable.sortables[s.element.id]; + } + }, + + create: function(element) { + element = $(element); + var options = Object.extend({ + element: element, + tag: 'li', // assumes li children, override with tag: 'tagname' + dropOnEmpty: false, + tree: false, + treeTag: 'ul', + overlap: 'vertical', // one of 'vertical', 'horizontal' + constraint: 'vertical', // one of 'vertical', 'horizontal', false + containment: element, // also takes array of elements (or id's); or false + handle: false, // or a CSS class + only: false, + delay: 0, + hoverclass: null, + ghosting: false, + quiet: false, + scroll: false, + scrollSensitivity: 20, + scrollSpeed: 15, + format: this.SERIALIZE_RULE, + + // these take arrays of elements or ids and can be + // used for better initialization performance + elements: false, + handles: false, + + onChange: Prototype.emptyFunction, + onUpdate: Prototype.emptyFunction + }, arguments[1] || { }); + + // clear any old sortable with same element + this.destroy(element); + + // build options for the draggables + var options_for_draggable = { + revert: true, + quiet: options.quiet, + scroll: options.scroll, + scrollSpeed: options.scrollSpeed, + scrollSensitivity: options.scrollSensitivity, + delay: options.delay, + ghosting: options.ghosting, + constraint: options.constraint, + handle: options.handle }; + + if(options.starteffect) + options_for_draggable.starteffect = options.starteffect; + + if(options.reverteffect) + options_for_draggable.reverteffect = options.reverteffect; + else + if(options.ghosting) options_for_draggable.reverteffect = function(element) { + element.style.top = 0; + element.style.left = 0; + }; + + if(options.endeffect) + options_for_draggable.endeffect = options.endeffect; + + if(options.zindex) + options_for_draggable.zindex = options.zindex; + + // build options for the droppables + var options_for_droppable = { + overlap: options.overlap, + containment: options.containment, + tree: options.tree, + hoverclass: options.hoverclass, + onHover: Sortable.onHover + }; + + var options_for_tree = { + onHover: Sortable.onEmptyHover, + overlap: options.overlap, + containment: options.containment, + hoverclass: options.hoverclass + }; + + // fix for gecko engine + Element.cleanWhitespace(element); + + options.draggables = []; + options.droppables = []; + + // drop on empty handling + if(options.dropOnEmpty || options.tree) { + Droppables.add(element, options_for_tree); + options.droppables.push(element); + } + + (options.elements || this.findElements(element, options) || []).each( function(e,i) { + var handle = options.handles ? $(options.handles[i]) : + (options.handle ? $(e).select('.' + options.handle)[0] : e); + options.draggables.push( + new Draggable(e, Object.extend(options_for_draggable, { handle: handle }))); + Droppables.add(e, options_for_droppable); + if(options.tree) e.treeNode = element; + options.droppables.push(e); + }); + + if(options.tree) { + (Sortable.findTreeElements(element, options) || []).each( function(e) { + Droppables.add(e, options_for_tree); + e.treeNode = element; + options.droppables.push(e); + }); + } + + // keep reference + this.sortables[element.id] = options; + + // for onupdate + Draggables.addObserver(new SortableObserver(element, options.onUpdate)); + + }, + + // return all suitable-for-sortable elements in a guaranteed order + findElements: function(element, options) { + return Element.findChildren( + element, options.only, options.tree ? true : false, options.tag); + }, + + findTreeElements: function(element, options) { + return Element.findChildren( + element, options.only, options.tree ? true : false, options.treeTag); + }, + + onHover: function(element, dropon, overlap) { + if(Element.isParent(dropon, element)) return; + + if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) { + return; + } else if(overlap>0.5) { + Sortable.mark(dropon, 'before'); + if(dropon.previousSibling != element) { + var oldParentNode = element.parentNode; + element.style.visibility = "hidden"; // fix gecko rendering + dropon.parentNode.insertBefore(element, dropon); + if(dropon.parentNode!=oldParentNode) + Sortable.options(oldParentNode).onChange(element); + Sortable.options(dropon.parentNode).onChange(element); + } + } else { + Sortable.mark(dropon, 'after'); + var nextElement = dropon.nextSibling || null; + if(nextElement != element) { + var oldParentNode = element.parentNode; + element.style.visibility = "hidden"; // fix gecko rendering + dropon.parentNode.insertBefore(element, nextElement); + if(dropon.parentNode!=oldParentNode) + Sortable.options(oldParentNode).onChange(element); + Sortable.options(dropon.parentNode).onChange(element); + } + } + }, + + onEmptyHover: function(element, dropon, overlap) { + var oldParentNode = element.parentNode; + var droponOptions = Sortable.options(dropon); + + if(!Element.isParent(dropon, element)) { + var index; + + var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only}); + var child = null; + + if(children) { + var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap); + + for (index = 0; index < children.length; index += 1) { + if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) { + offset -= Element.offsetSize (children[index], droponOptions.overlap); + } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) { + child = index + 1 < children.length ? children[index + 1] : null; + break; + } else { + child = children[index]; + break; + } + } + } + + dropon.insertBefore(element, child); + + Sortable.options(oldParentNode).onChange(element); + droponOptions.onChange(element); + } + }, + + unmark: function() { + if(Sortable._marker) Sortable._marker.hide(); + }, + + mark: function(dropon, position) { + // mark on ghosting only + var sortable = Sortable.options(dropon.parentNode); + if(sortable && !sortable.ghosting) return; + + if(!Sortable._marker) { + Sortable._marker = + ($('dropmarker') || Element.extend(document.createElement('DIV'))). + hide().addClassName('dropmarker').setStyle({position:'absolute'}); + document.getElementsByTagName("body").item(0).appendChild(Sortable._marker); + } + var offsets = Position.cumulativeOffset(dropon); + Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'}); + + if(position=='after') + if(sortable.overlap == 'horizontal') + Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'}); + else + Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'}); + + Sortable._marker.show(); + }, + + _tree: function(element, options, parent) { + var children = Sortable.findElements(element, options) || []; + + for (var i = 0; i < children.length; ++i) { + var match = children[i].id.match(options.format); + + if (!match) continue; + + var child = { + id: encodeURIComponent(match ? match[1] : null), + element: element, + parent: parent, + children: [], + position: parent.children.length, + container: $(children[i]).down(options.treeTag) + }; + + /* Get the element containing the children and recurse over it */ + if (child.container) + this._tree(child.container, options, child); + + parent.children.push (child); + } + + return parent; + }, + + tree: function(element) { + element = $(element); + var sortableOptions = this.options(element); + var options = Object.extend({ + tag: sortableOptions.tag, + treeTag: sortableOptions.treeTag, + only: sortableOptions.only, + name: element.id, + format: sortableOptions.format + }, arguments[1] || { }); + + var root = { + id: null, + parent: null, + children: [], + container: element, + position: 0 + }; + + return Sortable._tree(element, options, root); + }, + + /* Construct a [i] index for a particular node */ + _constructIndex: function(node) { + var index = ''; + do { + if (node.id) index = '[' + node.position + ']' + index; + } while ((node = node.parent) != null); + return index; + }, + + sequence: function(element) { + element = $(element); + var options = Object.extend(this.options(element), arguments[1] || { }); + + return $(this.findElements(element, options) || []).map( function(item) { + return item.id.match(options.format) ? item.id.match(options.format)[1] : ''; + }); + }, + + setSequence: function(element, new_sequence) { + element = $(element); + var options = Object.extend(this.options(element), arguments[2] || { }); + + var nodeMap = { }; + this.findElements(element, options).each( function(n) { + if (n.id.match(options.format)) + nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode]; + n.parentNode.removeChild(n); + }); + + new_sequence.each(function(ident) { + var n = nodeMap[ident]; + if (n) { + n[1].appendChild(n[0]); + delete nodeMap[ident]; + } + }); + }, + + serialize: function(element) { + element = $(element); + var options = Object.extend(Sortable.options(element), arguments[1] || { }); + var name = encodeURIComponent( + (arguments[1] && arguments[1].name) ? arguments[1].name : element.id); + + if (options.tree) { + return Sortable.tree(element, arguments[1]).children.map( function (item) { + return [name + Sortable._constructIndex(item) + "[id]=" + + encodeURIComponent(item.id)].concat(item.children.map(arguments.callee)); + }).flatten().join('&'); + } else { + return Sortable.sequence(element, arguments[1]).map( function(item) { + return name + "[]=" + encodeURIComponent(item); + }).join('&'); + } + } +}; + +// Returns true if child is contained within element +Element.isParent = function(child, element) { + if (!child.parentNode || child == element) return false; + if (child.parentNode == element) return true; + return Element.isParent(child.parentNode, element); +}; + +Element.findChildren = function(element, only, recursive, tagName) { + if(!element.hasChildNodes()) return null; + tagName = tagName.toUpperCase(); + if(only) only = [only].flatten(); + var elements = []; + $A(element.childNodes).each( function(e) { + if(e.tagName && e.tagName.toUpperCase()==tagName && + (!only || (Element.classNames(e).detect(function(v) { return only.include(v) })))) + elements.push(e); + if(recursive) { + var grandchildren = Element.findChildren(e, only, recursive, tagName); + if(grandchildren) elements.push(grandchildren); + } + }); + + return (elements.length>0 ? elements.flatten() : []); +}; + +Element.offsetSize = function (element, type) { + return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')]; +}; diff --git a/docs/effects.js b/docs/effects.js new file mode 100644 index 00000000..59b49f92 --- /dev/null +++ b/docs/effects.js @@ -0,0 +1,1128 @@ +// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// Contributors: +// Justin Palmer (http://encytemedia.com/) +// Mark Pilgrim (http://diveintomark.org/) +// Martin Bialasinki +// +// script.aculo.us is freely distributable under the terms of an MIT-style license. +// For details, see the script.aculo.us web site: http://script.aculo.us/ + +// converts rgb() and #xxx to #xxxxxx format, +// returns self (or first argument) if not convertable +String.prototype.parseColor = function() { + var color = '#'; + if (this.slice(0,4) == 'rgb(') { + var cols = this.slice(4,this.length-1).split(','); + var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3); + } else { + if (this.slice(0,1) == '#') { + if (this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase(); + if (this.length==7) color = this.toLowerCase(); + } + } + return (color.length==7 ? color : (arguments[0] || this)); +}; + +/*--------------------------------------------------------------------------*/ + +Element.collectTextNodes = function(element) { + return $A($(element).childNodes).collect( function(node) { + return (node.nodeType==3 ? node.nodeValue : + (node.hasChildNodes() ? Element.collectTextNodes(node) : '')); + }).flatten().join(''); +}; + +Element.collectTextNodesIgnoreClass = function(element, className) { + return $A($(element).childNodes).collect( function(node) { + return (node.nodeType==3 ? node.nodeValue : + ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? + Element.collectTextNodesIgnoreClass(node, className) : '')); + }).flatten().join(''); +}; + +Element.setContentZoom = function(element, percent) { + element = $(element); + element.setStyle({fontSize: (percent/100) + 'em'}); + if (Prototype.Browser.WebKit) window.scrollBy(0,0); + return element; +}; + +Element.getInlineOpacity = function(element){ + return $(element).style.opacity || ''; +}; + +Element.forceRerendering = function(element) { + try { + element = $(element); + var n = document.createTextNode(' '); + element.appendChild(n); + element.removeChild(n); + } catch(e) { } +}; + +/*--------------------------------------------------------------------------*/ + +var Effect = { + _elementDoesNotExistError: { + name: 'ElementDoesNotExistError', + message: 'The specified DOM element does not exist, but is required for this effect to operate' + }, + Transitions: { + linear: Prototype.K, + sinoidal: function(pos) { + return (-Math.cos(pos*Math.PI)/2) + .5; + }, + reverse: function(pos) { + return 1-pos; + }, + flicker: function(pos) { + var pos = ((-Math.cos(pos*Math.PI)/4) + .75) + Math.random()/4; + return pos > 1 ? 1 : pos; + }, + wobble: function(pos) { + return (-Math.cos(pos*Math.PI*(9*pos))/2) + .5; + }, + pulse: function(pos, pulses) { + return (-Math.cos((pos*((pulses||5)-.5)*2)*Math.PI)/2) + .5; + }, + spring: function(pos) { + return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6)); + }, + none: function(pos) { + return 0; + }, + full: function(pos) { + return 1; + } + }, + DefaultOptions: { + duration: 1.0, // seconds + fps: 100, // 100= assume 66fps max. + sync: false, // true for combining + from: 0.0, + to: 1.0, + delay: 0.0, + queue: 'parallel' + }, + tagifyText: function(element) { + var tagifyStyle = 'position:relative'; + if (Prototype.Browser.IE) tagifyStyle += ';zoom:1'; + + element = $(element); + $A(element.childNodes).each( function(child) { + if (child.nodeType==3) { + child.nodeValue.toArray().each( function(character) { + element.insertBefore( + new Element('span', {style: tagifyStyle}).update( + character == ' ' ? String.fromCharCode(160) : character), + child); + }); + Element.remove(child); + } + }); + }, + multiple: function(element, effect) { + var elements; + if (((typeof element == 'object') || + Object.isFunction(element)) && + (element.length)) + elements = element; + else + elements = $(element).childNodes; + + var options = Object.extend({ + speed: 0.1, + delay: 0.0 + }, arguments[2] || { }); + var masterDelay = options.delay; + + $A(elements).each( function(element, index) { + new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay })); + }); + }, + PAIRS: { + 'slide': ['SlideDown','SlideUp'], + 'blind': ['BlindDown','BlindUp'], + 'appear': ['Appear','Fade'] + }, + toggle: function(element, effect) { + element = $(element); + effect = (effect || 'appear').toLowerCase(); + var options = Object.extend({ + queue: { position:'end', scope:(element.id || 'global'), limit: 1 } + }, arguments[2] || { }); + Effect[element.visible() ? + Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options); + } +}; + +Effect.DefaultOptions.transition = Effect.Transitions.sinoidal; + +/* ------------- core effects ------------- */ + +Effect.ScopedQueue = Class.create(Enumerable, { + initialize: function() { + this.effects = []; + this.interval = null; + }, + _each: function(iterator) { + this.effects._each(iterator); + }, + add: function(effect) { + var timestamp = new Date().getTime(); + + var position = Object.isString(effect.options.queue) ? + effect.options.queue : effect.options.queue.position; + + switch(position) { + case 'front': + // move unstarted effects after this effect + this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) { + e.startOn += effect.finishOn; + e.finishOn += effect.finishOn; + }); + break; + case 'with-last': + timestamp = this.effects.pluck('startOn').max() || timestamp; + break; + case 'end': + // start effect after last queued effect has finished + timestamp = this.effects.pluck('finishOn').max() || timestamp; + break; + } + + effect.startOn += timestamp; + effect.finishOn += timestamp; + + if (!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit)) + this.effects.push(effect); + + if (!this.interval) + this.interval = setInterval(this.loop.bind(this), 15); + }, + remove: function(effect) { + this.effects = this.effects.reject(function(e) { return e==effect }); + if (this.effects.length == 0) { + clearInterval(this.interval); + this.interval = null; + } + }, + loop: function() { + var timePos = new Date().getTime(); + for(var i=0, len=this.effects.length;i= this.startOn) { + if (timePos >= this.finishOn) { + this.render(1.0); + this.cancel(); + this.event('beforeFinish'); + if (this.finish) this.finish(); + this.event('afterFinish'); + return; + } + var pos = (timePos - this.startOn) / this.totalTime, + frame = (pos * this.totalFrames).round(); + if (frame > this.currentFrame) { + this.render(pos); + this.currentFrame = frame; + } + } + }, + cancel: function() { + if (!this.options.sync) + Effect.Queues.get(Object.isString(this.options.queue) ? + 'global' : this.options.queue.scope).remove(this); + this.state = 'finished'; + }, + event: function(eventName) { + if (this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this); + if (this.options[eventName]) this.options[eventName](this); + }, + inspect: function() { + var data = $H(); + for(property in this) + if (!Object.isFunction(this[property])) data.set(property, this[property]); + return '#'; + } +}); + +Effect.Parallel = Class.create(Effect.Base, { + initialize: function(effects) { + this.effects = effects || []; + this.start(arguments[1]); + }, + update: function(position) { + this.effects.invoke('render', position); + }, + finish: function(position) { + this.effects.each( function(effect) { + effect.render(1.0); + effect.cancel(); + effect.event('beforeFinish'); + if (effect.finish) effect.finish(position); + effect.event('afterFinish'); + }); + } +}); + +Effect.Tween = Class.create(Effect.Base, { + initialize: function(object, from, to) { + object = Object.isString(object) ? $(object) : object; + var args = $A(arguments), method = args.last(), + options = args.length == 5 ? args[3] : null; + this.method = Object.isFunction(method) ? method.bind(object) : + Object.isFunction(object[method]) ? object[method].bind(object) : + function(value) { object[method] = value }; + this.start(Object.extend({ from: from, to: to }, options || { })); + }, + update: function(position) { + this.method(position); + } +}); + +Effect.Event = Class.create(Effect.Base, { + initialize: function() { + this.start(Object.extend({ duration: 0 }, arguments[0] || { })); + }, + update: Prototype.emptyFunction +}); + +Effect.Opacity = Class.create(Effect.Base, { + initialize: function(element) { + this.element = $(element); + if (!this.element) throw(Effect._elementDoesNotExistError); + // make this work on IE on elements without 'layout' + if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout)) + this.element.setStyle({zoom: 1}); + var options = Object.extend({ + from: this.element.getOpacity() || 0.0, + to: 1.0 + }, arguments[1] || { }); + this.start(options); + }, + update: function(position) { + this.element.setOpacity(position); + } +}); + +Effect.Move = Class.create(Effect.Base, { + initialize: function(element) { + this.element = $(element); + if (!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ + x: 0, + y: 0, + mode: 'relative' + }, arguments[1] || { }); + this.start(options); + }, + setup: function() { + this.element.makePositioned(); + this.originalLeft = parseFloat(this.element.getStyle('left') || '0'); + this.originalTop = parseFloat(this.element.getStyle('top') || '0'); + if (this.options.mode == 'absolute') { + this.options.x = this.options.x - this.originalLeft; + this.options.y = this.options.y - this.originalTop; + } + }, + update: function(position) { + this.element.setStyle({ + left: (this.options.x * position + this.originalLeft).round() + 'px', + top: (this.options.y * position + this.originalTop).round() + 'px' + }); + } +}); + +// for backwards compatibility +Effect.MoveBy = function(element, toTop, toLeft) { + return new Effect.Move(element, + Object.extend({ x: toLeft, y: toTop }, arguments[3] || { })); +}; + +Effect.Scale = Class.create(Effect.Base, { + initialize: function(element, percent) { + this.element = $(element); + if (!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ + scaleX: true, + scaleY: true, + scaleContent: true, + scaleFromCenter: false, + scaleMode: 'box', // 'box' or 'contents' or { } with provided values + scaleFrom: 100.0, + scaleTo: percent + }, arguments[2] || { }); + this.start(options); + }, + setup: function() { + this.restoreAfterFinish = this.options.restoreAfterFinish || false; + this.elementPositioning = this.element.getStyle('position'); + + this.originalStyle = { }; + ['top','left','width','height','fontSize'].each( function(k) { + this.originalStyle[k] = this.element.style[k]; + }.bind(this)); + + this.originalTop = this.element.offsetTop; + this.originalLeft = this.element.offsetLeft; + + var fontSize = this.element.getStyle('font-size') || '100%'; + ['em','px','%','pt'].each( function(fontSizeType) { + if (fontSize.indexOf(fontSizeType)>0) { + this.fontSize = parseFloat(fontSize); + this.fontSizeType = fontSizeType; + } + }.bind(this)); + + this.factor = (this.options.scaleTo - this.options.scaleFrom)/100; + + this.dims = null; + if (this.options.scaleMode=='box') + this.dims = [this.element.offsetHeight, this.element.offsetWidth]; + if (/^content/.test(this.options.scaleMode)) + this.dims = [this.element.scrollHeight, this.element.scrollWidth]; + if (!this.dims) + this.dims = [this.options.scaleMode.originalHeight, + this.options.scaleMode.originalWidth]; + }, + update: function(position) { + var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position); + if (this.options.scaleContent && this.fontSize) + this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType }); + this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale); + }, + finish: function(position) { + if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle); + }, + setDimensions: function(height, width) { + var d = { }; + if (this.options.scaleX) d.width = width.round() + 'px'; + if (this.options.scaleY) d.height = height.round() + 'px'; + if (this.options.scaleFromCenter) { + var topd = (height - this.dims[0])/2; + var leftd = (width - this.dims[1])/2; + if (this.elementPositioning == 'absolute') { + if (this.options.scaleY) d.top = this.originalTop-topd + 'px'; + if (this.options.scaleX) d.left = this.originalLeft-leftd + 'px'; + } else { + if (this.options.scaleY) d.top = -topd + 'px'; + if (this.options.scaleX) d.left = -leftd + 'px'; + } + } + this.element.setStyle(d); + } +}); + +Effect.Highlight = Class.create(Effect.Base, { + initialize: function(element) { + this.element = $(element); + if (!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || { }); + this.start(options); + }, + setup: function() { + // Prevent executing on elements not in the layout flow + if (this.element.getStyle('display')=='none') { this.cancel(); return; } + // Disable background image during the effect + this.oldStyle = { }; + if (!this.options.keepBackgroundImage) { + this.oldStyle.backgroundImage = this.element.getStyle('background-image'); + this.element.setStyle({backgroundImage: 'none'}); + } + if (!this.options.endcolor) + this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff'); + if (!this.options.restorecolor) + this.options.restorecolor = this.element.getStyle('background-color'); + // init color calculations + this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this)); + this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this)); + }, + update: function(position) { + this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){ + return m+((this._base[i]+(this._delta[i]*position)).round().toColorPart()); }.bind(this)) }); + }, + finish: function() { + this.element.setStyle(Object.extend(this.oldStyle, { + backgroundColor: this.options.restorecolor + })); + } +}); + +Effect.ScrollTo = function(element) { + var options = arguments[1] || { }, + scrollOffsets = document.viewport.getScrollOffsets(), + elementOffsets = $(element).cumulativeOffset(); + + if (options.offset) elementOffsets[1] += options.offset; + + return new Effect.Tween(null, + scrollOffsets.top, + elementOffsets[1], + options, + function(p){ scrollTo(scrollOffsets.left, p.round()); } + ); +}; + +/* ------------- combination effects ------------- */ + +Effect.Fade = function(element) { + element = $(element); + var oldOpacity = element.getInlineOpacity(); + var options = Object.extend({ + from: element.getOpacity() || 1.0, + to: 0.0, + afterFinishInternal: function(effect) { + if (effect.options.to!=0) return; + effect.element.hide().setStyle({opacity: oldOpacity}); + } + }, arguments[1] || { }); + return new Effect.Opacity(element,options); +}; + +Effect.Appear = function(element) { + element = $(element); + var options = Object.extend({ + from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0), + to: 1.0, + // force Safari to render floated elements properly + afterFinishInternal: function(effect) { + effect.element.forceRerendering(); + }, + beforeSetup: function(effect) { + effect.element.setOpacity(effect.options.from).show(); + }}, arguments[1] || { }); + return new Effect.Opacity(element,options); +}; + +Effect.Puff = function(element) { + element = $(element); + var oldStyle = { + opacity: element.getInlineOpacity(), + position: element.getStyle('position'), + top: element.style.top, + left: element.style.left, + width: element.style.width, + height: element.style.height + }; + return new Effect.Parallel( + [ new Effect.Scale(element, 200, + { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }), + new Effect.Opacity(element, { sync: true, to: 0.0 } ) ], + Object.extend({ duration: 1.0, + beforeSetupInternal: function(effect) { + Position.absolutize(effect.effects[0].element); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide().setStyle(oldStyle); } + }, arguments[1] || { }) + ); +}; + +Effect.BlindUp = function(element) { + element = $(element); + element.makeClipping(); + return new Effect.Scale(element, 0, + Object.extend({ scaleContent: false, + scaleX: false, + restoreAfterFinish: true, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping(); + } + }, arguments[1] || { }) + ); +}; + +Effect.BlindDown = function(element) { + element = $(element); + var elementDimensions = element.getDimensions(); + return new Effect.Scale(element, 100, Object.extend({ + scaleContent: false, + scaleX: false, + scaleFrom: 0, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + effect.element.makeClipping().setStyle({height: '0px'}).show(); + }, + afterFinishInternal: function(effect) { + effect.element.undoClipping(); + } + }, arguments[1] || { })); +}; + +Effect.SwitchOff = function(element) { + element = $(element); + var oldOpacity = element.getInlineOpacity(); + return new Effect.Appear(element, Object.extend({ + duration: 0.4, + from: 0, + transition: Effect.Transitions.flicker, + afterFinishInternal: function(effect) { + new Effect.Scale(effect.element, 1, { + duration: 0.3, scaleFromCenter: true, + scaleX: false, scaleContent: false, restoreAfterFinish: true, + beforeSetup: function(effect) { + effect.element.makePositioned().makeClipping(); + }, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity}); + } + }); + } + }, arguments[1] || { })); +}; + +Effect.DropOut = function(element) { + element = $(element); + var oldStyle = { + top: element.getStyle('top'), + left: element.getStyle('left'), + opacity: element.getInlineOpacity() }; + return new Effect.Parallel( + [ new Effect.Move(element, {x: 0, y: 100, sync: true }), + new Effect.Opacity(element, { sync: true, to: 0.0 }) ], + Object.extend( + { duration: 0.5, + beforeSetup: function(effect) { + effect.effects[0].element.makePositioned(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle); + } + }, arguments[1] || { })); +}; + +Effect.Shake = function(element) { + element = $(element); + var options = Object.extend({ + distance: 20, + duration: 0.5 + }, arguments[1] || {}); + var distance = parseFloat(options.distance); + var split = parseFloat(options.duration) / 10.0; + var oldStyle = { + top: element.getStyle('top'), + left: element.getStyle('left') }; + return new Effect.Move(element, + { x: distance, y: 0, duration: split, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -distance, y: 0, duration: split, afterFinishInternal: function(effect) { + effect.element.undoPositioned().setStyle(oldStyle); + }}); }}); }}); }}); }}); }}); +}; + +Effect.SlideDown = function(element) { + element = $(element).cleanWhitespace(); + // SlideDown need to have the content of the element wrapped in a container element with fixed height! + var oldInnerBottom = element.down().getStyle('bottom'); + var elementDimensions = element.getDimensions(); + return new Effect.Scale(element, 100, Object.extend({ + scaleContent: false, + scaleX: false, + scaleFrom: window.opera ? 0 : 1, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + effect.element.makePositioned(); + effect.element.down().makePositioned(); + if (window.opera) effect.element.setStyle({top: ''}); + effect.element.makeClipping().setStyle({height: '0px'}).show(); + }, + afterUpdateInternal: function(effect) { + effect.element.down().setStyle({bottom: + (effect.dims[0] - effect.element.clientHeight) + 'px' }); + }, + afterFinishInternal: function(effect) { + effect.element.undoClipping().undoPositioned(); + effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); } + }, arguments[1] || { }) + ); +}; + +Effect.SlideUp = function(element) { + element = $(element).cleanWhitespace(); + var oldInnerBottom = element.down().getStyle('bottom'); + var elementDimensions = element.getDimensions(); + return new Effect.Scale(element, window.opera ? 0 : 1, + Object.extend({ scaleContent: false, + scaleX: false, + scaleMode: 'box', + scaleFrom: 100, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + effect.element.makePositioned(); + effect.element.down().makePositioned(); + if (window.opera) effect.element.setStyle({top: ''}); + effect.element.makeClipping().show(); + }, + afterUpdateInternal: function(effect) { + effect.element.down().setStyle({bottom: + (effect.dims[0] - effect.element.clientHeight) + 'px' }); + }, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping().undoPositioned(); + effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); + } + }, arguments[1] || { }) + ); +}; + +// Bug in opera makes the TD containing this element expand for a instance after finish +Effect.Squish = function(element) { + return new Effect.Scale(element, window.opera ? 1 : 0, { + restoreAfterFinish: true, + beforeSetup: function(effect) { + effect.element.makeClipping(); + }, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping(); + } + }); +}; + +Effect.Grow = function(element) { + element = $(element); + var options = Object.extend({ + direction: 'center', + moveTransition: Effect.Transitions.sinoidal, + scaleTransition: Effect.Transitions.sinoidal, + opacityTransition: Effect.Transitions.full + }, arguments[1] || { }); + var oldStyle = { + top: element.style.top, + left: element.style.left, + height: element.style.height, + width: element.style.width, + opacity: element.getInlineOpacity() }; + + var dims = element.getDimensions(); + var initialMoveX, initialMoveY; + var moveX, moveY; + + switch (options.direction) { + case 'top-left': + initialMoveX = initialMoveY = moveX = moveY = 0; + break; + case 'top-right': + initialMoveX = dims.width; + initialMoveY = moveY = 0; + moveX = -dims.width; + break; + case 'bottom-left': + initialMoveX = moveX = 0; + initialMoveY = dims.height; + moveY = -dims.height; + break; + case 'bottom-right': + initialMoveX = dims.width; + initialMoveY = dims.height; + moveX = -dims.width; + moveY = -dims.height; + break; + case 'center': + initialMoveX = dims.width / 2; + initialMoveY = dims.height / 2; + moveX = -dims.width / 2; + moveY = -dims.height / 2; + break; + } + + return new Effect.Move(element, { + x: initialMoveX, + y: initialMoveY, + duration: 0.01, + beforeSetup: function(effect) { + effect.element.hide().makeClipping().makePositioned(); + }, + afterFinishInternal: function(effect) { + new Effect.Parallel( + [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }), + new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }), + new Effect.Scale(effect.element, 100, { + scaleMode: { originalHeight: dims.height, originalWidth: dims.width }, + sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true}) + ], Object.extend({ + beforeSetup: function(effect) { + effect.effects[0].element.setStyle({height: '0px'}).show(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle); + } + }, options) + ); + } + }); +}; + +Effect.Shrink = function(element) { + element = $(element); + var options = Object.extend({ + direction: 'center', + moveTransition: Effect.Transitions.sinoidal, + scaleTransition: Effect.Transitions.sinoidal, + opacityTransition: Effect.Transitions.none + }, arguments[1] || { }); + var oldStyle = { + top: element.style.top, + left: element.style.left, + height: element.style.height, + width: element.style.width, + opacity: element.getInlineOpacity() }; + + var dims = element.getDimensions(); + var moveX, moveY; + + switch (options.direction) { + case 'top-left': + moveX = moveY = 0; + break; + case 'top-right': + moveX = dims.width; + moveY = 0; + break; + case 'bottom-left': + moveX = 0; + moveY = dims.height; + break; + case 'bottom-right': + moveX = dims.width; + moveY = dims.height; + break; + case 'center': + moveX = dims.width / 2; + moveY = dims.height / 2; + break; + } + + return new Effect.Parallel( + [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }), + new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}), + new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }) + ], Object.extend({ + beforeStartInternal: function(effect) { + effect.effects[0].element.makePositioned().makeClipping(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); } + }, options) + ); +}; + +Effect.Pulsate = function(element) { + element = $(element); + var options = arguments[1] || { }, + oldOpacity = element.getInlineOpacity(), + transition = options.transition || Effect.Transitions.linear, + reverser = function(pos){ + return 1 - transition((-Math.cos((pos*(options.pulses||5)*2)*Math.PI)/2) + .5); + }; + + return new Effect.Opacity(element, + Object.extend(Object.extend({ duration: 2.0, from: 0, + afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); } + }, options), {transition: reverser})); +}; + +Effect.Fold = function(element) { + element = $(element); + var oldStyle = { + top: element.style.top, + left: element.style.left, + width: element.style.width, + height: element.style.height }; + element.makeClipping(); + return new Effect.Scale(element, 5, Object.extend({ + scaleContent: false, + scaleX: false, + afterFinishInternal: function(effect) { + new Effect.Scale(element, 1, { + scaleContent: false, + scaleY: false, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping().setStyle(oldStyle); + } }); + }}, arguments[1] || { })); +}; + +Effect.Morph = Class.create(Effect.Base, { + initialize: function(element) { + this.element = $(element); + if (!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ + style: { } + }, arguments[1] || { }); + + if (!Object.isString(options.style)) this.style = $H(options.style); + else { + if (options.style.include(':')) + this.style = options.style.parseStyle(); + else { + this.element.addClassName(options.style); + this.style = $H(this.element.getStyles()); + this.element.removeClassName(options.style); + var css = this.element.getStyles(); + this.style = this.style.reject(function(style) { + return style.value == css[style.key]; + }); + options.afterFinishInternal = function(effect) { + effect.element.addClassName(effect.options.style); + effect.transforms.each(function(transform) { + effect.element.style[transform.style] = ''; + }); + }; + } + } + this.start(options); + }, + + setup: function(){ + function parseColor(color){ + if (!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff'; + color = color.parseColor(); + return $R(0,2).map(function(i){ + return parseInt( color.slice(i*2+1,i*2+3), 16 ); + }); + } + this.transforms = this.style.map(function(pair){ + var property = pair[0], value = pair[1], unit = null; + + if (value.parseColor('#zzzzzz') != '#zzzzzz') { + value = value.parseColor(); + unit = 'color'; + } else if (property == 'opacity') { + value = parseFloat(value); + if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout)) + this.element.setStyle({zoom: 1}); + } else if (Element.CSS_LENGTH.test(value)) { + var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/); + value = parseFloat(components[1]); + unit = (components.length == 3) ? components[2] : null; + } + + var originalValue = this.element.getStyle(property); + return { + style: property.camelize(), + originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0), + targetValue: unit=='color' ? parseColor(value) : value, + unit: unit + }; + }.bind(this)).reject(function(transform){ + return ( + (transform.originalValue == transform.targetValue) || + ( + transform.unit != 'color' && + (isNaN(transform.originalValue) || isNaN(transform.targetValue)) + ) + ); + }); + }, + update: function(position) { + var style = { }, transform, i = this.transforms.length; + while(i--) + style[(transform = this.transforms[i]).style] = + transform.unit=='color' ? '#'+ + (Math.round(transform.originalValue[0]+ + (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() + + (Math.round(transform.originalValue[1]+ + (transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() + + (Math.round(transform.originalValue[2]+ + (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() : + (transform.originalValue + + (transform.targetValue - transform.originalValue) * position).toFixed(3) + + (transform.unit === null ? '' : transform.unit); + this.element.setStyle(style, true); + } +}); + +Effect.Transform = Class.create({ + initialize: function(tracks){ + this.tracks = []; + this.options = arguments[1] || { }; + this.addTracks(tracks); + }, + addTracks: function(tracks){ + tracks.each(function(track){ + track = $H(track); + var data = track.values().first(); + this.tracks.push($H({ + ids: track.keys().first(), + effect: Effect.Morph, + options: { style: data } + })); + }.bind(this)); + return this; + }, + play: function(){ + return new Effect.Parallel( + this.tracks.map(function(track){ + var ids = track.get('ids'), effect = track.get('effect'), options = track.get('options'); + var elements = [$(ids) || $$(ids)].flatten(); + return elements.map(function(e){ return new effect(e, Object.extend({ sync:true }, options)) }); + }).flatten(), + this.options + ); + } +}); + +Element.CSS_PROPERTIES = $w( + 'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' + + 'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' + + 'borderRightColor borderRightStyle borderRightWidth borderSpacing ' + + 'borderTopColor borderTopStyle borderTopWidth bottom clip color ' + + 'fontSize fontWeight height left letterSpacing lineHeight ' + + 'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+ + 'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' + + 'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' + + 'right textIndent top width wordSpacing zIndex'); + +Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/; + +String.__parseStyleElement = document.createElement('div'); +String.prototype.parseStyle = function(){ + var style, styleRules = $H(); + if (Prototype.Browser.WebKit) + style = new Element('div',{style:this}).style; + else { + String.__parseStyleElement.innerHTML = '
    '; + style = String.__parseStyleElement.childNodes[0].style; + } + + Element.CSS_PROPERTIES.each(function(property){ + if (style[property]) styleRules.set(property, style[property]); + }); + + if (Prototype.Browser.IE && this.include('opacity')) + styleRules.set('opacity', this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]); + + return styleRules; +}; + +if (document.defaultView && document.defaultView.getComputedStyle) { + Element.getStyles = function(element) { + var css = document.defaultView.getComputedStyle($(element), null); + return Element.CSS_PROPERTIES.inject({ }, function(styles, property) { + styles[property] = css[property]; + return styles; + }); + }; +} else { + Element.getStyles = function(element) { + element = $(element); + var css = element.currentStyle, styles; + styles = Element.CSS_PROPERTIES.inject({ }, function(results, property) { + results[property] = css[property]; + return results; + }); + if (!styles.opacity) styles.opacity = element.getOpacity(); + return styles; + }; +} + +Effect.Methods = { + morph: function(element, style) { + element = $(element); + new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || { })); + return element; + }, + visualEffect: function(element, effect, options) { + element = $(element); + var s = effect.dasherize().camelize(), klass = s.charAt(0).toUpperCase() + s.substring(1); + new Effect[klass](element, options); + return element; + }, + highlight: function(element, options) { + element = $(element); + new Effect.Highlight(element, options); + return element; + } +}; + +$w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+ + 'pulsate shake puff squish switchOff dropOut').each( + function(effect) { + Effect.Methods[effect] = function(element, options){ + element = $(element); + Effect[effect.charAt(0).toUpperCase() + effect.substring(1)](element, options); + return element; + }; + } +); + +$w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each( + function(f) { Effect.Methods[f] = Element[f]; } +); + +Element.addMethods(Effect.Methods); diff --git a/docs/exposure.jpg b/docs/exposure.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9f9e6a77fb27a86e3c344c6c73b8b28ffc0cefea GIT binary patch literal 28472 zcmb4pRZtx~7wy5_-R0o!?(QzdifeH=xI=MwD6R*0r+9Ifk0Qn0-R1H>+?o4&ck-4= z)=o0XO7>o#e?PYX=n69OG5`n&2!PyI0er3jBmq#6kpHV+2K`lG;9+2(pUs*1k_g_Xcz<-7zAt- zBoyrbZ~N>6V8BB#0Y;%9FaVGk5KtHppF;o=0N~5B|GE2r4H^a(4iX9;0)X(Hv^igTMw!_4(M)H5rs@yr-dqW@<}c zQ9Aj963CI1lZckiwO;K=gsB$ifcs0?FI-$pPn=T07K?9lLLe@MMT~(21OFDH_o}7# z@02i{YkjQS**;YFl=+DUwaeE80U&hD`!jb^eEjy#ZK07u}VDA}gMlqvIQG}}&P^K0P!BV9%G^5tkKcLn3V{=efTjcv;aC6rw9)=QCBKKN zYg7??btIi_n7OXSB21`IrvBZHjSwu(oX;kPpjxQFwWc)7@E46uLk1ZJgU1YJfxTX{13)MJH_L>;h~^! z(2^YkMn9%0i@+eCz$f5`TuJy2j*e2KmBqC94gPWA3ELhOHfMe3KfWEMn#R@R>bw;gJ4{^TL_myIl)K;BVahe zDMHwRso~`lF~4oM5wa9Lna-m@A(D@1Jy>yTHp6l=x4ZuKUs-e=2)0oRpI#yBR?{_@ zS9J)NK%n6jc8OCM)~>RMNb!N{AsWyExsRz(72?2;&Qry9>_7U)ho+_eg%!j?XNtNH z3`B7gs{aIdaaAhv+>J; z-8A$rHL>&3%=XOIMA(1lTpX7I=(y^l^b)tqYB{6%mFbn=K!dz7&pm3TIBga5r*nw*?7 zuYtcWf{rj=K_avE?j9ATh;GdyE`jFPK{T!()9cB>L);^#AvEm2dAa0bj{m5oG~%%u zd+x6DQYufVb~dRcPjIlgeGKj(qRb|itk5AQXt6_S*elBx@$n`i*~#G`Goq_58u|9R z(x2(#1go(QPSq*M<{`y1jZk{=y;CBLjogT@Culy|U8Dx}36bdy2>BWsb1ZN}=T0fa zMIIwXjo3{Ci5cb{~0s(Z+Waws>R~m*0)LX5v&K zjRhhgw(#$k^}tIf!fxRV7Pg4nNmrisYr$h>fxM!)O4(2TXW}qBVgSFx(s>+?VuE!HR`$2zFAsR-up=y5j zYjQOw3rZ!E-b;oOh0cDMa8*_}_eqzMS1&7aqqIeW*H*hS55#c|&s2D(xvY@?CSdX* z#n95o*)qGUdhb7^(f+!r6$2K&-$Qqub?_vgNVU&rt~oL_yCHacTWITyPRmqteYD)m zWu>xKC=I9EnpEJ_OHwr!-6~+?0{tIEj8P~GkJ?!^QTmkA7H@R9N$g#+y{y58GM*FG zu5jJvswmAE*k)8!GI#8n-2HJ5jGlAg>k5*!TDf(fb1>7iEUXe@VMUm%YulXgJa+vpKQC|A`w*C4Q75fJVBI2l=cdsXdQlckNs6zpbEDf#48=ruO z{CRb&M8xls&$F1qucu!8c`ozrZ`|0a(e-2FEvDWX-GbF zh26Eeno@P`w|F)>1NJC$#2f3KwT5%#zb`dojET}GjnFgr1f6t7sgDl^tX~#jW{dwiX@hAP#NuRrMB4 zZmn1nbV#*4GtZ|Jq+{OPA~}JYNW;7okgOy2l083jQ+Hetq(E%W-KP9|%koJI*2pN0^T%N_XTJoHY*3E?R7 zidZ+#)TLxPsO6NKca^C96SZ%SB8gDyWr6Y|#**yWBmIwmG{+a8MKec;^Ce*2V=}Z8 z3Ol9}q*FrR-)MfGy@U|PzeGfJwNl+ga7e)lfM`$SuunO(MMNjw*&}3w-Ut{k!6d5Y zywQ!f>dVyL&^R`h@~CdlZAg!Eu)QP>46?8HY_fx{x`n~7pMWchl+ukkI-g(Cl1g}8 zw#9K-Bw{0xfWZ@YHW!PdrN=>HZ<4g+PPp}wR^IG*Rf)92&~g_U)4Dpod0!VG9qn$ulrMdOX|18GZq^JEqI+3VyT{J8MRIRj7DbycVR z{TTYM$82Px$Lmo=bg*u$b=i7@G3sh**Vy#vJthGXL*0JAeNKMFagn7f0@68IocMn6 zYapyJNxwz1oVg@_rWC9zDjV8@vi+~*A{r#;P5#qwYl&53UEG>eaoNrk}C0{J_-C(FPc*TaT)YH@8CX=2lfs&Kg4 zwI9xO2+=2Y?h#meU=|#$g9468Gm^E6e$v36?h#yBF|oYdBymdFikcSO@lSvyFGBGF zbhs^caOlj@`=n1)>*pj?LtyJBstR8ce zBDte(Z06sJ$CFJdJeqC#rjCKWQF}i{U0l)FA#ZEV_*`Fj>7{%x`JE_+La*=A#Jj{p zPG$c2KcLhPH4iEjPkHs7?sT9SwJPTT^H-k`(SZ=vc!08)z0DqRf96 zm7H_8DkVjST|r!!=Fi%-+j8uPVV`< z8o!dyc`h_)j%v61ROaQ(`rYEwLin%hFm2$wwy!9XhT+3xu%&<5eE^w z3nr*;N>6Ru*_1e{?Fvuz^ZFR3U43rmIGtzuA{t&ApH z2vpt}b>TQxDpI|>U4GIYv@htA7W!~Wym+1zUwi@_O;uMOQH;|pv2Xj)fg{B0e>_je z`+vLdf@fkvCkf_l8*hzsqOYA0)b;tYS5%b+XC$uK6MNaNA=wi>Zf~G8*bY2~CkJAK ztNIhLipu2)9&9R(tfL~;mWZRpA3-m6HfC~Z{bF6YQBP!NIuQwcGUuM%nglk=y!8uv z=EI(znu{1eiF|TJNq^DQ0bxD?7_KyJMh}L3OFk)q&L5WC!?^rR#KxdDj>murbt*Al zLs)YE25_{>hUEi=34x9g6CP0;`l43Jm^@+E0Q}ozNo;;=+yN!ZZ$~Lhre)k!KF4gI z<|Tsj~lBwPT>_%Nge>ovqxwkjT#_J};b5p*XLV2NtHry^vi1^G%ZhP-Uxk5cA$I zZ<+;CJ7t6#uQ*{A`5xWV6|r~srp<-~HHOPz-!ZM53d>QVGadmtC-FkU0CGp}>Qb;A zrkphpWk+h=ub}|eU{4nPaGL{WwT0eSD^{F>YD^R(*Wid~`TI?kA+yse_4cU}*WCPK zWO8NJqNM?J#hjZJRT^C8x*Pzmd2OiG2P?`}hg-(5T`~mKeUhK382@dw8z*e>>K$pV zAa!;C0pJFMIP|g?XyiQp8i@k0h64-y1T0)B>VuOypYdaIKVFg!*>L$o=vWGigG_Qe zq%LYg^PCYYNI1(-G@&d4TsEztby$NiASFdwWQ`G#QL3hA7ON(%20zKNkB0uIx+|3aD~f(gKRvr$V%Hjn z3E~;*uog;5*#Yk^w~N|Orl-uVD7-e0tICePREA%N$so{cI!gX(UE6}Rl18L_SC8z8 z<5oh~j6-&9M$AC~lpJ0LIhXnUfVk_7R4FbsaKC4RJ6Go({_{zpfvd(|I zTW19o+Eij9XdB;*;qwIsA8j&A`ZGd|cF^MXA`$+Rk?4}?8iR45<+AS4N^-`&AAj~X zS6>D^IhN#fIHgP}T-30nxjjkD_Z>p)t%nBB`dr4TsK-KFQ2)4O63YkY2gwPi)Z3WM zu*bJrGM_$!%(texXnY>9M0WwPnf(v%ll$=-nX;>&04(I)*R@lTqBG_AtkHpdc_r<| zZk#2}2^vaSAk=1wvjF_8zswew`}N$c=2AN2#thBjsDj#e9anN2asGgcQmrkt9Z$$L&6In&2GC5Hn#*}r;8U=;XTCwff@67nTyr$+gd_4-IFpBNeQn_H_O>+tvfy@ z4n^cUsFY=u)wdQZTNY6W5;tI_0de3V_4qe1GGV=Tm!jkCLX=+A=7}DC%EYULP z9Yk*~X zSwy1t0t>MVJ&`=TP5~u#4|1de(TJHT`IH3|z=^tE8B1m<3|Q1zaaaPEjvCWeMRuR~ z{Sm=#B_aHg^1=lio^bHPeXyJPzoR&zRCc-F{rn`m0Y-1lOT&Ga!q%M34nc@7oc7^a zM&AR>Pz?da%Nia%rxoap+J-mFHX#is21p#YUL<|}BkGq6>&^Qn0^+r}_VQs$^ z*Yl6Cj|rnKsLEn{x?wb;%dF6CeJr+bHeT#I;CX*fmxttL%Hzw7eNu8t6n~$|gp<|~ zs794mD#{7KwDmoYFK$`I*XWs0wC_|4#$M2*m&(u0$au201g2h~qVbAX&Y%}JM)V#t z35ntl5!3I+_kJrnQe=jO^|szK5oU8$eZ5(-5bhdwoV-u zb}NQ9<3qRKV#m-jlwfdhU3g`hSs#51qK9a@T$mQF#m|Htm7^>IQ?_p`{vlSxFr9=F z>9clqIV?_&6lafegwKA;n{#XWOXE=^j-roQ2w|p>w3w2sG}~yoSc96u*ey|Z@b-?*nSYXyb04HqH%`BIuNTpGj=Ip%xCKh^0NMl`W(r*rkm%6#e z9Q#ol1u;Uic{!spuzABs=EbyyZTFC*X!M7sWnq{)Iwv!PV~^AcSW)}aHCYOm7Y-ki zudu2NcUAsa&NEKWQk#$b^`0at2Hh+ifXFHRu!}Mf!W)&$zvGuYBD8s2z9SJgBT0Nw z-B8-9uB!_lF@i_V;VCzer9rdWvY5TzOA>cHUimP_0n~!2)V&)U@C>MA>7q5Q0 zW2vivOn^yhsbK&`j@mI_x`~x*qyBTY@sBLhpoVWycW=fmS@~tH2|^KnoIaWSA?Q3! zJ54!WZb=asUtAVIX#7(QD~hO7yl#<;DK)uY6^U_TbUbES#&3ExDKgKKfMC%W(g`>7 zdw9L3=1?7EK01V3o0^D^+JAlhowHzs?N0qlc%zEkwJri?3ZlFc^Z)@mt48d8j&BEW z%$b_%nqwR)edFyk8vL$47k=R-D{O!?N{qn}eOrd#Ir0=~VUfg#mb8R5|M@X?9WuL1 z)dzO6*OkQJ3RWR_KJII+~=uXV$mU>yA_3W3KyfU_(TR3%RT&GIcaJ4 z+3#8v=5YMivI~b*3>rCENQ>XYsF(IiE0AN6;P|GWZr3OnBur|;^qPF<`Km#55* zN=sKO#CJ5=?)f@W_bzK^dYR8k&YIp6^KRJ=_Ty)upi_^*x4TL(#&zJcZ6w&P?{}}Z6h*dao zXlV|G>qy1A-Zq=WwGMBR$g8(40drbCG8+$31_ZSY{qLBd$IUS*p!Z(=Md*9gczIG)Qx!PvYlv0|7#Q0O-SF(fLg=i-vRN?}qpY5pZ!e+h9 zg|{>B^ePC96(rbkN-`&ItZKxqr8TS6$buLZ$Z8nGMLDW#&&`o676q*h)$J+Pg6s`< z&Dry`rU}4iEQ!|-ZV!B@aa||ZcUN#O&X)VvCK&sM8rlH45}oC*&GSc9_eb!FKK34% zN#2%i?e6CFv_`LWeHY|*9H$q<2#6zwJ-kHq6AXBvNA&8z52;@Raf`>N&1d6TI9}EH zKsbABI(fvHz4R};uz4n3tlwh*lqKk6+fAG|kxAmqb^wTEe7L?ze-zeWk`I@I7=uP$nbq+^iu51VoIv zbHbeyqD}Hl())I0`-ya$wQdRNITr&a0#aX~Hy zX04E|j=)_c2WeG!Wy=2)Y26s;5BtWLAcoiOmhvOo2}|`KQ$MKCR`B0N)fuG=Q%8Xu zjZwyq8r=2g>~;e~N4`IGz049X%)qyg=dY*JfOBE`1l4NPYnR%>pWaj?(+XwJ_(Wt2 z@^M-%p7CSdF(g!uYi@Q?40OaeQYo43d?R^lc9J{fj_6SIq~Wo26l{xT*q-GF*Gi~j z{I|0)Aq*_52ND&wG(Ya=a6$1Rcb<_$kb?nPPd=obogSZnI246-uSW~h+uXIky+|IG ze|xvHd=)AUq;;{i7G}768cUd(5YHM%km=t3vbWJu>pmOBuM9J4tZ=WUl&wpn!;>xw zyuS6E97xeljpE2-dp}G6j=nCp5mL9Rtn2nj(uD?5RPII-&|6g6=w;I;!v0*rF{IOg zS8sfD#i`J- z$Rut5jUe$DiuXMQ0WUM;#*&CC;1- z!%*Z=H+FEpyB^afQDBc8Ku0@(-tv2Sy8{jXDe*ADWoTaP*Ci46iIv~*GD~rq3hEHa zU)Gc@31)oBqmCKE{5?J*b>WK3xp8yZ@d+@ri_z}u%~{+bihPmQ#*Ny0kRum5vLcTE z2SN1qf}+D1+|f1mQ@PT2W)7{7(9#fN-A8C1d~Ct ztM+|oIPX^ntaM^htDo%SQbyS9((Mdp)t|UydjEYvGMHYAQ10yXBlZbz$}n3_RRxxzdpAjq{L#(W_4jdS~ucsZLBq>|UnNxuFAXs7&$ z^g?8BUPsgUZyh7`KR*H9X=E7c!ny<#kfUmf9e}eTpHsOSedi&i2}rkW&Tc{YvtJQk z!71agXi~U}t{8{Yh#w49W(kPC72n3y_`SwG*-8Llp_YI$R40|>h2AB2cWy->VSZ_c+1&F1YsaP_Ww&B!Y zwvBhWTqT#<_Rqg;&FjaVjHq_cpOy5E32wiasyUX1bZ|18x?_Sy6{>E*6sc@bB%rdZ zMYjk-j$1L%sUcc9<|9$8m-gQ!)ZZ4OZ7QQ>X9>-n0fg;{7M+7m@pkds9Yu$hRH@TX z!|-ra7V%ai?2V&Y?%OF;m+agWMoPC0J>xd7y1OxfaGI%Y>yGh9ZIG%AV3~kSVTs)` z-USyH!xhMiH%AZg1Wa7hXLxHn;oSKLf>q|z&<(jN-1T~KdgwOJOrUFDn&!^oG<&Pu z%>w84ctS%0aP@S91$X#qSo({V((Z-*sq2?LlVJ{_k~oB9cZ(dX)`l=v+a~A4g@qoe zt+SNuI-Cu^#3rGKS91!jfBX6*E*QM4Ro*F`iEWK~+^J zT0h76Q zKA3)GNguu==Cv*UHJ!=5rV)asd!F|HnApX?ELyVb;YByJD+MVqWb=)tMfLZ`X*VsD ztsN3BSj`muiEFY}kJB>Rbm|%D{Wi~yAAp?hA6a1+Prmg*)-r!prPZ(fN6Rgudufo~ zhTbst5hDkJGB(k;ka7DjrmKL~HHxFha-ycnKtl!gH{+qfH~BMxAaP+E#RWSVV@1*= zl#6{UgPP8=WH|#&uwdM@OR`XBXbmEZMsLRI8oRJ$1xdaNc#W-$>q0kTlT-R!lMUrw`!lBqO;>a`Jx{7&8n z%vh{NUa`KAQl2zd(Ty>U!S}fl#On;AEB_ToA%0L@lXkcchuY|ML_ZZ{kn=>&9=bSm z2>X9Dh5||xO&sB5BAb`HR6S8QzkvDWqOqqvBY{M`jKabeZ$J9AG6e6&uwk#QOXIWc zz!|xbV1j<3=^@vj#spXK<-GX=*Ywp)h_CFOLPl!B0fDjB50iQS33uxD_Uait)O-ac zNj;+6D$2K`jDy;>md3PEYKQ#ON|U0PnJ!6@y0B7uU_fbTRG zjhurso!JS~fI9xN+UH3Qz#M?Jfpj*0-M?IE@F#m$SKOrx(^ptaBhM=0$U{lDhtLruXi4}lHlcqKT+EMwZ6QEczG#M5Sa8un@;{?OK|GhSDOtt3~N3dw8pF|zm zRlL5aq5Vv|QZ+Q^hjte{QOQRi1CMks10QxTLl#9iOF=FsP*1n0XT{AE#zN`TkPrzF=P zZl2u54-I=8o3)6FqU0D-0Uezp8lh+d-EXGbj zU623#+7h^J(dstB6=*BJ(`UW_Ky!H=e_&wnHnla*S`piq5m>@p*2EDcebq&-A3E~m z=zD$B`UI49&#A57y$_Tit~W0ukaNn7A{&n5jA!r<^E}&hpa-jGW}-GwpB$qwYjDL- z*sKjuil%zo1i7da{{6S;sqCBm?f~q_rI2Q_OzUVY-Un|>v{%p(>2v4}c0lh+bWIb8?LI`Y8fEM8)o1)M7-}Yt@;tfwTk$Uz{(QUhu1_JFymZK@n0mT*FZL zr54u|!3OF%i0T%hvir~D)p=yG6o^A;`5e?k>7DrlPN<)NI>dbdX1T2-ed~M41s#)?c(5s9y%Dbunl;&piw6qgE*s}Q)7?TN zI-tc^j%9l3H2h!@v4_%w__`$gyD*kiMa7MA%>F2PDlW0p*H)!!^pa~*3 zT=cIaleWX1gY_5*c~sl|EyJO2Ibs97Uld3G9aqd!@r)$W^^Kp2d;(gVMD)xp%LaXd zB}`c6mA-s+l3RR1?`_Q(ue}!PM<@rE+e2a9`nY{*F9Ma>3Nifp;j=Tx@NMQY-bMfc z*)|nxTN7%tmaf!+FX5XGm|+se4!MIpFEHVlvmw*5)T}##|@_es~u%OJP(BV3&;P!MFU` z6S;&nJOw2?^*yhQx2pV2>0)BOD43?+3gN|hT#@e-uSdTsJuz~Gc#SAO{LsE%g_d~- z$!ZVkgjBBJn=)mQO@`%uWGin!35#wpaDj_a%i6UH_OZ1wL)+Nxp>ClAvi0E#$x0j` zV7t`kmiyP2R*q?+;+5QFs5YG_g*Wo_U7J`LMy_KS!ZabCZ%fn7c zvm+$J!zBfi=4X&G-wKjeam9F!5w8rtxKg^2q5{?ic`jvo5*C1Uq6zxbTj@dDjRgRXB&mT))Y+j7r8G&l-z zk;jOcy4f;dLJ+E)u>GDd_EWl`pZ9k4gu)mIn*U6TnSTftBMiEBnh40I+?c*|>(n?g z8~^Mua(?ghsYojh7b~aU+ZQVE&N(k`AfXaB&zcvG(<^LYc9^%4i(u$SupBcFp7g}O zBYM5hza}m@AL`XBznT)>(7a1o-Jo(-=vQeWGT~~3dH~qvu)48&L?VE})v;s3l$fHa zCTO*vfZF`NBeU~-jb)rPDfa`J&o7DlFEa63;QDmomm*;TGct!u??V%R=C~00QXuSy zLuq`2w`<3_=N2_+#nVFuuzz;32owydsCl59#ycVBar0S36~D z(uV1herEpw z)%+6qDU*hm$lrxQbCP0tmwCR@^7gf2&2JWoSc^^x6$`?&B@)9OSw@cN^Ah^H@K7)8Z?!V*p-mUKJKI#AT`0oJ;`o=8<$D=>CvFKI{{K)*X=GJ@#zwyQdCOKnANO)Wr6P#Ss#n1>6z90T*;gXbGeRXbZz1j2>SvLR>>qr@V2Q zF>=Swtx@d9U}~cWL;EJ}!TppU8*Ue+jyi_jNu;~fEI7*{rnA3WJXTfH(_}F>BsK3C zqk7J@Y&^K-z@>3;HoI|wzMwhcld(Mkc)zCI@msxOl<)@;qndg&TMB1~#Ix*|=gEpl z-9MX4tj|ozE@NAPZLGtyl9O}CcW?Qf>W@USJx)c6iCLS6|FXEaQA)z|U(2&YIlanW zmOA7Xdy|;ffzmUcY+V!bpNr*?kUs%Sg${7d%J(ors+t5A;F^RkLD$NkqNl|yqN!p# zc=oU@s*Tmjke(%y735)mX4rxp33nD-8)5i zE0%rt{$)r#9#8XU`$by%VaC3)(lmu2DTs`iM7c45G+B9Bork%Rj$$tazGCLP*bo_5 zz*yI7V0j+NGhQGw?T6l;8e3&@6IoewtAJR&IgcK*`m6+1NRg$PhpviQK&Kn#KXZvB z{F$NU%#v@y@%2#k7izBckLP8z)Iup_osKq6^YbY%$S%M=f$jgk&Ta?VX%URvW{*u_ zNl8&MbwyTD1r_SiVmklhE&DQtKQsY)Ja$bxqA>Nfblt5mFMz z9$Y*^b&;=zZM*IsDV|fKsrXM;h;RQQ=;{rhY(dw~etj&-P`2dNf${kOnfN95ixPW^ z-EHZAf#g|X^v!E`kQ6m32T8E43U9jmh_^-SCbU=EZW_=9ubJ-ny3e-qVkTScyrT2c zIMwwKOnC99MI;5Lk?sFMFW~}i%>H_HlqLP-YIImiCU&?MJlaqO#j%i|k6`v|NO@dy z240MY<)^;{CNowCW(pUdpwiUUlw)d1QgcaxY(@?J+y~>tw}v)(YLsyHDk_2_O1znG zH;r05!1MCicI228@tG0^%iSZ(WIgTLw*>X0`#q)R?ebW&T+=lnYUdSj<*+>9MQ>{P zQcKK%W@nyfIOrq4Jm6Cb>ZAjgn+q3b|7PM}p=ngK!EgB|;iBf0IzN6`93#EOD4TkS zj%Tf(9_FJ#pThlT>Ee>?T5}q2QPHC%#>hbb{4M_6Yra<;CJQRE7` zzy+g6M$4(6W`T1nF^Gt#h=cLpT@oVaQjIksH|a)4yqjIe)WxDy$*FhcmikG5%e`|c zgMIsOIny22%`MXRU0e(zOQ;Gj^2^YkLH3!AFvLm=0?FRLm~%kZWrgx3)hC61?EE4< z+2&^c|A3*w3!8Z!XpA1~P8;sjb`T&lr4v|5~1EqQ91DPDKdnF75R>>aDRgHI_ zBZU+dl%3uZu3=+ZL)x^#_@-obZXH(4V#^xv9|U;~HS%1&NLXi} zGT$+$yXkYn0n}@J>J8D2=#a7cYCgh02H#(44iQK~b_&SK%hWG^aZ2r z!2IN7CdMIhNqk=R^UPrSeF4OV^<0oIXHxn3L>LpE9oP{^m8o>*(Z@!i%IOmzHZz&) zZ@vA(bXlqa_xd!&BJ7yM%;|Q=4%%&1A==?+{upJxBY<8UuSnozs5C#T)OObV#*_{?1ONB8@YwgV=S4pCHq?UUm6~5E1RIRybT5k^kJDoaAMMF3MmM0 zY+_v^?mO<$W>TK+!&v`I;E z`;b*%^P(Q3!>Wq7ymdcz93})~LtfV7vR3BeLuAwsS@{WKspj5QDGBFxehm?V|3VQQ zx+kZ_eisl6D+`(Kv5zr0g+rNeDgE)qhlb@TEw6_EK_2=$7%mcOT$eEBw`ytOIIlW^ zB#JDChENJ6MU;2CnDaMtzGX*vuXrVE%3pcT@5B?(!*Grki6LzKDl$K_zymwq`4a5w zpj4)~yE-f460Wh&0{9jUe?@MM@kRDXEf0OkbBQ9iVlh#-AR*@1E=ZJgExITfn)C#( z(aXwA8m+T-)&hg(Hd!{5A~8P*IJZQmOM6@(3Vn*XOc%kXo$klS)sHb=87o|eHSfIR4q&@5N4GjkSsl zwQg<7Ev+B<9~}hPW{U)C;P9mQv35IOI1&w+?O`Emx#Q3U-QGo?H~lFn4x;ceC1z_Y zm%qT^(gOhMX)WkB|0@r4I`W?k)-7v7jgu&r){T^o<8?F-e?4l?#MR_@wltGP5a57+ zJ7DT5`77dxtTw^K!5RsXSiUThcP_r{wUMAyZPY38T!{AHHzCOgbZlnI2CqK}U7s#K(jGJ$pK<8JM z_6`c0}uKnt{g1{wKX0T5O z%AS!ulYqBvA(4EZ&5R4`ZJE0qK}{@Sspy}wo&whFpMcbtQ>L{hROnf9Lu80wj-Gl> zAVw9Xd9_->Lk1x+b0GfT*Za|!SiMukGftA?a_>hpS!EQwrYFkpPo}>`L^>VKCI^Ih zmQ>rzcHe8{sYxn1)y#!{znm2>2MUWKLYqvRG_qS#KnJ@pQ@iO`=Czh{(_`IKl4>U) z#3lr|^;F;Oqly8xM)ja-@vIzWf%_6VD672aOB%9J%YsFaIirvr#!9C$olNk5LMA7k z@=6zhTn$bh41XO#n9K@F$ddysaUmaOUg**rlZ;IzEPI-O(RUO+9^zC{4j8} z?q}^X-@|!vB_nS)lx(~zlT&X;h_BsbBjnlUa`HE|&x3zO*>DtK`Mrr-r-#VBhN@E7 z*HPA!v3g{)?dvo^yP4MtO!<*{g%d`Z98!=p7C)=j8L<@N2U(kVv$d>Jb}4d7Z{WaH zf;7P>DJFO{$i8QPbf+;Zng@ZBl*+_eW>vkcBMY)4S+33&_L&3_YpC1bi7LxWql*yJ z6aq9iAYH}}%YJ4sBx^;sUpLl74$rxPw+%Xt%yUR}*+pTQF!0nf>nOxcO-N6@kWm|4 zFwJfkF1gAz?i3M-f}NIj(0YQC^-DAZ?f`V8e|ai2h?f%D9I&g+{AvyN_emocQ4-VB zoG7>&)(L&-G&@RYIL`vFg=N*ttltt`mTsfS#(=){LvbsA;pJQf8@p9Yzaw{{ zJ09I-KcMiX;chS|hx8js@2S2zXWUw-6UOS@8QV^$E7afwj6)zlDSo>PXM7sTqKkg`O7`6 z*u=>wuY@U)%WRSek9&gk4-h3DjK<9i-RsOcFj_7y%@%88UvoOHbMG~>BCUqbewc}PeMdK+rl#> zfvxw3ibK7hQTf0|DF0P2qz&%@kD3@;3oX;T^4p_N#-&6h(N@G-imhexOY z(FS2?97|)TYIe6tG)%-7?t9EB-x)%D^tnFIl-}SB4m?_%JQd;rS$dW#^A%k=K{(HL zPDeMmFjJ*mLakaMzC$d|Te@3s6m_1#P+?Fbk)w%#MLvBrPcO_uw<8u9*d4!=vb#O) zdYa3{5r}IPMa&P1U$s*S`2)^j?<;4C9$6XO?WvcH*uvx$!N{B&n)&ieO1O{G-(T z_3AoJ2A@c@i302z2}2yVORP^pAn(ahAuE-W*CSHc?o)A9cCZU1l+xFxmP%QHj2`9*BQwrW)&Co!C zLQs)zGM+eW=HBuoqq&s34!@Ov{k3PK1mp}o{{V?!tza;+Ut{_iQg>!SvR} zZ1tF9FB`S;Da>IO924@sdrNV--! zEOju3ZGi6c_t6fE8ubQ38vLiBg-gfR$_#haAdGcHQRnJLhBTO;;StAQs9vr42*QuG zyW*sgOE8j{0CO~GLmy1wAHIU#ABgkc@BU-g#Is!!Vi^xHH@czOq2k=L8V4xTNA~sF~X1X{%%i0-jt9`R5>Qt--{t@INucy71_K2&L~fbAFC* zX2mqBRzxZfE`7kCL8%^ILyWESB9ei_FydiZai)^sWr3BXEWE0x85{X^(?Yh|X!`*A zVbfQ=&YC&cv;v&+1?lCv`{}Bq8LxT8%N@cz;0^@1-y)J)YIc$}Pic}+If@``laC>i zKDrKP^;DaUNKnV8^A4Ye;I>=?r#s3R%vrpg`?TP9tr&}p77gAguelWB(KMw;H8b;) zI_|n*O2@T*;~4tv#8nsgr>K=xd)6STgxn1rVxS2%0lYCHM`x$HRH91RqllHqFyA@b z{rmMj8yfWHLty>ptG^0UMQOOSj?+}dPT`%l1cf>_pE2{00mc6S5)P*jTx@<{%&4pA z(vkq`%%>i{?QDd{19JYpx=LvQiD!-C0f`+l2E6wji{^SRPHKdX3vDeW(PLOdkvKTz zOl16XsYTh=%m_t8#FE9r)}tI!cdZoJ-Xg~$Hl0V5=oERTzMM zMy~Wncp%CxQS#d4N}Z}F!GR=f^ug5D3OF&Vn4Xh)tf-AzIpcL0!36EsOxV3t$N9Iz zO^WYHB2N!_PU>>3QLU@wbrO)ZG|^NH?(m*gBj9$>Mx8;mFsCckG-`O3 z*--+k#XLk{6=?D^>#;hI_I!Z#hB;M-0Lfh3 znG=$xnU6P&23`L8YK(yMaY;kWDwgj@M(WViQ^w2JmBP2!=^Gn3rWXt{;~gvVf!a9c z_sSAl`6h7KPFE#|%xUkU!PG5CnDf8AnLD-dMO0AtmD7fExCgJylh@^_88pboluZ^L zPDNYDd5_@Ys*3t>(K#R=%EuVc4wNZ|8r|Fv))h%nOrS zeEBafo-C0e@VE7y{r> zJ}0YuNsAgDdj1_hdCc9d@bKa8(XX@_7=o@vUR{Pc$sb}hU5@tD)8QeMFzzK^FALIF zTxFiNXOd2H%7EYn^!p7F^0JCq9H3^n1)iFhzG%WK#ojWE40-DQApELzzXD~V0?Aw_3I*fAY-siY$&O2v9zG5 zAdSJ;k*9!4aX8R1;HIXz(Y;HHg~Kk`KK)3^xVGAY#s=NfW#&lh&`BB|WdkFqCrOAA!H>uTi<|1EM4*6~ zcQ_oQd}#;7VxNRX-o|9EL|_$<6Y11>Oj`Dqg_>n3(YC27)8NfRa_4;TFlvOC{9N6uNCVrxR z0n#|T3P_^Bjymap_;__c!HH~{@7PTL0E>XR61~59$sdTa7q&ho1azuw{dG%^n-f`p zk%>N%ov6hmv(7wn6O~eTAn6!xia2Y7cxi0Z)f|JE%^`3;I#bK-f+mH;d{c0W?PrE5 zd_B~-#(Js7PuS{SoqV>j0hPC}F&`4WJzbusxPRbNLC6`Ks}eOgNC?!W_dles*04He zLGAj=vg

    Xrad@M^o?C)lJMX6lJAOXOSu*rwVE!03c(w%)hZZ!Ws+C0Im$pA6m2jQnCP2;2+W82;nLvNBP z-cM--&OhcTCm#&yj=%~6JyhMoJKCwL+5{0u$h)(yHq#om+;}C76bjlXnR%d=5=$7) zK_0$Zf1$|h2X3JBg`_o8$4bz`p<-_FM|;=nuAnaCho}jZ^%Y(Gku@Wt^(urD>+A2* zDlG$XOB*7fZ&@^S(bKgov7jXLcX{T~f?2R+a{a1qw;}^8yl)V5;RO!f{V5m3MQY}z zirJ}Nc#N`t2atflPlqI(^yCBsQLO&+ft+`5(#yU}~X3>r&Bx@5KEg`~W}y0HP8pJBs;y{Z$UGJQx1%Z=jiv86LE+Z{Bi; z;xW`OF z?zLzS8rZG$d_5IR63H*MBLZ2@0R&^Frsy&=>IUJ{_?7-6jAOvezc1ERb++(8EY1_i z^ZDyuTDnYib1kHtM=evt#a)rGSFU+>{k4Q)%G};5sg*2Mm2`z>9qS#>KU{qD)^M%F zeX$f~Tr{4?GsQ7jg91CU0U6TeV%&+??RYRQE9IKqKEMp)3)50G=WBxjs3fV7S}K;F znlyOjkfQQsM@?Be(?~d&l^jEoytNES&`Sic5(ZS9=vxNjNsh7yNHt~6t~84qBe_<} zl~N9fsuKC@5VrNAA=EXAHB4=18*%hj>tuz z)9Q-KGZ!y9cvm?sf_>V0p@{4z+FJTasn$g(8Fnk)42>%(x`S3z$eUoA8D^+{@jaj- zlnUD%9W<{MQAy_Rd3LXkrD0(FvLv1^cE;Z=6%?;9OhNAoNX2ChQ0F2%9zwf={?5Cd z*9P4hTyx>aE>EtCIW;5nj`$)MlB%Uq z(gQdXWDcGt)n2^q3QV`~{{U9;^pg6kM0HmQ7P;k;B*7(H9-RpI>Y;KKLAG3E#y1_u zr0eg(XW}A*vJL~e*(<4~sDepedNUB_C5}{vJ8gwuzL|?YOb*PS9Ds=ZBlj3G2`3WjBc*`eysTQ7ZcBTBh95DG12kBj?jvv&_bW z&oawec`0Qik~8Dq;D4sEj(1JC;K@zqSeDfhN`eSG1i{a^>I&ZKB2OSP*fK7V&r@_0 z)8Un0bz_1xFt`gwMF_>F`^77zwpiSP#FoZ2*iWoR>T#7?mHnNV3D9T5CJsF{ahu*& zU_P3Tsb$(|`{2=qCy<@ap^?ZkT9HIRtj`0GaN<=Y9rpcx8Z+Dqg(4EN%Fhf-CpcLT zDPy|lu9?9Xao6l7n~g!Jc>91Ook-_U22TAz(&GRFW+U@eHqt>nle`FHY#hHZ=rs0f z7jdempqf=-g=C3&T263tpDwz6lg-Ww7qoRt8a${P7#&ZAfdCLabExc3V+a5|kpiKm zrk&?FM=H#vT{1n|KI#LQ9gv89ml7rcve|Bxi}}WV7CC=xVShrj?

    zECm$URb2ZgMwmRry+6!GLb|;;PjZYyc4_{uoE59{UlyP@5 zWsYikj_F8+K~v1xE$`EZ1VA$umHiwtMLkmX9i+pv9>n7uTBlb^N}u>g(3$ zU-ohT01r+Nx^Sd5SMJjluT=9(9mD?sPgKQGMN1+v%w@`cby7Kj#t9>)3D0HmhihO0 zuUdY!$4-0ptGLsafneYP??dO`tmjTI{4aKg!SF{=lug98N`NG!z3|MU<3~}I3^`gg z=3Ist4ysP%0yov39~IK(9I#gI4+MTc$*4te>$>Gx6(IJgkj&$s#mu7wZL4j@xe$Yc zM8*)s7-=U;>0xp?TPipB=vHfOLQocNED?gHF*$Z)$`~lyub!ljoB?C+Hq0Jc==tYt z#=aIzV@=>G;x$;^7*wz=Eh^0&TNXKIRZ)x|mYNurs)albvni=0?h%@xuw>650|yw# zm&Z@AO+c`raJo2DBtPDr83%f&kRDy|bi29f7zZ6@x;X+|gu-t4iL=kjIt~8-eM(-C z8~GA{bf6T`4(?ufc~}PN%rlSw0Jf<$2hu*XK))6IrpC3?rM{h#MlQ@FRR_yB8mQjh zWmK~N015g-{{RmP6{75#^AG^Ay2;Fae};!2Qgv(K>>EB?ALIPY-;FNMEKXniOVUMc z?VpL^r$vbkTe$QvgN=0bA$Cb)+V{By<@;XIja8vNg^WDD=>~$H~{(_AJa*Qi`yRQxNg3& zEv8Amx>(tH;pWIt19NKg0)bTrb(z;a`ThPS0nprEV|b zx>+shBw^r!mDC4Ukw`5fra{|tAY+k{uRqXfUsgrfHp?zX;jl6`7@OmZtKW%mKGI8f zAYWc~vQI{3Zb_ zl0`-omC{s5lF?E~)bXcIVbe@2LYTwvx+)nCjTFlE$PBNO6lG5^{NDWi^+~H9kzVsjr=ofG zk5=DLS}5Giu)#FI6$hd6`~I4wvNsCmTF~61wEYqM081bL08H=vhbXsK-~RyHPwA^q z1wZu}FON_2Grl-1G86467+{imspaNh zVQC#@Jbe9eURg$Oo^ ziLX@kHFar9@vxRzLaTJ!ApY8wj3`oMOsvJIh!1I0^^d{mm2KU*WnjLCC;IBk!kB%g z*V9Gv{S`0Tny196zwb> z-#Ku0Q_R5rntl+Vl1ytC?H5?MrlQ4ISVUx2@R*q3V#sZV|t`vUD-gL_WmXyxHcg{Rr=KuBuo_L15|c+w{l z#u=C+42CK>ST+e7e~#)jz9FQ3;5Z6tN##q=Yrrty3yRgHYL+(GxM9psC$ zF2^nW8QV7&_J!LOx|YGhG}l_uZL8uJj+QFQlQa0Eqi|Y7%goDxh1ia4WnB8xlShL` zfR;N{GOd&FfklS`x*z`lM6PaqFVso)4B=sbcNw<7nStA=<7|gj5OD@>A@ZoOZ zZaD30hqz~o>td(jdg-JUaRJI67CY;mAZW(A2IRNf?4K#FbSWFkm(r03Gv^$Hbed4S^ht`vW@BGN@YVU^AHL z=akBz2JTyLwu`p=!8MEYo__5G?V=Rg=$`azPrV4PX=0%Tr>&oz?#U51!Jj zmR`>Ip51z`j)+Ahw2 z6n4`e0^9A=C5Fz+Kef)PDlUvh8m3GS3C}hVY@py0NhX^`;JI0w#kFsFvT;K8;~)E0 z`uxfGzLgDwP}tn2LCiyb;^9;%ZUT;2Vn$LQ9Ty|to|)D1UELxW5l!@j*)B8`HA(%W zPLk!ajLJOr+e65%!_Z+>kq@j!d`BG|k%_4QF@?&GZN`C!FyQrs$xI1elC!Ge%8R|y zNg`52TNVTj^?Wl7X+~7z(wxp)+HXz;u9)S6Xa2uGE}9qG)p(P?x9;mY3$mZYPiuXb zS?XBXd>GZi=WKp#D+d}@ZtKpz9VZr|Z zeeOg%MwMKq|Wu zqOC${!K8fT5TS?Pf1w&E@pfLaEBGEJ914-z{>N|jc@j!5X08ZXfJZUNNys}k$834& z@8Y;~uFRZBso;6E>~dl^#6xhN1_vn1h}K0T z^!$2EF4r2`zRoXH*3^n)Q%_K<9(-k4NH`mdzr;Z=q+Ys1-_)#~Ol57dXa200WGZfs>6z@f`Y10|eDKF2Dg>i8t5} zr#{1;odtt6V_QoALO5H3xPyr*cH_Sty1oJ!BMSyrao z8+T+kd=w_zY{ysL90|xp_WuBXSxMro7XSm?e)1t2@yLj=M6I=wt9Vyh+5Bl0#MC5%Q~K zmSMThl^9o9&IfqT(FIj~x#p^w3{k0Yh?w7?86NYX?;F4pLa(Ca>c_y6ML?h2Ip%*G zmKogdkC#nV=GCcUFNvbk=={86i1r=_sRJ)cBOmnRQBI^u<4${s+TFqmtOIf%a5+Dw zfZCsEVT0u))O;;V7y@I-{{UhBnvB`l!VE*&Hm9&#&}@YJ{InC9cmyl=~-kG8~9KS43)4`AZ_x*sT8K@7kqyT_z_4l;`Cly{YzR zd*O>kHIK4kPIyumY3nWZ4zX2SDmVT!NKObD!lqOv{YfW7@NzTcI{yG0KlKDq?x?@} zK;pmo9!WnSCUN{HQ-+^S&$#~p%&B98wZ0j<(_QFzR;JTkPN8F|Nzy|bWqe$byD|7+ zjcYR}h3hc3pEf|~-9;X6>q19-9w0%yHTQ%16C_+)UBXws9FqS47A%}?QzEVE*swHl zR5)F#BL`@~!OCm_Y>v1Y{{Rie;#y5X(XjJ0!{B?eqU&pWk$g8&I6WX$JiW@@PgxJC zyvbcfOG!^M{{RbeM#Ztz>q;?zOPKQIf`yRI(U}Uz+~B&(hFl!r^0uzLFjEqJkLo2i ziyOoM_aC8=Nl)<=qiym={zFknS`Op1Lxi%~=|r)y zP^wfPKuFXqp1Tsm7}x}l(^Q+K&z^ahM2(ze@21dIs){L_nRIX+3vms~nu?M)nwk~w z<^E7{UfsgMHzC1>^YY!!+0AncM$4%Mo-x5HaxzDYYQIJUTYvc z)fEq)FcUwnbfhWaVs2>Gj=DzWPF`&P0EvIoL1Y3DGe+%gX{q^&C?0&NzJt58b+iaZ z@i9n)N9B?R`hRX`Q%vEub3ZwaW`FTW;C`n_PtJDIV@;rlhp2UMHb_t(u0N)S$JTKb z0#hMkOvQod9ZH|3oqob5*hM`Yl^G#bhpJ~ku9``mP|8~~Gv$d~c@{19&NP-pT7*>7 z7-Hq`9T($_{q@7F>WV5Uok1oZ`F(fMXc^HtqKW1_=#Z)YZu4yukV>?^alP zF8WRF?H2A~TT4w(79(XlAI>^^`e|0%sEs56Q&6sWaf_84Bd#zqKKdwMk%O>is;TNK z5O-CAV1K~M2is2z&oE`S@FaTmXQ!=+lI3iIy+|XPbVlXa^dw|;Jvw=6Qf^Wi8jOvQ zW5_Ai<$ZbVV~4H zwpLO_)Xb5FkN*H5ajVqv3>g5XnarLYsKjWjd10VqaiW?30OiRpK3V>nfsud|J4;+6 z)(Mi1Bd6!F%&HGGQJ$XW$L*?+ZMl|>@WdQDW1g!Lx`m1`S%Cx-_R_lxs4%9tPKxZp zo(nayqP9p(lGGJM9ROARchAskGvW?%IL0~&vGCj~xctM^$u&*prdBHzN3$tE$f~*e zYPe(hOLA6ir|;Gh?N%870FOC8bEG1TRB$4u`Y~Bh*@ffVt4hEhpvlt?(bhRl^o=50 zl-nc%a5}y~U%oUtQ52ITwu))m9%&XNBdOSp35w3M8njW_PInVX zqz|SvQWvyn-5tLaNk>l6!A(kK#_VTb+-PGEW66ljL`x?a)N_@ZwwfmY006_2@Z~x< z#Dk=6=7ie#g{6(!W(c|2<&1!T@+9f4j2@G>p7Uku{ZDPB4J4GZGj2Pq$WPZAa$($f zjBJ!HM%Mdrym)Q#K*aw5Gi{IlT9f4}d{RG}Tcj#36T8-_d#uUb^$7&rtbV6lT?fD? zQy!)VQ^IRfrcqbyRM8(7elUKcW1=onIH)F1V=$(YlAJ2hSHo0E>CcdU8|gtOtU|yJ z4A*?Gl7=v`qol36zfYF$qarJJfiF>Y4c1DB4?H;o^GV7HUyfMnLBU^2s}I zu-Io&*@A|1+COQ1sJ+bS%1$5lIv8okv#P{_Fv?&3on#I|%vkrEv_XwRFKLw*4pN!< z!jg_jR?Eu51F$VT}Y6#dKtf~kfJZXRv$uPoZ)_TXLD$=bqc-l36w7ZlZ z1YM`zE~mD=7;g5iNMp}P0A6pN4xYyQM)1AowKZ{UQj$y!yXj{+KP@x^?-+7L$#pT# zOfgLq#=@UEkVt%`6835_bCz?zW^~O}z;;BzxJnX(j zKm+Oz+fPh8x`-#r(Hq|{97gK2RV4{M8T>zO6e42fga~GShBSkK?yjO*yUHd!MN9a8 z8fna+Qml52 zv6m#TxNV~Z0DLox)OeOUn(8&1`(cz0jIxwmd_44ngVHdY<^*mYz{?kRHF-ig=81d2 zza=LhO+RUFv4nv&@jEr*fXNh5rz%!-0AgD1~aCXwcsWtg*&nT04f?_rM`noE4)jT z9d$o-Jyc)d@c#O{Q+&*;3N#~|bcU{+jO4Uf$4vCpcHyDJS>{go8DXlgimsu8!$lb! z#N#qI^cmGgw6IN!)XVP;P}w}g{yikoxYC?AaY+od5)Fqbz&}0pPBh1>aWo!s{{V~% zekORNKGq_oJ$Yr2mOlZV5MWy3cbsOpxT5D)=h$UI z>m!6b^9qK?DhoNFcE%# z)Ezxaw*nz2yx(iMQl3_)t&XlnI06Y)NBR-^Y0Qn%r*0L*S8}S3s_2bSW+$5gq$(JH z+nkT4o~po55M{tT&DBxUR$@L^okvw;{Wlumr@SW&Au-8KYlTG6(V!#1wFHlu#x~OI zC!sKGE2P^`SoLucU{S9l_3$(pX$8JdzcwnW3m=)K@S_5u!+ zC~gdCY7sPdIIBCuJwjG{rH)9>J=uJHv~Z+Giaf#@C{~I&rh;xld%BzI zJ48<{6!le&g^q|E6;oG+1J|xI?$K0%Be0&c#6z><8eg6RN&Ih<-Tof>}BY~)TamCgmGcwH5 z0pgSv$G~R;>7k4$-9%Ex1ow)ua95I1Gq~NPQMbqpeY96lqJ%{@WT%I#r>pL{-@hwe`C}5_J5S1G@GMujdSR)!{JZsiH*!GL7sw*I&hDEs6WxzP^ z$OGYwV@6QE(mP+gZ$ZTsyfARUF<>oImVTn2OI}@RtYR8yWBnTC=jqco4#Iey< z&Z|#_Vz93vIRN$R=ck-&j9S5NCApK@-UYE4JNLK6ET^e_klT5Vhf$Y4LU!CQl!RSK zHFb1G^98;;bqo-NBs5R}!yf%iTdO7ofCqsZ7{xbo@RJkc^CsE-wCb^OE{_p~m?c!@ zXGNL0zdP=KrkF)^ide^U%+zzF343?#4ne^UZd>?NMQuI$%2*@fhlLN+{^4U#YL zh%9Dc;1&X5#!GkW{s0A8(j9#r85lP9bN@+X1@d_Ud_r7-^to%tw(i|JXX%`pe3@TB zK4VFcUoeBivm0qZj*h2`V@Sm<-=0QJ1_Pc$L7)Ghf6XQvIzd4xATZeB!Bdle$G%PN gHo9{9=GyJd0y7w*QkgdQ0rfF>y85}Sb4q9e0L|b#qyPW_ literal 0 HcmV?d00001 diff --git a/docs/floodlight.jpg b/docs/floodlight.jpg new file mode 100644 index 0000000000000000000000000000000000000000..449789966974ba2831fe11e00734e6dc5da11c75 GIT binary patch literal 7171 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 OwG5uFelF{r5}E+r$3Kw( literal 0 HcmV?d00001 diff --git a/docs/help-top.png b/docs/help-top.png new file mode 100644 index 0000000000000000000000000000000000000000..5c870176d4dea68aab9e51166cc3d7a582f326d6 GIT binary patch literal 786 zcmV+t1MU2YP)$XgYMs^AIOw1Qr{*Wn)N-{9ma}x2(<~`9Go1=*>YR!KZvrBS zCd!u}@M0og%Ev@_;Z?Kk>Wwv=%h_57zmt2<_1msz_niYE=YRNPpd%02TK9oK1z z>ooPno}v^sikz_|1XHFx_L%~;ljh7i(jiay5F0x*+(9aXXFCl?AdQj5XlQ65%sEv+ ztfe?|YcjPN*@yYtE~ImQh{l|#A6Z8iu>pf43Rj52CzU_dMQm|S2xR62YjQOn+z8WH zaK=!}ggOZi{4pB7SQ=xC0n|vXP_Bkx_a)FeNd}w8U97BNbSWxa^QW-li9BZ#M1!_xE*?wzt^GcoeoL*JGLSe_+l-JT2#2tz!z&^ z_s5anq&^nBklIMwRvcoP3%qs%%Ea?1c{_*V*Xj&~uLu-2Dp1fUN4<0zMo$EH>*U83 zm_9;Vt%-bE{_J_!If!1y=c+`QVZ>0_BPy z+%^pgnv`f8H)Z%0&Tp8&u*MCIC4igNW5MeWM_DHpDNi)Zxz|9XboOnitwFq$ETN=X zj-tkCJnz**Y4k#6_Ty^B=hWo~L!47r`HoP=x&3T1)JLr2t2+#fHZaYC#E*VPIgcU|?WnXl`X?yLa#T`}g00)U|8ZfUIBqPD()aj3q&S!3+-1ZlnP@k)AG&Ar-gQ zo^#}DaNuBen6_=h>i_@Ot8^XfR=1pBpvb!Uf7c4OZyZ&OHys)qzTFEDT4JLAL16i| zl5@fvlwNi%JtrX1KIqEP)v;U&v3%|^C`Ga3?LtY&4dQB4Oz;1v;J%z!D&%WRH@BZ?x; z3)8@IUIv@hG|@IwyHLC`l{1<4BK>wam95g|i|?Cfzt876&-Zx_0f5*l-9`IJI&mHu zE6$@xB)6N}7VeR;!X8D!TAw;;&0Bsj?A071cO>X3K0wl7WZ1;Tg!4LHyNcnzoeQ7t zNW`aSlm8WXYkek&ir$13=ngczvf zV0vnjNpCF&K8px}dunv+`LIb-sOC$_jD(;IBI$xC|7`(+9cA>Vir_V#z{?k7SX^Ah z^71m~W@q439Ycqfhi7+gp#A14n1n1!e>$EdeATG|f798Y=ggzwEKH2Q!qU2QA(Se?dwqG69%>n$6rtE z%F(845Az8c{w(XgimJg96!jLMz?zS6I1HUm2baqQx7&@nx;lhHA!r6vs2|fqJETOu zLxeu2OQ(3(au%dg>AcZsWI(zXn9XJg1cLe8k~0h0wOL=&HK}7X k{AKr*U4z7Szv)i%9gTgghwgU$Q~&?~07*qoM6N<$g31kYk^lez literal 0 HcmV?d00001 diff --git a/docs/jstoolbar.css b/docs/jstoolbar.css new file mode 100644 index 00000000..9514e3eb --- /dev/null +++ b/docs/jstoolbar.css @@ -0,0 +1,95 @@ +.jstEditor { + padding-left: 0px; +} +.jstEditor textarea, .jstEditor iframe { + margin: 0; +} + +.jstHandle { + height: 10px; + font-size: 0.1em; + cursor: s-resize; + /*background: transparent url(img/resizer.png) no-repeat 45% 50%;*/ +} + +.jstElements { + padding: 3px 3px; +} + +.jstElements button { + margin-right : 6px; + width : 24px; + height: 24px; + padding: 4px; + border-style: solid; + border-width: 1px; + border-color: #ddd; + background-color : #f7f7f7; + background-position : 50% 50%; + background-repeat: no-repeat; +} +.jstElements button:hover { + border-color : #000; +} +.jstElements button span { + display : none; +} +.jstElements span { + display : inline; +} + +.jstSpacer { + width : 0px; + font-size: 1px; + margin-right: 4px; +} + +.jstElements .help { float: right; margin-right: 1em; padding-top: 8px; font-size: 0.9em; } + +/* Buttons +-------------------------------------------------------- */ +.jstb_strong { + background-image: url(../images/jstoolbar/bt_strong.png); +} +.jstb_em { + background-image: url(../images/jstoolbar/bt_em.png); +} +.jstb_ins { + background-image: url(../images/jstoolbar/bt_ins.png); +} +.jstb_del { + background-image: url(../images/jstoolbar/bt_del.png); +} +.jstb_code { + background-image: url(../images/jstoolbar/bt_code.png); +} +.jstb_h1 { + background-image: url(../images/jstoolbar/bt_h1.png); +} +.jstb_h2 { + background-image: url(../images/jstoolbar/bt_h2.png); +} +.jstb_h3 { + background-image: url(../images/jstoolbar/bt_h3.png); +} +.jstb_ul { + background-image: url(../images/jstoolbar/bt_ul.png); +} +.jstb_ol { + background-image: url(../images/jstoolbar/bt_ol.png); +} +.jstb_bq { + background-image: url(../images/jstoolbar/bt_bq.png); +} +.jstb_unbq { + background-image: url(../images/jstoolbar/bt_bq_remove.png); +} +.jstb_pre { + background-image: url(../images/jstoolbar/bt_pre.png); +} +.jstb_link { + background-image: url(../images/jstoolbar/bt_link.png); +} +.jstb_img { + background-image: url(../images/jstoolbar/bt_img.png); +} diff --git a/docs/lightning.png b/docs/lightning.png new file mode 100644 index 0000000000000000000000000000000000000000..1800099bd2096512ab19633ee25907ad7b0294f2 GIT binary patch literal 669 zcmV;O0%HA%P)G12gJvsP#1y->c&;Ul`aZ`7NHeXN-SbgsBP1rF*dC> zVkyMh<~7lD9+~T%JakIZy70o`KF_)5`@VZQN-6%Q3D(KvQ&Jl`ISvmtBJK?uXX^wE zqtY|-k*#f^_KsZ__R&(2tx#}+8=3Tcb~1c1;t=WOh7PI%gbAGTw}(2lT%?65neE|+^-tCIf&-XQR8b{3Ao+;2 zkeo{{E~H*ESGNe^XlxdEI1GG9Q1sqzAfW5zu2pmhJJUZhaw7iy4U4&N<_ok(&w*Hi zZu}P>vqNW&q026EwrFH&Dlw0%Lt)7A&y~>r`I*>Z@5yd-YnqmRF@~E@MqqmKn&!+Z zTVPbNR)94R!@+@J+<85L0vk&VDuYsmTE!nbzXcco3a|4CzXvnK00000NkvXXu0mjf D)D|oK literal 0 HcmV?d00001 diff --git a/docs/newspaper.png b/docs/newspaper.png new file mode 100644 index 0000000000000000000000000000000000000000..70e7e5a0b07179ed375429e54eeecb6e5d153d21 GIT binary patch literal 581 zcmV-L0=oT)P)+S<4hQ=EKD=HpGMNm9 z!y(*mH#{B>7K?@2TP~OAbUH|<)2(K+`7U`s1+7v*0=ZmHoy?)<+66~GpI7dDJ|7SG zbMHW*-EJcs4kMe*qF%47#$+;yU@(Ysxr}bNi%=*;2hcomI2>#TrBX?q8jHmq$m8)C zE|&|%Vo^DWMx&5CLxp&=rvzrR8P@AH)gH*x=@d?<6Z838)tMJLJfP$;N+B9Tx6l4tyt0d3zF%8fpq zqTEZRQff_yb_R4nH8$}3n8SS@tm|CpK+nlxO>DPY+W|x4xOYJC=lbeU@;*OdG#Vii zi3p}m_0?(xorm04zc2^Ex)zlV1#g8a&wjuES$l7EKDOeMMNREh!#C4h@_W`2wGSRY0)AGf*!P~ zO;K5kpeTe|Bvx9JWiJgatt``dHagDy&)X)a2UK@^&pF(4&tXJ_e@%1W>v86BwHU0RA`f z&if_yQW3ncxVkz=DTzZAN8zcoQ0H4;oBTkBW^jKMNXC=P=KK5wu*adZD0CJ@24iq| z)9`sK*UjLhEaPUqFs3uBEqjHZYa@e{rnHhb3E~nAnnubBL&Y~RaMi)sg{P#(=9mEE z09k;}%WLZ24h(llL!Je`QnyXWh@_I}3BA7)8I(?BQgEL{xg3nuRg|d@%l7r3-nD}EAKqL=jEImvTm0DWbu9ZciSD7ol^^mU($QkNGeGt1Cb0e8PrHB-Zs4Zo+HoBUXB6%Ux8O& zfZkDnaS5`!s4@FQ&(Xw_@dL91xpPu`giL-@V}q#)=~S1teEUinO%B`7tDFFIlYs~p z@SiF>H{M-X5vY4!TbcV?;a-2%(HnZ&ICZJ^X0}KzEiJsWvs1)kF`=reFbsn$z{n80 zdMKAKITk2)NRj~FeAej2%i-f!HYaWY0BdMy5TQ^gU(>WXmSq(Ykr#jmKn?%_z=F%~ zlFHZmnc`Bd@!khCdN(9VQq<*g#TgNyt*wovy>pR*#O3<-TAv*v7002ovPDHLkV1lX$bQu5u literal 0 HcmV?d00001 diff --git a/docs/prototype.js b/docs/prototype.js new file mode 100644 index 00000000..7c12fcf5 --- /dev/null +++ b/docs/prototype.js @@ -0,0 +1,4320 @@ +/* Prototype JavaScript framework, version 1.6.0.3 + * (c) 2005-2008 Sam Stephenson + * + * Prototype is freely distributable under the terms of an MIT-style license. + * For details, see the Prototype web site: http://www.prototypejs.org/ + * + *--------------------------------------------------------------------------*/ + +var Prototype = { + Version: '1.6.0.3', + + Browser: { + IE: !!(window.attachEvent && + navigator.userAgent.indexOf('Opera') === -1), + Opera: navigator.userAgent.indexOf('Opera') > -1, + WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1, + Gecko: navigator.userAgent.indexOf('Gecko') > -1 && + navigator.userAgent.indexOf('KHTML') === -1, + MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/) + }, + + BrowserFeatures: { + XPath: !!document.evaluate, + SelectorsAPI: !!document.querySelector, + ElementExtensions: !!window.HTMLElement, + SpecificElementExtensions: + document.createElement('div')['__proto__'] && + document.createElement('div')['__proto__'] !== + document.createElement('form')['__proto__'] + }, + + ScriptFragment: ']*>([\\S\\s]*?)<\/script>', + JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/, + + emptyFunction: function() { }, + K: function(x) { return x } +}; + +if (Prototype.Browser.MobileSafari) + Prototype.BrowserFeatures.SpecificElementExtensions = false; + + +/* Based on Alex Arnell's inheritance implementation. */ +var Class = { + create: function() { + var parent = null, properties = $A(arguments); + if (Object.isFunction(properties[0])) + parent = properties.shift(); + + function klass() { + this.initialize.apply(this, arguments); + } + + Object.extend(klass, Class.Methods); + klass.superclass = parent; + klass.subclasses = []; + + if (parent) { + var subclass = function() { }; + subclass.prototype = parent.prototype; + klass.prototype = new subclass; + parent.subclasses.push(klass); + } + + for (var i = 0; i < properties.length; i++) + klass.addMethods(properties[i]); + + if (!klass.prototype.initialize) + klass.prototype.initialize = Prototype.emptyFunction; + + klass.prototype.constructor = klass; + + return klass; + } +}; + +Class.Methods = { + addMethods: function(source) { + var ancestor = this.superclass && this.superclass.prototype; + var properties = Object.keys(source); + + if (!Object.keys({ toString: true }).length) + properties.push("toString", "valueOf"); + + for (var i = 0, length = properties.length; i < length; i++) { + var property = properties[i], value = source[property]; + if (ancestor && Object.isFunction(value) && + value.argumentNames().first() == "$super") { + var method = value; + value = (function(m) { + return function() { return ancestor[m].apply(this, arguments) }; + })(property).wrap(method); + + value.valueOf = method.valueOf.bind(method); + value.toString = method.toString.bind(method); + } + this.prototype[property] = value; + } + + return this; + } +}; + +var Abstract = { }; + +Object.extend = function(destination, source) { + for (var property in source) + destination[property] = source[property]; + return destination; +}; + +Object.extend(Object, { + inspect: function(object) { + try { + if (Object.isUndefined(object)) return 'undefined'; + if (object === null) return 'null'; + return object.inspect ? object.inspect() : String(object); + } catch (e) { + if (e instanceof RangeError) return '...'; + throw e; + } + }, + + toJSON: function(object) { + var type = typeof object; + switch (type) { + case 'undefined': + case 'function': + case 'unknown': return; + case 'boolean': return object.toString(); + } + + if (object === null) return 'null'; + if (object.toJSON) return object.toJSON(); + if (Object.isElement(object)) return; + + var results = []; + for (var property in object) { + var value = Object.toJSON(object[property]); + if (!Object.isUndefined(value)) + results.push(property.toJSON() + ': ' + value); + } + + return '{' + results.join(', ') + '}'; + }, + + toQueryString: function(object) { + return $H(object).toQueryString(); + }, + + toHTML: function(object) { + return object && object.toHTML ? object.toHTML() : String.interpret(object); + }, + + keys: function(object) { + var keys = []; + for (var property in object) + keys.push(property); + return keys; + }, + + values: function(object) { + var values = []; + for (var property in object) + values.push(object[property]); + return values; + }, + + clone: function(object) { + return Object.extend({ }, object); + }, + + isElement: function(object) { + return !!(object && object.nodeType == 1); + }, + + isArray: function(object) { + return object != null && typeof object == "object" && + 'splice' in object && 'join' in object; + }, + + isHash: function(object) { + return object instanceof Hash; + }, + + isFunction: function(object) { + return typeof object == "function"; + }, + + isString: function(object) { + return typeof object == "string"; + }, + + isNumber: function(object) { + return typeof object == "number"; + }, + + isUndefined: function(object) { + return typeof object == "undefined"; + } +}); + +Object.extend(Function.prototype, { + argumentNames: function() { + var names = this.toString().match(/^[\s\(]*function[^(]*\(([^\)]*)\)/)[1] + .replace(/\s+/g, '').split(','); + return names.length == 1 && !names[0] ? [] : names; + }, + + bind: function() { + if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this; + var __method = this, args = $A(arguments), object = args.shift(); + return function() { + return __method.apply(object, args.concat($A(arguments))); + } + }, + + bindAsEventListener: function() { + var __method = this, args = $A(arguments), object = args.shift(); + return function(event) { + return __method.apply(object, [event || window.event].concat(args)); + } + }, + + curry: function() { + if (!arguments.length) return this; + var __method = this, args = $A(arguments); + return function() { + return __method.apply(this, args.concat($A(arguments))); + } + }, + + delay: function() { + var __method = this, args = $A(arguments), timeout = args.shift() * 1000; + return window.setTimeout(function() { + return __method.apply(__method, args); + }, timeout); + }, + + defer: function() { + var args = [0.01].concat($A(arguments)); + return this.delay.apply(this, args); + }, + + wrap: function(wrapper) { + var __method = this; + return function() { + return wrapper.apply(this, [__method.bind(this)].concat($A(arguments))); + } + }, + + methodize: function() { + if (this._methodized) return this._methodized; + var __method = this; + return this._methodized = function() { + return __method.apply(null, [this].concat($A(arguments))); + }; + } +}); + +Date.prototype.toJSON = function() { + return '"' + this.getUTCFullYear() + '-' + + (this.getUTCMonth() + 1).toPaddedString(2) + '-' + + this.getUTCDate().toPaddedString(2) + 'T' + + this.getUTCHours().toPaddedString(2) + ':' + + this.getUTCMinutes().toPaddedString(2) + ':' + + this.getUTCSeconds().toPaddedString(2) + 'Z"'; +}; + +var Try = { + these: function() { + var returnValue; + + for (var i = 0, length = arguments.length; i < length; i++) { + var lambda = arguments[i]; + try { + returnValue = lambda(); + break; + } catch (e) { } + } + + return returnValue; + } +}; + +RegExp.prototype.match = RegExp.prototype.test; + +RegExp.escape = function(str) { + return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1'); +}; + +/*--------------------------------------------------------------------------*/ + +var PeriodicalExecuter = Class.create({ + initialize: function(callback, frequency) { + this.callback = callback; + this.frequency = frequency; + this.currentlyExecuting = false; + + this.registerCallback(); + }, + + registerCallback: function() { + this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + execute: function() { + this.callback(this); + }, + + stop: function() { + if (!this.timer) return; + clearInterval(this.timer); + this.timer = null; + }, + + onTimerEvent: function() { + if (!this.currentlyExecuting) { + try { + this.currentlyExecuting = true; + this.execute(); + } finally { + this.currentlyExecuting = false; + } + } + } +}); +Object.extend(String, { + interpret: function(value) { + return value == null ? '' : String(value); + }, + specialChar: { + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '\\': '\\\\' + } +}); + +Object.extend(String.prototype, { + gsub: function(pattern, replacement) { + var result = '', source = this, match; + replacement = arguments.callee.prepareReplacement(replacement); + + while (source.length > 0) { + if (match = source.match(pattern)) { + result += source.slice(0, match.index); + result += String.interpret(replacement(match)); + source = source.slice(match.index + match[0].length); + } else { + result += source, source = ''; + } + } + return result; + }, + + sub: function(pattern, replacement, count) { + replacement = this.gsub.prepareReplacement(replacement); + count = Object.isUndefined(count) ? 1 : count; + + return this.gsub(pattern, function(match) { + if (--count < 0) return match[0]; + return replacement(match); + }); + }, + + scan: function(pattern, iterator) { + this.gsub(pattern, iterator); + return String(this); + }, + + truncate: function(length, truncation) { + length = length || 30; + truncation = Object.isUndefined(truncation) ? '...' : truncation; + return this.length > length ? + this.slice(0, length - truncation.length) + truncation : String(this); + }, + + strip: function() { + return this.replace(/^\s+/, '').replace(/\s+$/, ''); + }, + + stripTags: function() { + return this.replace(/<\/?[^>]+>/gi, ''); + }, + + stripScripts: function() { + return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); + }, + + extractScripts: function() { + var matchAll = new RegExp(Prototype.ScriptFragment, 'img'); + var matchOne = new RegExp(Prototype.ScriptFragment, 'im'); + return (this.match(matchAll) || []).map(function(scriptTag) { + return (scriptTag.match(matchOne) || ['', ''])[1]; + }); + }, + + evalScripts: function() { + return this.extractScripts().map(function(script) { return eval(script) }); + }, + + escapeHTML: function() { + var self = arguments.callee; + self.text.data = this; + return self.div.innerHTML; + }, + + unescapeHTML: function() { + var div = new Element('div'); + div.innerHTML = this.stripTags(); + return div.childNodes[0] ? (div.childNodes.length > 1 ? + $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) : + div.childNodes[0].nodeValue) : ''; + }, + + toQueryParams: function(separator) { + var match = this.strip().match(/([^?#]*)(#.*)?$/); + if (!match) return { }; + + return match[1].split(separator || '&').inject({ }, function(hash, pair) { + if ((pair = pair.split('='))[0]) { + var key = decodeURIComponent(pair.shift()); + var value = pair.length > 1 ? pair.join('=') : pair[0]; + if (value != undefined) value = decodeURIComponent(value); + + if (key in hash) { + if (!Object.isArray(hash[key])) hash[key] = [hash[key]]; + hash[key].push(value); + } + else hash[key] = value; + } + return hash; + }); + }, + + toArray: function() { + return this.split(''); + }, + + succ: function() { + return this.slice(0, this.length - 1) + + String.fromCharCode(this.charCodeAt(this.length - 1) + 1); + }, + + times: function(count) { + return count < 1 ? '' : new Array(count + 1).join(this); + }, + + camelize: function() { + var parts = this.split('-'), len = parts.length; + if (len == 1) return parts[0]; + + var camelized = this.charAt(0) == '-' + ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1) + : parts[0]; + + for (var i = 1; i < len; i++) + camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1); + + return camelized; + }, + + capitalize: function() { + return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase(); + }, + + underscore: function() { + return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase(); + }, + + dasherize: function() { + return this.gsub(/_/,'-'); + }, + + inspect: function(useDoubleQuotes) { + var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) { + var character = String.specialChar[match[0]]; + return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16); + }); + if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"'; + return "'" + escapedString.replace(/'/g, '\\\'') + "'"; + }, + + toJSON: function() { + return this.inspect(true); + }, + + unfilterJSON: function(filter) { + return this.sub(filter || Prototype.JSONFilter, '#{1}'); + }, + + isJSON: function() { + var str = this; + if (str.blank()) return false; + str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, ''); + return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str); + }, + + evalJSON: function(sanitize) { + var json = this.unfilterJSON(); + try { + if (!sanitize || json.isJSON()) return eval('(' + json + ')'); + } catch (e) { } + throw new SyntaxError('Badly formed JSON string: ' + this.inspect()); + }, + + include: function(pattern) { + return this.indexOf(pattern) > -1; + }, + + startsWith: function(pattern) { + return this.indexOf(pattern) === 0; + }, + + endsWith: function(pattern) { + var d = this.length - pattern.length; + return d >= 0 && this.lastIndexOf(pattern) === d; + }, + + empty: function() { + return this == ''; + }, + + blank: function() { + return /^\s*$/.test(this); + }, + + interpolate: function(object, pattern) { + return new Template(this, pattern).evaluate(object); + } +}); + +if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, { + escapeHTML: function() { + return this.replace(/&/g,'&').replace(//g,'>'); + }, + unescapeHTML: function() { + return this.stripTags().replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); + } +}); + +String.prototype.gsub.prepareReplacement = function(replacement) { + if (Object.isFunction(replacement)) return replacement; + var template = new Template(replacement); + return function(match) { return template.evaluate(match) }; +}; + +String.prototype.parseQuery = String.prototype.toQueryParams; + +Object.extend(String.prototype.escapeHTML, { + div: document.createElement('div'), + text: document.createTextNode('') +}); + +String.prototype.escapeHTML.div.appendChild(String.prototype.escapeHTML.text); + +var Template = Class.create({ + initialize: function(template, pattern) { + this.template = template.toString(); + this.pattern = pattern || Template.Pattern; + }, + + evaluate: function(object) { + if (Object.isFunction(object.toTemplateReplacements)) + object = object.toTemplateReplacements(); + + return this.template.gsub(this.pattern, function(match) { + if (object == null) return ''; + + var before = match[1] || ''; + if (before == '\\') return match[2]; + + var ctx = object, expr = match[3]; + var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/; + match = pattern.exec(expr); + if (match == null) return before; + + while (match != null) { + var comp = match[1].startsWith('[') ? match[2].gsub('\\\\]', ']') : match[1]; + ctx = ctx[comp]; + if (null == ctx || '' == match[3]) break; + expr = expr.substring('[' == match[3] ? match[1].length : match[0].length); + match = pattern.exec(expr); + } + + return before + String.interpret(ctx); + }); + } +}); +Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; + +var $break = { }; + +var Enumerable = { + each: function(iterator, context) { + var index = 0; + try { + this._each(function(value) { + iterator.call(context, value, index++); + }); + } catch (e) { + if (e != $break) throw e; + } + return this; + }, + + eachSlice: function(number, iterator, context) { + var index = -number, slices = [], array = this.toArray(); + if (number < 1) return array; + while ((index += number) < array.length) + slices.push(array.slice(index, index+number)); + return slices.collect(iterator, context); + }, + + all: function(iterator, context) { + iterator = iterator || Prototype.K; + var result = true; + this.each(function(value, index) { + result = result && !!iterator.call(context, value, index); + if (!result) throw $break; + }); + return result; + }, + + any: function(iterator, context) { + iterator = iterator || Prototype.K; + var result = false; + this.each(function(value, index) { + if (result = !!iterator.call(context, value, index)) + throw $break; + }); + return result; + }, + + collect: function(iterator, context) { + iterator = iterator || Prototype.K; + var results = []; + this.each(function(value, index) { + results.push(iterator.call(context, value, index)); + }); + return results; + }, + + detect: function(iterator, context) { + var result; + this.each(function(value, index) { + if (iterator.call(context, value, index)) { + result = value; + throw $break; + } + }); + return result; + }, + + findAll: function(iterator, context) { + var results = []; + this.each(function(value, index) { + if (iterator.call(context, value, index)) + results.push(value); + }); + return results; + }, + + grep: function(filter, iterator, context) { + iterator = iterator || Prototype.K; + var results = []; + + if (Object.isString(filter)) + filter = new RegExp(filter); + + this.each(function(value, index) { + if (filter.match(value)) + results.push(iterator.call(context, value, index)); + }); + return results; + }, + + include: function(object) { + if (Object.isFunction(this.indexOf)) + if (this.indexOf(object) != -1) return true; + + var found = false; + this.each(function(value) { + if (value == object) { + found = true; + throw $break; + } + }); + return found; + }, + + inGroupsOf: function(number, fillWith) { + fillWith = Object.isUndefined(fillWith) ? null : fillWith; + return this.eachSlice(number, function(slice) { + while(slice.length < number) slice.push(fillWith); + return slice; + }); + }, + + inject: function(memo, iterator, context) { + this.each(function(value, index) { + memo = iterator.call(context, memo, value, index); + }); + return memo; + }, + + invoke: function(method) { + var args = $A(arguments).slice(1); + return this.map(function(value) { + return value[method].apply(value, args); + }); + }, + + max: function(iterator, context) { + iterator = iterator || Prototype.K; + var result; + this.each(function(value, index) { + value = iterator.call(context, value, index); + if (result == null || value >= result) + result = value; + }); + return result; + }, + + min: function(iterator, context) { + iterator = iterator || Prototype.K; + var result; + this.each(function(value, index) { + value = iterator.call(context, value, index); + if (result == null || value < result) + result = value; + }); + return result; + }, + + partition: function(iterator, context) { + iterator = iterator || Prototype.K; + var trues = [], falses = []; + this.each(function(value, index) { + (iterator.call(context, value, index) ? + trues : falses).push(value); + }); + return [trues, falses]; + }, + + pluck: function(property) { + var results = []; + this.each(function(value) { + results.push(value[property]); + }); + return results; + }, + + reject: function(iterator, context) { + var results = []; + this.each(function(value, index) { + if (!iterator.call(context, value, index)) + results.push(value); + }); + return results; + }, + + sortBy: function(iterator, context) { + return this.map(function(value, index) { + return { + value: value, + criteria: iterator.call(context, value, index) + }; + }).sort(function(left, right) { + var a = left.criteria, b = right.criteria; + return a < b ? -1 : a > b ? 1 : 0; + }).pluck('value'); + }, + + toArray: function() { + return this.map(); + }, + + zip: function() { + var iterator = Prototype.K, args = $A(arguments); + if (Object.isFunction(args.last())) + iterator = args.pop(); + + var collections = [this].concat(args).map($A); + return this.map(function(value, index) { + return iterator(collections.pluck(index)); + }); + }, + + size: function() { + return this.toArray().length; + }, + + inspect: function() { + return '#'; + } +}; + +Object.extend(Enumerable, { + map: Enumerable.collect, + find: Enumerable.detect, + select: Enumerable.findAll, + filter: Enumerable.findAll, + member: Enumerable.include, + entries: Enumerable.toArray, + every: Enumerable.all, + some: Enumerable.any +}); +function $A(iterable) { + if (!iterable) return []; + if (iterable.toArray) return iterable.toArray(); + var length = iterable.length || 0, results = new Array(length); + while (length--) results[length] = iterable[length]; + return results; +} + +if (Prototype.Browser.WebKit) { + $A = function(iterable) { + if (!iterable) return []; + // In Safari, only use the `toArray` method if it's not a NodeList. + // A NodeList is a function, has an function `item` property, and a numeric + // `length` property. Adapted from Google Doctype. + if (!(typeof iterable === 'function' && typeof iterable.length === + 'number' && typeof iterable.item === 'function') && iterable.toArray) + return iterable.toArray(); + var length = iterable.length || 0, results = new Array(length); + while (length--) results[length] = iterable[length]; + return results; + }; +} + +Array.from = $A; + +Object.extend(Array.prototype, Enumerable); + +if (!Array.prototype._reverse) Array.prototype._reverse = Array.prototype.reverse; + +Object.extend(Array.prototype, { + _each: function(iterator) { + for (var i = 0, length = this.length; i < length; i++) + iterator(this[i]); + }, + + clear: function() { + this.length = 0; + return this; + }, + + first: function() { + return this[0]; + }, + + last: function() { + return this[this.length - 1]; + }, + + compact: function() { + return this.select(function(value) { + return value != null; + }); + }, + + flatten: function() { + return this.inject([], function(array, value) { + return array.concat(Object.isArray(value) ? + value.flatten() : [value]); + }); + }, + + without: function() { + var values = $A(arguments); + return this.select(function(value) { + return !values.include(value); + }); + }, + + reverse: function(inline) { + return (inline !== false ? this : this.toArray())._reverse(); + }, + + reduce: function() { + return this.length > 1 ? this : this[0]; + }, + + uniq: function(sorted) { + return this.inject([], function(array, value, index) { + if (0 == index || (sorted ? array.last() != value : !array.include(value))) + array.push(value); + return array; + }); + }, + + intersect: function(array) { + return this.uniq().findAll(function(item) { + return array.detect(function(value) { return item === value }); + }); + }, + + clone: function() { + return [].concat(this); + }, + + size: function() { + return this.length; + }, + + inspect: function() { + return '[' + this.map(Object.inspect).join(', ') + ']'; + }, + + toJSON: function() { + var results = []; + this.each(function(object) { + var value = Object.toJSON(object); + if (!Object.isUndefined(value)) results.push(value); + }); + return '[' + results.join(', ') + ']'; + } +}); + +// use native browser JS 1.6 implementation if available +if (Object.isFunction(Array.prototype.forEach)) + Array.prototype._each = Array.prototype.forEach; + +if (!Array.prototype.indexOf) Array.prototype.indexOf = function(item, i) { + i || (i = 0); + var length = this.length; + if (i < 0) i = length + i; + for (; i < length; i++) + if (this[i] === item) return i; + return -1; +}; + +if (!Array.prototype.lastIndexOf) Array.prototype.lastIndexOf = function(item, i) { + i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1; + var n = this.slice(0, i).reverse().indexOf(item); + return (n < 0) ? n : i - n - 1; +}; + +Array.prototype.toArray = Array.prototype.clone; + +function $w(string) { + if (!Object.isString(string)) return []; + string = string.strip(); + return string ? string.split(/\s+/) : []; +} + +if (Prototype.Browser.Opera){ + Array.prototype.concat = function() { + var array = []; + for (var i = 0, length = this.length; i < length; i++) array.push(this[i]); + for (var i = 0, length = arguments.length; i < length; i++) { + if (Object.isArray(arguments[i])) { + for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++) + array.push(arguments[i][j]); + } else { + array.push(arguments[i]); + } + } + return array; + }; +} +Object.extend(Number.prototype, { + toColorPart: function() { + return this.toPaddedString(2, 16); + }, + + succ: function() { + return this + 1; + }, + + times: function(iterator, context) { + $R(0, this, true).each(iterator, context); + return this; + }, + + toPaddedString: function(length, radix) { + var string = this.toString(radix || 10); + return '0'.times(length - string.length) + string; + }, + + toJSON: function() { + return isFinite(this) ? this.toString() : 'null'; + } +}); + +$w('abs round ceil floor').each(function(method){ + Number.prototype[method] = Math[method].methodize(); +}); +function $H(object) { + return new Hash(object); +}; + +var Hash = Class.create(Enumerable, (function() { + + function toQueryPair(key, value) { + if (Object.isUndefined(value)) return key; + return key + '=' + encodeURIComponent(String.interpret(value)); + } + + return { + initialize: function(object) { + this._object = Object.isHash(object) ? object.toObject() : Object.clone(object); + }, + + _each: function(iterator) { + for (var key in this._object) { + var value = this._object[key], pair = [key, value]; + pair.key = key; + pair.value = value; + iterator(pair); + } + }, + + set: function(key, value) { + return this._object[key] = value; + }, + + get: function(key) { + // simulating poorly supported hasOwnProperty + if (this._object[key] !== Object.prototype[key]) + return this._object[key]; + }, + + unset: function(key) { + var value = this._object[key]; + delete this._object[key]; + return value; + }, + + toObject: function() { + return Object.clone(this._object); + }, + + keys: function() { + return this.pluck('key'); + }, + + values: function() { + return this.pluck('value'); + }, + + index: function(value) { + var match = this.detect(function(pair) { + return pair.value === value; + }); + return match && match.key; + }, + + merge: function(object) { + return this.clone().update(object); + }, + + update: function(object) { + return new Hash(object).inject(this, function(result, pair) { + result.set(pair.key, pair.value); + return result; + }); + }, + + toQueryString: function() { + return this.inject([], function(results, pair) { + var key = encodeURIComponent(pair.key), values = pair.value; + + if (values && typeof values == 'object') { + if (Object.isArray(values)) + return results.concat(values.map(toQueryPair.curry(key))); + } else results.push(toQueryPair(key, values)); + return results; + }).join('&'); + }, + + inspect: function() { + return '#'; + }, + + toJSON: function() { + return Object.toJSON(this.toObject()); + }, + + clone: function() { + return new Hash(this); + } + } +})()); + +Hash.prototype.toTemplateReplacements = Hash.prototype.toObject; +Hash.from = $H; +var ObjectRange = Class.create(Enumerable, { + initialize: function(start, end, exclusive) { + this.start = start; + this.end = end; + this.exclusive = exclusive; + }, + + _each: function(iterator) { + var value = this.start; + while (this.include(value)) { + iterator(value); + value = value.succ(); + } + }, + + include: function(value) { + if (value < this.start) + return false; + if (this.exclusive) + return value < this.end; + return value <= this.end; + } +}); + +var $R = function(start, end, exclusive) { + return new ObjectRange(start, end, exclusive); +}; + +var Ajax = { + getTransport: function() { + return Try.these( + function() {return new XMLHttpRequest()}, + function() {return new ActiveXObject('Msxml2.XMLHTTP')}, + function() {return new ActiveXObject('Microsoft.XMLHTTP')} + ) || false; + }, + + activeRequestCount: 0 +}; + +Ajax.Responders = { + responders: [], + + _each: function(iterator) { + this.responders._each(iterator); + }, + + register: function(responder) { + if (!this.include(responder)) + this.responders.push(responder); + }, + + unregister: function(responder) { + this.responders = this.responders.without(responder); + }, + + dispatch: function(callback, request, transport, json) { + this.each(function(responder) { + if (Object.isFunction(responder[callback])) { + try { + responder[callback].apply(responder, [request, transport, json]); + } catch (e) { } + } + }); + } +}; + +Object.extend(Ajax.Responders, Enumerable); + +Ajax.Responders.register({ + onCreate: function() { Ajax.activeRequestCount++ }, + onComplete: function() { Ajax.activeRequestCount-- } +}); + +Ajax.Base = Class.create({ + initialize: function(options) { + this.options = { + method: 'post', + asynchronous: true, + contentType: 'application/x-www-form-urlencoded', + encoding: 'UTF-8', + parameters: '', + evalJSON: true, + evalJS: true + }; + Object.extend(this.options, options || { }); + + this.options.method = this.options.method.toLowerCase(); + + if (Object.isString(this.options.parameters)) + this.options.parameters = this.options.parameters.toQueryParams(); + else if (Object.isHash(this.options.parameters)) + this.options.parameters = this.options.parameters.toObject(); + } +}); + +Ajax.Request = Class.create(Ajax.Base, { + _complete: false, + + initialize: function($super, url, options) { + $super(options); + this.transport = Ajax.getTransport(); + this.request(url); + }, + + request: function(url) { + this.url = url; + this.method = this.options.method; + var params = Object.clone(this.options.parameters); + + if (!['get', 'post'].include(this.method)) { + // simulate other verbs over post + params['_method'] = this.method; + this.method = 'post'; + } + + this.parameters = params; + + if (params = Object.toQueryString(params)) { + // when GET, append parameters to URL + if (this.method == 'get') + this.url += (this.url.include('?') ? '&' : '?') + params; + else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) + params += '&_='; + } + + try { + var response = new Ajax.Response(this); + if (this.options.onCreate) this.options.onCreate(response); + Ajax.Responders.dispatch('onCreate', this, response); + + this.transport.open(this.method.toUpperCase(), this.url, + this.options.asynchronous); + + if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1); + + this.transport.onreadystatechange = this.onStateChange.bind(this); + this.setRequestHeaders(); + + this.body = this.method == 'post' ? (this.options.postBody || params) : null; + this.transport.send(this.body); + + /* Force Firefox to handle ready state 4 for synchronous requests */ + if (!this.options.asynchronous && this.transport.overrideMimeType) + this.onStateChange(); + + } + catch (e) { + this.dispatchException(e); + } + }, + + onStateChange: function() { + var readyState = this.transport.readyState; + if (readyState > 1 && !((readyState == 4) && this._complete)) + this.respondToReadyState(this.transport.readyState); + }, + + setRequestHeaders: function() { + var headers = { + 'X-Requested-With': 'XMLHttpRequest', + 'X-Prototype-Version': Prototype.Version, + 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' + }; + + if (this.method == 'post') { + headers['Content-type'] = this.options.contentType + + (this.options.encoding ? '; charset=' + this.options.encoding : ''); + + /* Force "Connection: close" for older Mozilla browsers to work + * around a bug where XMLHttpRequest sends an incorrect + * Content-length header. See Mozilla Bugzilla #246651. + */ + if (this.transport.overrideMimeType && + (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005) + headers['Connection'] = 'close'; + } + + // user-defined headers + if (typeof this.options.requestHeaders == 'object') { + var extras = this.options.requestHeaders; + + if (Object.isFunction(extras.push)) + for (var i = 0, length = extras.length; i < length; i += 2) + headers[extras[i]] = extras[i+1]; + else + $H(extras).each(function(pair) { headers[pair.key] = pair.value }); + } + + for (var name in headers) + this.transport.setRequestHeader(name, headers[name]); + }, + + success: function() { + var status = this.getStatus(); + return !status || (status >= 200 && status < 300); + }, + + getStatus: function() { + try { + return this.transport.status || 0; + } catch (e) { return 0 } + }, + + respondToReadyState: function(readyState) { + var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this); + + if (state == 'Complete') { + try { + this._complete = true; + (this.options['on' + response.status] + || this.options['on' + (this.success() ? 'Success' : 'Failure')] + || Prototype.emptyFunction)(response, response.headerJSON); + } catch (e) { + this.dispatchException(e); + } + + var contentType = response.getHeader('Content-type'); + if (this.options.evalJS == 'force' + || (this.options.evalJS && this.isSameOrigin() && contentType + && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i))) + this.evalResponse(); + } + + try { + (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON); + Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON); + } catch (e) { + this.dispatchException(e); + } + + if (state == 'Complete') { + // avoid memory leak in MSIE: clean up + this.transport.onreadystatechange = Prototype.emptyFunction; + } + }, + + isSameOrigin: function() { + var m = this.url.match(/^\s*https?:\/\/[^\/]*/); + return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({ + protocol: location.protocol, + domain: document.domain, + port: location.port ? ':' + location.port : '' + })); + }, + + getHeader: function(name) { + try { + return this.transport.getResponseHeader(name) || null; + } catch (e) { return null } + }, + + evalResponse: function() { + try { + return eval((this.transport.responseText || '').unfilterJSON()); + } catch (e) { + this.dispatchException(e); + } + }, + + dispatchException: function(exception) { + (this.options.onException || Prototype.emptyFunction)(this, exception); + Ajax.Responders.dispatch('onException', this, exception); + } +}); + +Ajax.Request.Events = + ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; + +Ajax.Response = Class.create({ + initialize: function(request){ + this.request = request; + var transport = this.transport = request.transport, + readyState = this.readyState = transport.readyState; + + if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) { + this.status = this.getStatus(); + this.statusText = this.getStatusText(); + this.responseText = String.interpret(transport.responseText); + this.headerJSON = this._getHeaderJSON(); + } + + if(readyState == 4) { + var xml = transport.responseXML; + this.responseXML = Object.isUndefined(xml) ? null : xml; + this.responseJSON = this._getResponseJSON(); + } + }, + + status: 0, + statusText: '', + + getStatus: Ajax.Request.prototype.getStatus, + + getStatusText: function() { + try { + return this.transport.statusText || ''; + } catch (e) { return '' } + }, + + getHeader: Ajax.Request.prototype.getHeader, + + getAllHeaders: function() { + try { + return this.getAllResponseHeaders(); + } catch (e) { return null } + }, + + getResponseHeader: function(name) { + return this.transport.getResponseHeader(name); + }, + + getAllResponseHeaders: function() { + return this.transport.getAllResponseHeaders(); + }, + + _getHeaderJSON: function() { + var json = this.getHeader('X-JSON'); + if (!json) return null; + json = decodeURIComponent(escape(json)); + try { + return json.evalJSON(this.request.options.sanitizeJSON || + !this.request.isSameOrigin()); + } catch (e) { + this.request.dispatchException(e); + } + }, + + _getResponseJSON: function() { + var options = this.request.options; + if (!options.evalJSON || (options.evalJSON != 'force' && + !(this.getHeader('Content-type') || '').include('application/json')) || + this.responseText.blank()) + return null; + try { + return this.responseText.evalJSON(options.sanitizeJSON || + !this.request.isSameOrigin()); + } catch (e) { + this.request.dispatchException(e); + } + } +}); + +Ajax.Updater = Class.create(Ajax.Request, { + initialize: function($super, container, url, options) { + this.container = { + success: (container.success || container), + failure: (container.failure || (container.success ? null : container)) + }; + + options = Object.clone(options); + var onComplete = options.onComplete; + options.onComplete = (function(response, json) { + this.updateContent(response.responseText); + if (Object.isFunction(onComplete)) onComplete(response, json); + }).bind(this); + + $super(url, options); + }, + + updateContent: function(responseText) { + var receiver = this.container[this.success() ? 'success' : 'failure'], + options = this.options; + + if (!options.evalScripts) responseText = responseText.stripScripts(); + + if (receiver = $(receiver)) { + if (options.insertion) { + if (Object.isString(options.insertion)) { + var insertion = { }; insertion[options.insertion] = responseText; + receiver.insert(insertion); + } + else options.insertion(receiver, responseText); + } + else receiver.update(responseText); + } + } +}); + +Ajax.PeriodicalUpdater = Class.create(Ajax.Base, { + initialize: function($super, container, url, options) { + $super(options); + this.onComplete = this.options.onComplete; + + this.frequency = (this.options.frequency || 2); + this.decay = (this.options.decay || 1); + + this.updater = { }; + this.container = container; + this.url = url; + + this.start(); + }, + + start: function() { + this.options.onComplete = this.updateComplete.bind(this); + this.onTimerEvent(); + }, + + stop: function() { + this.updater.options.onComplete = undefined; + clearTimeout(this.timer); + (this.onComplete || Prototype.emptyFunction).apply(this, arguments); + }, + + updateComplete: function(response) { + if (this.options.decay) { + this.decay = (response.responseText == this.lastText ? + this.decay * this.options.decay : 1); + + this.lastText = response.responseText; + } + this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency); + }, + + onTimerEvent: function() { + this.updater = new Ajax.Updater(this.container, this.url, this.options); + } +}); +function $(element) { + if (arguments.length > 1) { + for (var i = 0, elements = [], length = arguments.length; i < length; i++) + elements.push($(arguments[i])); + return elements; + } + if (Object.isString(element)) + element = document.getElementById(element); + return Element.extend(element); +} + +if (Prototype.BrowserFeatures.XPath) { + document._getElementsByXPath = function(expression, parentElement) { + var results = []; + var query = document.evaluate(expression, $(parentElement) || document, + null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); + for (var i = 0, length = query.snapshotLength; i < length; i++) + results.push(Element.extend(query.snapshotItem(i))); + return results; + }; +} + +/*--------------------------------------------------------------------------*/ + +if (!window.Node) var Node = { }; + +if (!Node.ELEMENT_NODE) { + // DOM level 2 ECMAScript Language Binding + Object.extend(Node, { + ELEMENT_NODE: 1, + ATTRIBUTE_NODE: 2, + TEXT_NODE: 3, + CDATA_SECTION_NODE: 4, + ENTITY_REFERENCE_NODE: 5, + ENTITY_NODE: 6, + PROCESSING_INSTRUCTION_NODE: 7, + COMMENT_NODE: 8, + DOCUMENT_NODE: 9, + DOCUMENT_TYPE_NODE: 10, + DOCUMENT_FRAGMENT_NODE: 11, + NOTATION_NODE: 12 + }); +} + +(function() { + var element = this.Element; + this.Element = function(tagName, attributes) { + attributes = attributes || { }; + tagName = tagName.toLowerCase(); + var cache = Element.cache; + if (Prototype.Browser.IE && attributes.name) { + tagName = '<' + tagName + ' name="' + attributes.name + '">'; + delete attributes.name; + return Element.writeAttribute(document.createElement(tagName), attributes); + } + if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName)); + return Element.writeAttribute(cache[tagName].cloneNode(false), attributes); + }; + Object.extend(this.Element, element || { }); + if (element) this.Element.prototype = element.prototype; +}).call(window); + +Element.cache = { }; + +Element.Methods = { + visible: function(element) { + return $(element).style.display != 'none'; + }, + + toggle: function(element) { + element = $(element); + Element[Element.visible(element) ? 'hide' : 'show'](element); + return element; + }, + + hide: function(element) { + element = $(element); + element.style.display = 'none'; + return element; + }, + + show: function(element) { + element = $(element); + element.style.display = ''; + return element; + }, + + remove: function(element) { + element = $(element); + element.parentNode.removeChild(element); + return element; + }, + + update: function(element, content) { + element = $(element); + if (content && content.toElement) content = content.toElement(); + if (Object.isElement(content)) return element.update().insert(content); + content = Object.toHTML(content); + element.innerHTML = content.stripScripts(); + content.evalScripts.bind(content).defer(); + return element; + }, + + replace: function(element, content) { + element = $(element); + if (content && content.toElement) content = content.toElement(); + else if (!Object.isElement(content)) { + content = Object.toHTML(content); + var range = element.ownerDocument.createRange(); + range.selectNode(element); + content.evalScripts.bind(content).defer(); + content = range.createContextualFragment(content.stripScripts()); + } + element.parentNode.replaceChild(content, element); + return element; + }, + + insert: function(element, insertions) { + element = $(element); + + if (Object.isString(insertions) || Object.isNumber(insertions) || + Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML))) + insertions = {bottom:insertions}; + + var content, insert, tagName, childNodes; + + for (var position in insertions) { + content = insertions[position]; + position = position.toLowerCase(); + insert = Element._insertionTranslations[position]; + + if (content && content.toElement) content = content.toElement(); + if (Object.isElement(content)) { + insert(element, content); + continue; + } + + content = Object.toHTML(content); + + tagName = ((position == 'before' || position == 'after') + ? element.parentNode : element).tagName.toUpperCase(); + + childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); + + if (position == 'top' || position == 'after') childNodes.reverse(); + childNodes.each(insert.curry(element)); + + content.evalScripts.bind(content).defer(); + } + + return element; + }, + + wrap: function(element, wrapper, attributes) { + element = $(element); + if (Object.isElement(wrapper)) + $(wrapper).writeAttribute(attributes || { }); + else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes); + else wrapper = new Element('div', wrapper); + if (element.parentNode) + element.parentNode.replaceChild(wrapper, element); + wrapper.appendChild(element); + return wrapper; + }, + + inspect: function(element) { + element = $(element); + var result = '<' + element.tagName.toLowerCase(); + $H({'id': 'id', 'className': 'class'}).each(function(pair) { + var property = pair.first(), attribute = pair.last(); + var value = (element[property] || '').toString(); + if (value) result += ' ' + attribute + '=' + value.inspect(true); + }); + return result + '>'; + }, + + recursivelyCollect: function(element, property) { + element = $(element); + var elements = []; + while (element = element[property]) + if (element.nodeType == 1) + elements.push(Element.extend(element)); + return elements; + }, + + ancestors: function(element) { + return $(element).recursivelyCollect('parentNode'); + }, + + descendants: function(element) { + return $(element).select("*"); + }, + + firstDescendant: function(element) { + element = $(element).firstChild; + while (element && element.nodeType != 1) element = element.nextSibling; + return $(element); + }, + + immediateDescendants: function(element) { + if (!(element = $(element).firstChild)) return []; + while (element && element.nodeType != 1) element = element.nextSibling; + if (element) return [element].concat($(element).nextSiblings()); + return []; + }, + + previousSiblings: function(element) { + return $(element).recursivelyCollect('previousSibling'); + }, + + nextSiblings: function(element) { + return $(element).recursivelyCollect('nextSibling'); + }, + + siblings: function(element) { + element = $(element); + return element.previousSiblings().reverse().concat(element.nextSiblings()); + }, + + match: function(element, selector) { + if (Object.isString(selector)) + selector = new Selector(selector); + return selector.match($(element)); + }, + + up: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return $(element.parentNode); + var ancestors = element.ancestors(); + return Object.isNumber(expression) ? ancestors[expression] : + Selector.findElement(ancestors, expression, index); + }, + + down: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return element.firstDescendant(); + return Object.isNumber(expression) ? element.descendants()[expression] : + Element.select(element, expression)[index || 0]; + }, + + previous: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element)); + var previousSiblings = element.previousSiblings(); + return Object.isNumber(expression) ? previousSiblings[expression] : + Selector.findElement(previousSiblings, expression, index); + }, + + next: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element)); + var nextSiblings = element.nextSiblings(); + return Object.isNumber(expression) ? nextSiblings[expression] : + Selector.findElement(nextSiblings, expression, index); + }, + + select: function() { + var args = $A(arguments), element = $(args.shift()); + return Selector.findChildElements(element, args); + }, + + adjacent: function() { + var args = $A(arguments), element = $(args.shift()); + return Selector.findChildElements(element.parentNode, args).without(element); + }, + + identify: function(element) { + element = $(element); + var id = element.readAttribute('id'), self = arguments.callee; + if (id) return id; + do { id = 'anonymous_element_' + self.counter++ } while ($(id)); + element.writeAttribute('id', id); + return id; + }, + + readAttribute: function(element, name) { + element = $(element); + if (Prototype.Browser.IE) { + var t = Element._attributeTranslations.read; + if (t.values[name]) return t.values[name](element, name); + if (t.names[name]) name = t.names[name]; + if (name.include(':')) { + return (!element.attributes || !element.attributes[name]) ? null : + element.attributes[name].value; + } + } + return element.getAttribute(name); + }, + + writeAttribute: function(element, name, value) { + element = $(element); + var attributes = { }, t = Element._attributeTranslations.write; + + if (typeof name == 'object') attributes = name; + else attributes[name] = Object.isUndefined(value) ? true : value; + + for (var attr in attributes) { + name = t.names[attr] || attr; + value = attributes[attr]; + if (t.values[attr]) name = t.values[attr](element, value); + if (value === false || value === null) + element.removeAttribute(name); + else if (value === true) + element.setAttribute(name, name); + else element.setAttribute(name, value); + } + return element; + }, + + getHeight: function(element) { + return $(element).getDimensions().height; + }, + + getWidth: function(element) { + return $(element).getDimensions().width; + }, + + classNames: function(element) { + return new Element.ClassNames(element); + }, + + hasClassName: function(element, className) { + if (!(element = $(element))) return; + var elementClassName = element.className; + return (elementClassName.length > 0 && (elementClassName == className || + new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName))); + }, + + addClassName: function(element, className) { + if (!(element = $(element))) return; + if (!element.hasClassName(className)) + element.className += (element.className ? ' ' : '') + className; + return element; + }, + + removeClassName: function(element, className) { + if (!(element = $(element))) return; + element.className = element.className.replace( + new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip(); + return element; + }, + + toggleClassName: function(element, className) { + if (!(element = $(element))) return; + return element[element.hasClassName(className) ? + 'removeClassName' : 'addClassName'](className); + }, + + // removes whitespace-only text node children + cleanWhitespace: function(element) { + element = $(element); + var node = element.firstChild; + while (node) { + var nextNode = node.nextSibling; + if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) + element.removeChild(node); + node = nextNode; + } + return element; + }, + + empty: function(element) { + return $(element).innerHTML.blank(); + }, + + descendantOf: function(element, ancestor) { + element = $(element), ancestor = $(ancestor); + + if (element.compareDocumentPosition) + return (element.compareDocumentPosition(ancestor) & 8) === 8; + + if (ancestor.contains) + return ancestor.contains(element) && ancestor !== element; + + while (element = element.parentNode) + if (element == ancestor) return true; + + return false; + }, + + scrollTo: function(element) { + element = $(element); + var pos = element.cumulativeOffset(); + window.scrollTo(pos[0], pos[1]); + return element; + }, + + getStyle: function(element, style) { + element = $(element); + style = style == 'float' ? 'cssFloat' : style.camelize(); + var value = element.style[style]; + if (!value || value == 'auto') { + var css = document.defaultView.getComputedStyle(element, null); + value = css ? css[style] : null; + } + if (style == 'opacity') return value ? parseFloat(value) : 1.0; + return value == 'auto' ? null : value; + }, + + getOpacity: function(element) { + return $(element).getStyle('opacity'); + }, + + setStyle: function(element, styles) { + element = $(element); + var elementStyle = element.style, match; + if (Object.isString(styles)) { + element.style.cssText += ';' + styles; + return styles.include('opacity') ? + element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element; + } + for (var property in styles) + if (property == 'opacity') element.setOpacity(styles[property]); + else + elementStyle[(property == 'float' || property == 'cssFloat') ? + (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') : + property] = styles[property]; + + return element; + }, + + setOpacity: function(element, value) { + element = $(element); + element.style.opacity = (value == 1 || value === '') ? '' : + (value < 0.00001) ? 0 : value; + return element; + }, + + getDimensions: function(element) { + element = $(element); + var display = element.getStyle('display'); + if (display != 'none' && display != null) // Safari bug + return {width: element.offsetWidth, height: element.offsetHeight}; + + // All *Width and *Height properties give 0 on elements with display none, + // so enable the element temporarily + var els = element.style; + var originalVisibility = els.visibility; + var originalPosition = els.position; + var originalDisplay = els.display; + els.visibility = 'hidden'; + els.position = 'absolute'; + els.display = 'block'; + var originalWidth = element.clientWidth; + var originalHeight = element.clientHeight; + els.display = originalDisplay; + els.position = originalPosition; + els.visibility = originalVisibility; + return {width: originalWidth, height: originalHeight}; + }, + + makePositioned: function(element) { + element = $(element); + var pos = Element.getStyle(element, 'position'); + if (pos == 'static' || !pos) { + element._madePositioned = true; + element.style.position = 'relative'; + // Opera returns the offset relative to the positioning context, when an + // element is position relative but top and left have not been defined + if (Prototype.Browser.Opera) { + element.style.top = 0; + element.style.left = 0; + } + } + return element; + }, + + undoPositioned: function(element) { + element = $(element); + if (element._madePositioned) { + element._madePositioned = undefined; + element.style.position = + element.style.top = + element.style.left = + element.style.bottom = + element.style.right = ''; + } + return element; + }, + + makeClipping: function(element) { + element = $(element); + if (element._overflow) return element; + element._overflow = Element.getStyle(element, 'overflow') || 'auto'; + if (element._overflow !== 'hidden') + element.style.overflow = 'hidden'; + return element; + }, + + undoClipping: function(element) { + element = $(element); + if (!element._overflow) return element; + element.style.overflow = element._overflow == 'auto' ? '' : element._overflow; + element._overflow = null; + return element; + }, + + cumulativeOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + } while (element); + return Element._returnOffset(valueL, valueT); + }, + + positionedOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + if (element) { + if (element.tagName.toUpperCase() == 'BODY') break; + var p = Element.getStyle(element, 'position'); + if (p !== 'static') break; + } + } while (element); + return Element._returnOffset(valueL, valueT); + }, + + absolutize: function(element) { + element = $(element); + if (element.getStyle('position') == 'absolute') return element; + // Position.prepare(); // To be done manually by Scripty when it needs it. + + var offsets = element.positionedOffset(); + var top = offsets[1]; + var left = offsets[0]; + var width = element.clientWidth; + var height = element.clientHeight; + + element._originalLeft = left - parseFloat(element.style.left || 0); + element._originalTop = top - parseFloat(element.style.top || 0); + element._originalWidth = element.style.width; + element._originalHeight = element.style.height; + + element.style.position = 'absolute'; + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.width = width + 'px'; + element.style.height = height + 'px'; + return element; + }, + + relativize: function(element) { + element = $(element); + if (element.getStyle('position') == 'relative') return element; + // Position.prepare(); // To be done manually by Scripty when it needs it. + + element.style.position = 'relative'; + var top = parseFloat(element.style.top || 0) - (element._originalTop || 0); + var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); + + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.height = element._originalHeight; + element.style.width = element._originalWidth; + return element; + }, + + cumulativeScrollOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.scrollTop || 0; + valueL += element.scrollLeft || 0; + element = element.parentNode; + } while (element); + return Element._returnOffset(valueL, valueT); + }, + + getOffsetParent: function(element) { + if (element.offsetParent) return $(element.offsetParent); + if (element == document.body) return $(element); + + while ((element = element.parentNode) && element != document.body) + if (Element.getStyle(element, 'position') != 'static') + return $(element); + + return $(document.body); + }, + + viewportOffset: function(forElement) { + var valueT = 0, valueL = 0; + + var element = forElement; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + + // Safari fix + if (element.offsetParent == document.body && + Element.getStyle(element, 'position') == 'absolute') break; + + } while (element = element.offsetParent); + + element = forElement; + do { + if (!Prototype.Browser.Opera || (element.tagName && (element.tagName.toUpperCase() == 'BODY'))) { + valueT -= element.scrollTop || 0; + valueL -= element.scrollLeft || 0; + } + } while (element = element.parentNode); + + return Element._returnOffset(valueL, valueT); + }, + + clonePosition: function(element, source) { + var options = Object.extend({ + setLeft: true, + setTop: true, + setWidth: true, + setHeight: true, + offsetTop: 0, + offsetLeft: 0 + }, arguments[2] || { }); + + // find page position of source + source = $(source); + var p = source.viewportOffset(); + + // find coordinate system to use + element = $(element); + var delta = [0, 0]; + var parent = null; + // delta [0,0] will do fine with position: fixed elements, + // position:absolute needs offsetParent deltas + if (Element.getStyle(element, 'position') == 'absolute') { + parent = element.getOffsetParent(); + delta = parent.viewportOffset(); + } + + // correct by body offsets (fixes Safari) + if (parent == document.body) { + delta[0] -= document.body.offsetLeft; + delta[1] -= document.body.offsetTop; + } + + // set position + if (options.setLeft) element.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; + if (options.setTop) element.style.top = (p[1] - delta[1] + options.offsetTop) + 'px'; + if (options.setWidth) element.style.width = source.offsetWidth + 'px'; + if (options.setHeight) element.style.height = source.offsetHeight + 'px'; + return element; + } +}; + +Element.Methods.identify.counter = 1; + +Object.extend(Element.Methods, { + getElementsBySelector: Element.Methods.select, + childElements: Element.Methods.immediateDescendants +}); + +Element._attributeTranslations = { + write: { + names: { + className: 'class', + htmlFor: 'for' + }, + values: { } + } +}; + +if (Prototype.Browser.Opera) { + Element.Methods.getStyle = Element.Methods.getStyle.wrap( + function(proceed, element, style) { + switch (style) { + case 'left': case 'top': case 'right': case 'bottom': + if (proceed(element, 'position') === 'static') return null; + case 'height': case 'width': + // returns '0px' for hidden elements; we want it to return null + if (!Element.visible(element)) return null; + + // returns the border-box dimensions rather than the content-box + // dimensions, so we subtract padding and borders from the value + var dim = parseInt(proceed(element, style), 10); + + if (dim !== element['offset' + style.capitalize()]) + return dim + 'px'; + + var properties; + if (style === 'height') { + properties = ['border-top-width', 'padding-top', + 'padding-bottom', 'border-bottom-width']; + } + else { + properties = ['border-left-width', 'padding-left', + 'padding-right', 'border-right-width']; + } + return properties.inject(dim, function(memo, property) { + var val = proceed(element, property); + return val === null ? memo : memo - parseInt(val, 10); + }) + 'px'; + default: return proceed(element, style); + } + } + ); + + Element.Methods.readAttribute = Element.Methods.readAttribute.wrap( + function(proceed, element, attribute) { + if (attribute === 'title') return element.title; + return proceed(element, attribute); + } + ); +} + +else if (Prototype.Browser.IE) { + // IE doesn't report offsets correctly for static elements, so we change them + // to "relative" to get the values, then change them back. + Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap( + function(proceed, element) { + element = $(element); + // IE throws an error if element is not in document + try { element.offsetParent } + catch(e) { return $(document.body) } + var position = element.getStyle('position'); + if (position !== 'static') return proceed(element); + element.setStyle({ position: 'relative' }); + var value = proceed(element); + element.setStyle({ position: position }); + return value; + } + ); + + $w('positionedOffset viewportOffset').each(function(method) { + Element.Methods[method] = Element.Methods[method].wrap( + function(proceed, element) { + element = $(element); + try { element.offsetParent } + catch(e) { return Element._returnOffset(0,0) } + var position = element.getStyle('position'); + if (position !== 'static') return proceed(element); + // Trigger hasLayout on the offset parent so that IE6 reports + // accurate offsetTop and offsetLeft values for position: fixed. + var offsetParent = element.getOffsetParent(); + if (offsetParent && offsetParent.getStyle('position') === 'fixed') + offsetParent.setStyle({ zoom: 1 }); + element.setStyle({ position: 'relative' }); + var value = proceed(element); + element.setStyle({ position: position }); + return value; + } + ); + }); + + Element.Methods.cumulativeOffset = Element.Methods.cumulativeOffset.wrap( + function(proceed, element) { + try { element.offsetParent } + catch(e) { return Element._returnOffset(0,0) } + return proceed(element); + } + ); + + Element.Methods.getStyle = function(element, style) { + element = $(element); + style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize(); + var value = element.style[style]; + if (!value && element.currentStyle) value = element.currentStyle[style]; + + if (style == 'opacity') { + if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/)) + if (value[1]) return parseFloat(value[1]) / 100; + return 1.0; + } + + if (value == 'auto') { + if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none')) + return element['offset' + style.capitalize()] + 'px'; + return null; + } + return value; + }; + + Element.Methods.setOpacity = function(element, value) { + function stripAlpha(filter){ + return filter.replace(/alpha\([^\)]*\)/gi,''); + } + element = $(element); + var currentStyle = element.currentStyle; + if ((currentStyle && !currentStyle.hasLayout) || + (!currentStyle && element.style.zoom == 'normal')) + element.style.zoom = 1; + + var filter = element.getStyle('filter'), style = element.style; + if (value == 1 || value === '') { + (filter = stripAlpha(filter)) ? + style.filter = filter : style.removeAttribute('filter'); + return element; + } else if (value < 0.00001) value = 0; + style.filter = stripAlpha(filter) + + 'alpha(opacity=' + (value * 100) + ')'; + return element; + }; + + Element._attributeTranslations = { + read: { + names: { + 'class': 'className', + 'for': 'htmlFor' + }, + values: { + _getAttr: function(element, attribute) { + return element.getAttribute(attribute, 2); + }, + _getAttrNode: function(element, attribute) { + var node = element.getAttributeNode(attribute); + return node ? node.value : ""; + }, + _getEv: function(element, attribute) { + attribute = element.getAttribute(attribute); + return attribute ? attribute.toString().slice(23, -2) : null; + }, + _flag: function(element, attribute) { + return $(element).hasAttribute(attribute) ? attribute : null; + }, + style: function(element) { + return element.style.cssText.toLowerCase(); + }, + title: function(element) { + return element.title; + } + } + } + }; + + Element._attributeTranslations.write = { + names: Object.extend({ + cellpadding: 'cellPadding', + cellspacing: 'cellSpacing' + }, Element._attributeTranslations.read.names), + values: { + checked: function(element, value) { + element.checked = !!value; + }, + + style: function(element, value) { + element.style.cssText = value ? value : ''; + } + } + }; + + Element._attributeTranslations.has = {}; + + $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' + + 'encType maxLength readOnly longDesc frameBorder').each(function(attr) { + Element._attributeTranslations.write.names[attr.toLowerCase()] = attr; + Element._attributeTranslations.has[attr.toLowerCase()] = attr; + }); + + (function(v) { + Object.extend(v, { + href: v._getAttr, + src: v._getAttr, + type: v._getAttr, + action: v._getAttrNode, + disabled: v._flag, + checked: v._flag, + readonly: v._flag, + multiple: v._flag, + onload: v._getEv, + onunload: v._getEv, + onclick: v._getEv, + ondblclick: v._getEv, + onmousedown: v._getEv, + onmouseup: v._getEv, + onmouseover: v._getEv, + onmousemove: v._getEv, + onmouseout: v._getEv, + onfocus: v._getEv, + onblur: v._getEv, + onkeypress: v._getEv, + onkeydown: v._getEv, + onkeyup: v._getEv, + onsubmit: v._getEv, + onreset: v._getEv, + onselect: v._getEv, + onchange: v._getEv + }); + })(Element._attributeTranslations.read.values); +} + +else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) { + Element.Methods.setOpacity = function(element, value) { + element = $(element); + element.style.opacity = (value == 1) ? 0.999999 : + (value === '') ? '' : (value < 0.00001) ? 0 : value; + return element; + }; +} + +else if (Prototype.Browser.WebKit) { + Element.Methods.setOpacity = function(element, value) { + element = $(element); + element.style.opacity = (value == 1 || value === '') ? '' : + (value < 0.00001) ? 0 : value; + + if (value == 1) + if(element.tagName.toUpperCase() == 'IMG' && element.width) { + element.width++; element.width--; + } else try { + var n = document.createTextNode(' '); + element.appendChild(n); + element.removeChild(n); + } catch (e) { } + + return element; + }; + + // Safari returns margins on body which is incorrect if the child is absolutely + // positioned. For performance reasons, redefine Element#cumulativeOffset for + // KHTML/WebKit only. + Element.Methods.cumulativeOffset = function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + if (element.offsetParent == document.body) + if (Element.getStyle(element, 'position') == 'absolute') break; + + element = element.offsetParent; + } while (element); + + return Element._returnOffset(valueL, valueT); + }; +} + +if (Prototype.Browser.IE || Prototype.Browser.Opera) { + // IE and Opera are missing .innerHTML support for TABLE-related and SELECT elements + Element.Methods.update = function(element, content) { + element = $(element); + + if (content && content.toElement) content = content.toElement(); + if (Object.isElement(content)) return element.update().insert(content); + + content = Object.toHTML(content); + var tagName = element.tagName.toUpperCase(); + + if (tagName in Element._insertionTranslations.tags) { + $A(element.childNodes).each(function(node) { element.removeChild(node) }); + Element._getContentFromAnonymousElement(tagName, content.stripScripts()) + .each(function(node) { element.appendChild(node) }); + } + else element.innerHTML = content.stripScripts(); + + content.evalScripts.bind(content).defer(); + return element; + }; +} + +if ('outerHTML' in document.createElement('div')) { + Element.Methods.replace = function(element, content) { + element = $(element); + + if (content && content.toElement) content = content.toElement(); + if (Object.isElement(content)) { + element.parentNode.replaceChild(content, element); + return element; + } + + content = Object.toHTML(content); + var parent = element.parentNode, tagName = parent.tagName.toUpperCase(); + + if (Element._insertionTranslations.tags[tagName]) { + var nextSibling = element.next(); + var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); + parent.removeChild(element); + if (nextSibling) + fragments.each(function(node) { parent.insertBefore(node, nextSibling) }); + else + fragments.each(function(node) { parent.appendChild(node) }); + } + else element.outerHTML = content.stripScripts(); + + content.evalScripts.bind(content).defer(); + return element; + }; +} + +Element._returnOffset = function(l, t) { + var result = [l, t]; + result.left = l; + result.top = t; + return result; +}; + +Element._getContentFromAnonymousElement = function(tagName, html) { + var div = new Element('div'), t = Element._insertionTranslations.tags[tagName]; + if (t) { + div.innerHTML = t[0] + html + t[1]; + t[2].times(function() { div = div.firstChild }); + } else div.innerHTML = html; + return $A(div.childNodes); +}; + +Element._insertionTranslations = { + before: function(element, node) { + element.parentNode.insertBefore(node, element); + }, + top: function(element, node) { + element.insertBefore(node, element.firstChild); + }, + bottom: function(element, node) { + element.appendChild(node); + }, + after: function(element, node) { + element.parentNode.insertBefore(node, element.nextSibling); + }, + tags: { + TABLE: ['', '
    ', 1], + TBODY: ['', '
    ', 2], + TR: ['', '
    ', 3], + TD: ['
    ', '
    ', 4], + SELECT: ['', 1] + } +}; + +(function() { + Object.extend(this.tags, { + THEAD: this.tags.TBODY, + TFOOT: this.tags.TBODY, + TH: this.tags.TD + }); +}).call(Element._insertionTranslations); + +Element.Methods.Simulated = { + hasAttribute: function(element, attribute) { + attribute = Element._attributeTranslations.has[attribute] || attribute; + var node = $(element).getAttributeNode(attribute); + return !!(node && node.specified); + } +}; + +Element.Methods.ByTag = { }; + +Object.extend(Element, Element.Methods); + +if (!Prototype.BrowserFeatures.ElementExtensions && + document.createElement('div')['__proto__']) { + window.HTMLElement = { }; + window.HTMLElement.prototype = document.createElement('div')['__proto__']; + Prototype.BrowserFeatures.ElementExtensions = true; +} + +Element.extend = (function() { + if (Prototype.BrowserFeatures.SpecificElementExtensions) + return Prototype.K; + + var Methods = { }, ByTag = Element.Methods.ByTag; + + var extend = Object.extend(function(element) { + if (!element || element._extendedByPrototype || + element.nodeType != 1 || element == window) return element; + + var methods = Object.clone(Methods), + tagName = element.tagName.toUpperCase(), property, value; + + // extend methods for specific tags + if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]); + + for (property in methods) { + value = methods[property]; + if (Object.isFunction(value) && !(property in element)) + element[property] = value.methodize(); + } + + element._extendedByPrototype = Prototype.emptyFunction; + return element; + + }, { + refresh: function() { + // extend methods for all tags (Safari doesn't need this) + if (!Prototype.BrowserFeatures.ElementExtensions) { + Object.extend(Methods, Element.Methods); + Object.extend(Methods, Element.Methods.Simulated); + } + } + }); + + extend.refresh(); + return extend; +})(); + +Element.hasAttribute = function(element, attribute) { + if (element.hasAttribute) return element.hasAttribute(attribute); + return Element.Methods.Simulated.hasAttribute(element, attribute); +}; + +Element.addMethods = function(methods) { + var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag; + + if (!methods) { + Object.extend(Form, Form.Methods); + Object.extend(Form.Element, Form.Element.Methods); + Object.extend(Element.Methods.ByTag, { + "FORM": Object.clone(Form.Methods), + "INPUT": Object.clone(Form.Element.Methods), + "SELECT": Object.clone(Form.Element.Methods), + "TEXTAREA": Object.clone(Form.Element.Methods) + }); + } + + if (arguments.length == 2) { + var tagName = methods; + methods = arguments[1]; + } + + if (!tagName) Object.extend(Element.Methods, methods || { }); + else { + if (Object.isArray(tagName)) tagName.each(extend); + else extend(tagName); + } + + function extend(tagName) { + tagName = tagName.toUpperCase(); + if (!Element.Methods.ByTag[tagName]) + Element.Methods.ByTag[tagName] = { }; + Object.extend(Element.Methods.ByTag[tagName], methods); + } + + function copy(methods, destination, onlyIfAbsent) { + onlyIfAbsent = onlyIfAbsent || false; + for (var property in methods) { + var value = methods[property]; + if (!Object.isFunction(value)) continue; + if (!onlyIfAbsent || !(property in destination)) + destination[property] = value.methodize(); + } + } + + function findDOMClass(tagName) { + var klass; + var trans = { + "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph", + "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList", + "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading", + "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote", + "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION": + "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD": + "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR": + "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET": + "FrameSet", "IFRAME": "IFrame" + }; + if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element'; + if (window[klass]) return window[klass]; + klass = 'HTML' + tagName + 'Element'; + if (window[klass]) return window[klass]; + klass = 'HTML' + tagName.capitalize() + 'Element'; + if (window[klass]) return window[klass]; + + window[klass] = { }; + window[klass].prototype = document.createElement(tagName)['__proto__']; + return window[klass]; + } + + if (F.ElementExtensions) { + copy(Element.Methods, HTMLElement.prototype); + copy(Element.Methods.Simulated, HTMLElement.prototype, true); + } + + if (F.SpecificElementExtensions) { + for (var tag in Element.Methods.ByTag) { + var klass = findDOMClass(tag); + if (Object.isUndefined(klass)) continue; + copy(T[tag], klass.prototype); + } + } + + Object.extend(Element, Element.Methods); + delete Element.ByTag; + + if (Element.extend.refresh) Element.extend.refresh(); + Element.cache = { }; +}; + +document.viewport = { + getDimensions: function() { + var dimensions = { }, B = Prototype.Browser; + $w('width height').each(function(d) { + var D = d.capitalize(); + if (B.WebKit && !document.evaluate) { + // Safari <3.0 needs self.innerWidth/Height + dimensions[d] = self['inner' + D]; + } else if (B.Opera && parseFloat(window.opera.version()) < 9.5) { + // Opera <9.5 needs document.body.clientWidth/Height + dimensions[d] = document.body['client' + D] + } else { + dimensions[d] = document.documentElement['client' + D]; + } + }); + return dimensions; + }, + + getWidth: function() { + return this.getDimensions().width; + }, + + getHeight: function() { + return this.getDimensions().height; + }, + + getScrollOffsets: function() { + return Element._returnOffset( + window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft, + window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop); + } +}; +/* Portions of the Selector class are derived from Jack Slocum's DomQuery, + * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style + * license. Please see http://www.yui-ext.com/ for more information. */ + +var Selector = Class.create({ + initialize: function(expression) { + this.expression = expression.strip(); + + if (this.shouldUseSelectorsAPI()) { + this.mode = 'selectorsAPI'; + } else if (this.shouldUseXPath()) { + this.mode = 'xpath'; + this.compileXPathMatcher(); + } else { + this.mode = "normal"; + this.compileMatcher(); + } + + }, + + shouldUseXPath: function() { + if (!Prototype.BrowserFeatures.XPath) return false; + + var e = this.expression; + + // Safari 3 chokes on :*-of-type and :empty + if (Prototype.Browser.WebKit && + (e.include("-of-type") || e.include(":empty"))) + return false; + + // XPath can't do namespaced attributes, nor can it read + // the "checked" property from DOM nodes + if ((/(\[[\w-]*?:|:checked)/).test(e)) + return false; + + return true; + }, + + shouldUseSelectorsAPI: function() { + if (!Prototype.BrowserFeatures.SelectorsAPI) return false; + + if (!Selector._div) Selector._div = new Element('div'); + + // Make sure the browser treats the selector as valid. Test on an + // isolated element to minimize cost of this check. + try { + Selector._div.querySelector(this.expression); + } catch(e) { + return false; + } + + return true; + }, + + compileMatcher: function() { + var e = this.expression, ps = Selector.patterns, h = Selector.handlers, + c = Selector.criteria, le, p, m; + + if (Selector._cache[e]) { + this.matcher = Selector._cache[e]; + return; + } + + this.matcher = ["this.matcher = function(root) {", + "var r = root, h = Selector.handlers, c = false, n;"]; + + while (e && le != e && (/\S/).test(e)) { + le = e; + for (var i in ps) { + p = ps[i]; + if (m = e.match(p)) { + this.matcher.push(Object.isFunction(c[i]) ? c[i](m) : + new Template(c[i]).evaluate(m)); + e = e.replace(m[0], ''); + break; + } + } + } + + this.matcher.push("return h.unique(n);\n}"); + eval(this.matcher.join('\n')); + Selector._cache[this.expression] = this.matcher; + }, + + compileXPathMatcher: function() { + var e = this.expression, ps = Selector.patterns, + x = Selector.xpath, le, m; + + if (Selector._cache[e]) { + this.xpath = Selector._cache[e]; return; + } + + this.matcher = ['.//*']; + while (e && le != e && (/\S/).test(e)) { + le = e; + for (var i in ps) { + if (m = e.match(ps[i])) { + this.matcher.push(Object.isFunction(x[i]) ? x[i](m) : + new Template(x[i]).evaluate(m)); + e = e.replace(m[0], ''); + break; + } + } + } + + this.xpath = this.matcher.join(''); + Selector._cache[this.expression] = this.xpath; + }, + + findElements: function(root) { + root = root || document; + var e = this.expression, results; + + switch (this.mode) { + case 'selectorsAPI': + // querySelectorAll queries document-wide, then filters to descendants + // of the context element. That's not what we want. + // Add an explicit context to the selector if necessary. + if (root !== document) { + var oldId = root.id, id = $(root).identify(); + e = "#" + id + " " + e; + } + + results = $A(root.querySelectorAll(e)).map(Element.extend); + root.id = oldId; + + return results; + case 'xpath': + return document._getElementsByXPath(this.xpath, root); + default: + return this.matcher(root); + } + }, + + match: function(element) { + this.tokens = []; + + var e = this.expression, ps = Selector.patterns, as = Selector.assertions; + var le, p, m; + + while (e && le !== e && (/\S/).test(e)) { + le = e; + for (var i in ps) { + p = ps[i]; + if (m = e.match(p)) { + // use the Selector.assertions methods unless the selector + // is too complex. + if (as[i]) { + this.tokens.push([i, Object.clone(m)]); + e = e.replace(m[0], ''); + } else { + // reluctantly do a document-wide search + // and look for a match in the array + return this.findElements(document).include(element); + } + } + } + } + + var match = true, name, matches; + for (var i = 0, token; token = this.tokens[i]; i++) { + name = token[0], matches = token[1]; + if (!Selector.assertions[name](element, matches)) { + match = false; break; + } + } + + return match; + }, + + toString: function() { + return this.expression; + }, + + inspect: function() { + return "#"; + } +}); + +Object.extend(Selector, { + _cache: { }, + + xpath: { + descendant: "//*", + child: "/*", + adjacent: "/following-sibling::*[1]", + laterSibling: '/following-sibling::*', + tagName: function(m) { + if (m[1] == '*') return ''; + return "[local-name()='" + m[1].toLowerCase() + + "' or local-name()='" + m[1].toUpperCase() + "']"; + }, + className: "[contains(concat(' ', @class, ' '), ' #{1} ')]", + id: "[@id='#{1}']", + attrPresence: function(m) { + m[1] = m[1].toLowerCase(); + return new Template("[@#{1}]").evaluate(m); + }, + attr: function(m) { + m[1] = m[1].toLowerCase(); + m[3] = m[5] || m[6]; + return new Template(Selector.xpath.operators[m[2]]).evaluate(m); + }, + pseudo: function(m) { + var h = Selector.xpath.pseudos[m[1]]; + if (!h) return ''; + if (Object.isFunction(h)) return h(m); + return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m); + }, + operators: { + '=': "[@#{1}='#{3}']", + '!=': "[@#{1}!='#{3}']", + '^=': "[starts-with(@#{1}, '#{3}')]", + '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']", + '*=': "[contains(@#{1}, '#{3}')]", + '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]", + '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]" + }, + pseudos: { + 'first-child': '[not(preceding-sibling::*)]', + 'last-child': '[not(following-sibling::*)]', + 'only-child': '[not(preceding-sibling::* or following-sibling::*)]', + 'empty': "[count(*) = 0 and (count(text()) = 0)]", + 'checked': "[@checked]", + 'disabled': "[(@disabled) and (@type!='hidden')]", + 'enabled': "[not(@disabled) and (@type!='hidden')]", + 'not': function(m) { + var e = m[6], p = Selector.patterns, + x = Selector.xpath, le, v; + + var exclusion = []; + while (e && le != e && (/\S/).test(e)) { + le = e; + for (var i in p) { + if (m = e.match(p[i])) { + v = Object.isFunction(x[i]) ? x[i](m) : new Template(x[i]).evaluate(m); + exclusion.push("(" + v.substring(1, v.length - 1) + ")"); + e = e.replace(m[0], ''); + break; + } + } + } + return "[not(" + exclusion.join(" and ") + ")]"; + }, + 'nth-child': function(m) { + return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m); + }, + 'nth-last-child': function(m) { + return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m); + }, + 'nth-of-type': function(m) { + return Selector.xpath.pseudos.nth("position() ", m); + }, + 'nth-last-of-type': function(m) { + return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m); + }, + 'first-of-type': function(m) { + m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m); + }, + 'last-of-type': function(m) { + m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m); + }, + 'only-of-type': function(m) { + var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m); + }, + nth: function(fragment, m) { + var mm, formula = m[6], predicate; + if (formula == 'even') formula = '2n+0'; + if (formula == 'odd') formula = '2n+1'; + if (mm = formula.match(/^(\d+)$/)) // digit only + return '[' + fragment + "= " + mm[1] + ']'; + if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b + if (mm[1] == "-") mm[1] = -1; + var a = mm[1] ? Number(mm[1]) : 1; + var b = mm[2] ? Number(mm[2]) : 0; + predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " + + "((#{fragment} - #{b}) div #{a} >= 0)]"; + return new Template(predicate).evaluate({ + fragment: fragment, a: a, b: b }); + } + } + } + }, + + criteria: { + tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;', + className: 'n = h.className(n, r, "#{1}", c); c = false;', + id: 'n = h.id(n, r, "#{1}", c); c = false;', + attrPresence: 'n = h.attrPresence(n, r, "#{1}", c); c = false;', + attr: function(m) { + m[3] = (m[5] || m[6]); + return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m); + }, + pseudo: function(m) { + if (m[6]) m[6] = m[6].replace(/"/g, '\\"'); + return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m); + }, + descendant: 'c = "descendant";', + child: 'c = "child";', + adjacent: 'c = "adjacent";', + laterSibling: 'c = "laterSibling";' + }, + + patterns: { + // combinators must be listed first + // (and descendant needs to be last combinator) + laterSibling: /^\s*~\s*/, + child: /^\s*>\s*/, + adjacent: /^\s*\+\s*/, + descendant: /^\s/, + + // selectors follow + tagName: /^\s*(\*|[\w\-]+)(\b|$)?/, + id: /^#([\w\-\*]+)(\b|$)/, + className: /^\.([\w\-\*]+)(\b|$)/, + pseudo: +/^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/, + attrPresence: /^\[((?:[\w]+:)?[\w]+)\]/, + attr: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/ + }, + + // for Selector.match and Element#match + assertions: { + tagName: function(element, matches) { + return matches[1].toUpperCase() == element.tagName.toUpperCase(); + }, + + className: function(element, matches) { + return Element.hasClassName(element, matches[1]); + }, + + id: function(element, matches) { + return element.id === matches[1]; + }, + + attrPresence: function(element, matches) { + return Element.hasAttribute(element, matches[1]); + }, + + attr: function(element, matches) { + var nodeValue = Element.readAttribute(element, matches[1]); + return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]); + } + }, + + handlers: { + // UTILITY FUNCTIONS + // joins two collections + concat: function(a, b) { + for (var i = 0, node; node = b[i]; i++) + a.push(node); + return a; + }, + + // marks an array of nodes for counting + mark: function(nodes) { + var _true = Prototype.emptyFunction; + for (var i = 0, node; node = nodes[i]; i++) + node._countedByPrototype = _true; + return nodes; + }, + + unmark: function(nodes) { + for (var i = 0, node; node = nodes[i]; i++) + node._countedByPrototype = undefined; + return nodes; + }, + + // mark each child node with its position (for nth calls) + // "ofType" flag indicates whether we're indexing for nth-of-type + // rather than nth-child + index: function(parentNode, reverse, ofType) { + parentNode._countedByPrototype = Prototype.emptyFunction; + if (reverse) { + for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) { + var node = nodes[i]; + if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++; + } + } else { + for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++) + if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++; + } + }, + + // filters out duplicates and extends all nodes + unique: function(nodes) { + if (nodes.length == 0) return nodes; + var results = [], n; + for (var i = 0, l = nodes.length; i < l; i++) + if (!(n = nodes[i])._countedByPrototype) { + n._countedByPrototype = Prototype.emptyFunction; + results.push(Element.extend(n)); + } + return Selector.handlers.unmark(results); + }, + + // COMBINATOR FUNCTIONS + descendant: function(nodes) { + var h = Selector.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) + h.concat(results, node.getElementsByTagName('*')); + return results; + }, + + child: function(nodes) { + var h = Selector.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) { + for (var j = 0, child; child = node.childNodes[j]; j++) + if (child.nodeType == 1 && child.tagName != '!') results.push(child); + } + return results; + }, + + adjacent: function(nodes) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + var next = this.nextElementSibling(node); + if (next) results.push(next); + } + return results; + }, + + laterSibling: function(nodes) { + var h = Selector.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) + h.concat(results, Element.nextSiblings(node)); + return results; + }, + + nextElementSibling: function(node) { + while (node = node.nextSibling) + if (node.nodeType == 1) return node; + return null; + }, + + previousElementSibling: function(node) { + while (node = node.previousSibling) + if (node.nodeType == 1) return node; + return null; + }, + + // TOKEN FUNCTIONS + tagName: function(nodes, root, tagName, combinator) { + var uTagName = tagName.toUpperCase(); + var results = [], h = Selector.handlers; + if (nodes) { + if (combinator) { + // fastlane for ordinary descendant combinators + if (combinator == "descendant") { + for (var i = 0, node; node = nodes[i]; i++) + h.concat(results, node.getElementsByTagName(tagName)); + return results; + } else nodes = this[combinator](nodes); + if (tagName == "*") return nodes; + } + for (var i = 0, node; node = nodes[i]; i++) + if (node.tagName.toUpperCase() === uTagName) results.push(node); + return results; + } else return root.getElementsByTagName(tagName); + }, + + id: function(nodes, root, id, combinator) { + var targetNode = $(id), h = Selector.handlers; + if (!targetNode) return []; + if (!nodes && root == document) return [targetNode]; + if (nodes) { + if (combinator) { + if (combinator == 'child') { + for (var i = 0, node; node = nodes[i]; i++) + if (targetNode.parentNode == node) return [targetNode]; + } else if (combinator == 'descendant') { + for (var i = 0, node; node = nodes[i]; i++) + if (Element.descendantOf(targetNode, node)) return [targetNode]; + } else if (combinator == 'adjacent') { + for (var i = 0, node; node = nodes[i]; i++) + if (Selector.handlers.previousElementSibling(targetNode) == node) + return [targetNode]; + } else nodes = h[combinator](nodes); + } + for (var i = 0, node; node = nodes[i]; i++) + if (node == targetNode) return [targetNode]; + return []; + } + return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : []; + }, + + className: function(nodes, root, className, combinator) { + if (nodes && combinator) nodes = this[combinator](nodes); + return Selector.handlers.byClassName(nodes, root, className); + }, + + byClassName: function(nodes, root, className) { + if (!nodes) nodes = Selector.handlers.descendant([root]); + var needle = ' ' + className + ' '; + for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) { + nodeClassName = node.className; + if (nodeClassName.length == 0) continue; + if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle)) + results.push(node); + } + return results; + }, + + attrPresence: function(nodes, root, attr, combinator) { + if (!nodes) nodes = root.getElementsByTagName("*"); + if (nodes && combinator) nodes = this[combinator](nodes); + var results = []; + for (var i = 0, node; node = nodes[i]; i++) + if (Element.hasAttribute(node, attr)) results.push(node); + return results; + }, + + attr: function(nodes, root, attr, value, operator, combinator) { + if (!nodes) nodes = root.getElementsByTagName("*"); + if (nodes && combinator) nodes = this[combinator](nodes); + var handler = Selector.operators[operator], results = []; + for (var i = 0, node; node = nodes[i]; i++) { + var nodeValue = Element.readAttribute(node, attr); + if (nodeValue === null) continue; + if (handler(nodeValue, value)) results.push(node); + } + return results; + }, + + pseudo: function(nodes, name, value, root, combinator) { + if (nodes && combinator) nodes = this[combinator](nodes); + if (!nodes) nodes = root.getElementsByTagName("*"); + return Selector.pseudos[name](nodes, value, root); + } + }, + + pseudos: { + 'first-child': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + if (Selector.handlers.previousElementSibling(node)) continue; + results.push(node); + } + return results; + }, + 'last-child': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + if (Selector.handlers.nextElementSibling(node)) continue; + results.push(node); + } + return results; + }, + 'only-child': function(nodes, value, root) { + var h = Selector.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (!h.previousElementSibling(node) && !h.nextElementSibling(node)) + results.push(node); + return results; + }, + 'nth-child': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, formula, root); + }, + 'nth-last-child': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, formula, root, true); + }, + 'nth-of-type': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, formula, root, false, true); + }, + 'nth-last-of-type': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, formula, root, true, true); + }, + 'first-of-type': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, "1", root, false, true); + }, + 'last-of-type': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, "1", root, true, true); + }, + 'only-of-type': function(nodes, formula, root) { + var p = Selector.pseudos; + return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root); + }, + + // handles the an+b logic + getIndices: function(a, b, total) { + if (a == 0) return b > 0 ? [b] : []; + return $R(1, total).inject([], function(memo, i) { + if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i); + return memo; + }); + }, + + // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type + nth: function(nodes, formula, root, reverse, ofType) { + if (nodes.length == 0) return []; + if (formula == 'even') formula = '2n+0'; + if (formula == 'odd') formula = '2n+1'; + var h = Selector.handlers, results = [], indexed = [], m; + h.mark(nodes); + for (var i = 0, node; node = nodes[i]; i++) { + if (!node.parentNode._countedByPrototype) { + h.index(node.parentNode, reverse, ofType); + indexed.push(node.parentNode); + } + } + if (formula.match(/^\d+$/)) { // just a number + formula = Number(formula); + for (var i = 0, node; node = nodes[i]; i++) + if (node.nodeIndex == formula) results.push(node); + } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b + if (m[1] == "-") m[1] = -1; + var a = m[1] ? Number(m[1]) : 1; + var b = m[2] ? Number(m[2]) : 0; + var indices = Selector.pseudos.getIndices(a, b, nodes.length); + for (var i = 0, node, l = indices.length; node = nodes[i]; i++) { + for (var j = 0; j < l; j++) + if (node.nodeIndex == indices[j]) results.push(node); + } + } + h.unmark(nodes); + h.unmark(indexed); + return results; + }, + + 'empty': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + // IE treats comments as element nodes + if (node.tagName == '!' || node.firstChild) continue; + results.push(node); + } + return results; + }, + + 'not': function(nodes, selector, root) { + var h = Selector.handlers, selectorType, m; + var exclusions = new Selector(selector).findElements(root); + h.mark(exclusions); + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (!node._countedByPrototype) results.push(node); + h.unmark(exclusions); + return results; + }, + + 'enabled': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (!node.disabled && (!node.type || node.type !== 'hidden')) + results.push(node); + return results; + }, + + 'disabled': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (node.disabled) results.push(node); + return results; + }, + + 'checked': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (node.checked) results.push(node); + return results; + } + }, + + operators: { + '=': function(nv, v) { return nv == v; }, + '!=': function(nv, v) { return nv != v; }, + '^=': function(nv, v) { return nv == v || nv && nv.startsWith(v); }, + '$=': function(nv, v) { return nv == v || nv && nv.endsWith(v); }, + '*=': function(nv, v) { return nv == v || nv && nv.include(v); }, + '$=': function(nv, v) { return nv.endsWith(v); }, + '*=': function(nv, v) { return nv.include(v); }, + '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); }, + '|=': function(nv, v) { return ('-' + (nv || "").toUpperCase() + + '-').include('-' + (v || "").toUpperCase() + '-'); } + }, + + split: function(expression) { + var expressions = []; + expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) { + expressions.push(m[1].strip()); + }); + return expressions; + }, + + matchElements: function(elements, expression) { + var matches = $$(expression), h = Selector.handlers; + h.mark(matches); + for (var i = 0, results = [], element; element = elements[i]; i++) + if (element._countedByPrototype) results.push(element); + h.unmark(matches); + return results; + }, + + findElement: function(elements, expression, index) { + if (Object.isNumber(expression)) { + index = expression; expression = false; + } + return Selector.matchElements(elements, expression || '*')[index || 0]; + }, + + findChildElements: function(element, expressions) { + expressions = Selector.split(expressions.join(',')); + var results = [], h = Selector.handlers; + for (var i = 0, l = expressions.length, selector; i < l; i++) { + selector = new Selector(expressions[i].strip()); + h.concat(results, selector.findElements(element)); + } + return (l > 1) ? h.unique(results) : results; + } +}); + +if (Prototype.Browser.IE) { + Object.extend(Selector.handlers, { + // IE returns comment nodes on getElementsByTagName("*"). + // Filter them out. + concat: function(a, b) { + for (var i = 0, node; node = b[i]; i++) + if (node.tagName !== "!") a.push(node); + return a; + }, + + // IE improperly serializes _countedByPrototype in (inner|outer)HTML. + unmark: function(nodes) { + for (var i = 0, node; node = nodes[i]; i++) + node.removeAttribute('_countedByPrototype'); + return nodes; + } + }); +} + +function $$() { + return Selector.findChildElements(document, $A(arguments)); +} +var Form = { + reset: function(form) { + $(form).reset(); + return form; + }, + + serializeElements: function(elements, options) { + if (typeof options != 'object') options = { hash: !!options }; + else if (Object.isUndefined(options.hash)) options.hash = true; + var key, value, submitted = false, submit = options.submit; + + var data = elements.inject({ }, function(result, element) { + if (!element.disabled && element.name) { + key = element.name; value = $(element).getValue(); + if (value != null && element.type != 'file' && (element.type != 'submit' || (!submitted && + submit !== false && (!submit || key == submit) && (submitted = true)))) { + if (key in result) { + // a key is already present; construct an array of values + if (!Object.isArray(result[key])) result[key] = [result[key]]; + result[key].push(value); + } + else result[key] = value; + } + } + return result; + }); + + return options.hash ? data : Object.toQueryString(data); + } +}; + +Form.Methods = { + serialize: function(form, options) { + return Form.serializeElements(Form.getElements(form), options); + }, + + getElements: function(form) { + return $A($(form).getElementsByTagName('*')).inject([], + function(elements, child) { + if (Form.Element.Serializers[child.tagName.toLowerCase()]) + elements.push(Element.extend(child)); + return elements; + } + ); + }, + + getInputs: function(form, typeName, name) { + form = $(form); + var inputs = form.getElementsByTagName('input'); + + if (!typeName && !name) return $A(inputs).map(Element.extend); + + for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) { + var input = inputs[i]; + if ((typeName && input.type != typeName) || (name && input.name != name)) + continue; + matchingInputs.push(Element.extend(input)); + } + + return matchingInputs; + }, + + disable: function(form) { + form = $(form); + Form.getElements(form).invoke('disable'); + return form; + }, + + enable: function(form) { + form = $(form); + Form.getElements(form).invoke('enable'); + return form; + }, + + findFirstElement: function(form) { + var elements = $(form).getElements().findAll(function(element) { + return 'hidden' != element.type && !element.disabled; + }); + var firstByIndex = elements.findAll(function(element) { + return element.hasAttribute('tabIndex') && element.tabIndex >= 0; + }).sortBy(function(element) { return element.tabIndex }).first(); + + return firstByIndex ? firstByIndex : elements.find(function(element) { + return ['input', 'select', 'textarea'].include(element.tagName.toLowerCase()); + }); + }, + + focusFirstElement: function(form) { + form = $(form); + form.findFirstElement().activate(); + return form; + }, + + request: function(form, options) { + form = $(form), options = Object.clone(options || { }); + + var params = options.parameters, action = form.readAttribute('action') || ''; + if (action.blank()) action = window.location.href; + options.parameters = form.serialize(true); + + if (params) { + if (Object.isString(params)) params = params.toQueryParams(); + Object.extend(options.parameters, params); + } + + if (form.hasAttribute('method') && !options.method) + options.method = form.method; + + return new Ajax.Request(action, options); + } +}; + +/*--------------------------------------------------------------------------*/ + +Form.Element = { + focus: function(element) { + $(element).focus(); + return element; + }, + + select: function(element) { + $(element).select(); + return element; + } +}; + +Form.Element.Methods = { + serialize: function(element) { + element = $(element); + if (!element.disabled && element.name) { + var value = element.getValue(); + if (value != undefined) { + var pair = { }; + pair[element.name] = value; + return Object.toQueryString(pair); + } + } + return ''; + }, + + getValue: function(element) { + element = $(element); + var method = element.tagName.toLowerCase(); + return Form.Element.Serializers[method](element); + }, + + setValue: function(element, value) { + element = $(element); + var method = element.tagName.toLowerCase(); + Form.Element.Serializers[method](element, value); + return element; + }, + + clear: function(element) { + $(element).value = ''; + return element; + }, + + present: function(element) { + return $(element).value != ''; + }, + + activate: function(element) { + element = $(element); + try { + element.focus(); + if (element.select && (element.tagName.toLowerCase() != 'input' || + !['button', 'reset', 'submit'].include(element.type))) + element.select(); + } catch (e) { } + return element; + }, + + disable: function(element) { + element = $(element); + element.disabled = true; + return element; + }, + + enable: function(element) { + element = $(element); + element.disabled = false; + return element; + } +}; + +/*--------------------------------------------------------------------------*/ + +var Field = Form.Element; +var $F = Form.Element.Methods.getValue; + +/*--------------------------------------------------------------------------*/ + +Form.Element.Serializers = { + input: function(element, value) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + return Form.Element.Serializers.inputSelector(element, value); + default: + return Form.Element.Serializers.textarea(element, value); + } + }, + + inputSelector: function(element, value) { + if (Object.isUndefined(value)) return element.checked ? element.value : null; + else element.checked = !!value; + }, + + textarea: function(element, value) { + if (Object.isUndefined(value)) return element.value; + else element.value = value; + }, + + select: function(element, value) { + if (Object.isUndefined(value)) + return this[element.type == 'select-one' ? + 'selectOne' : 'selectMany'](element); + else { + var opt, currentValue, single = !Object.isArray(value); + for (var i = 0, length = element.length; i < length; i++) { + opt = element.options[i]; + currentValue = this.optionValue(opt); + if (single) { + if (currentValue == value) { + opt.selected = true; + return; + } + } + else opt.selected = value.include(currentValue); + } + } + }, + + selectOne: function(element) { + var index = element.selectedIndex; + return index >= 0 ? this.optionValue(element.options[index]) : null; + }, + + selectMany: function(element) { + var values, length = element.length; + if (!length) return null; + + for (var i = 0, values = []; i < length; i++) { + var opt = element.options[i]; + if (opt.selected) values.push(this.optionValue(opt)); + } + return values; + }, + + optionValue: function(opt) { + // extend element because hasAttribute may not be native + return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text; + } +}; + +/*--------------------------------------------------------------------------*/ + +Abstract.TimedObserver = Class.create(PeriodicalExecuter, { + initialize: function($super, element, frequency, callback) { + $super(callback, frequency); + this.element = $(element); + this.lastValue = this.getValue(); + }, + + execute: function() { + var value = this.getValue(); + if (Object.isString(this.lastValue) && Object.isString(value) ? + this.lastValue != value : String(this.lastValue) != String(value)) { + this.callback(this.element, value); + this.lastValue = value; + } + } +}); + +Form.Element.Observer = Class.create(Abstract.TimedObserver, { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.Observer = Class.create(Abstract.TimedObserver, { + getValue: function() { + return Form.serialize(this.element); + } +}); + +/*--------------------------------------------------------------------------*/ + +Abstract.EventObserver = Class.create({ + initialize: function(element, callback) { + this.element = $(element); + this.callback = callback; + + this.lastValue = this.getValue(); + if (this.element.tagName.toLowerCase() == 'form') + this.registerFormCallbacks(); + else + this.registerCallback(this.element); + }, + + onElementEvent: function() { + var value = this.getValue(); + if (this.lastValue != value) { + this.callback(this.element, value); + this.lastValue = value; + } + }, + + registerFormCallbacks: function() { + Form.getElements(this.element).each(this.registerCallback, this); + }, + + registerCallback: function(element) { + if (element.type) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + Event.observe(element, 'click', this.onElementEvent.bind(this)); + break; + default: + Event.observe(element, 'change', this.onElementEvent.bind(this)); + break; + } + } + } +}); + +Form.Element.EventObserver = Class.create(Abstract.EventObserver, { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.EventObserver = Class.create(Abstract.EventObserver, { + getValue: function() { + return Form.serialize(this.element); + } +}); +if (!window.Event) var Event = { }; + +Object.extend(Event, { + KEY_BACKSPACE: 8, + KEY_TAB: 9, + KEY_RETURN: 13, + KEY_ESC: 27, + KEY_LEFT: 37, + KEY_UP: 38, + KEY_RIGHT: 39, + KEY_DOWN: 40, + KEY_DELETE: 46, + KEY_HOME: 36, + KEY_END: 35, + KEY_PAGEUP: 33, + KEY_PAGEDOWN: 34, + KEY_INSERT: 45, + + cache: { }, + + relatedTarget: function(event) { + var element; + switch(event.type) { + case 'mouseover': element = event.fromElement; break; + case 'mouseout': element = event.toElement; break; + default: return null; + } + return Element.extend(element); + } +}); + +Event.Methods = (function() { + var isButton; + + if (Prototype.Browser.IE) { + var buttonMap = { 0: 1, 1: 4, 2: 2 }; + isButton = function(event, code) { + return event.button == buttonMap[code]; + }; + + } else if (Prototype.Browser.WebKit) { + isButton = function(event, code) { + switch (code) { + case 0: return event.which == 1 && !event.metaKey; + case 1: return event.which == 1 && event.metaKey; + default: return false; + } + }; + + } else { + isButton = function(event, code) { + return event.which ? (event.which === code + 1) : (event.button === code); + }; + } + + return { + isLeftClick: function(event) { return isButton(event, 0) }, + isMiddleClick: function(event) { return isButton(event, 1) }, + isRightClick: function(event) { return isButton(event, 2) }, + + element: function(event) { + event = Event.extend(event); + + var node = event.target, + type = event.type, + currentTarget = event.currentTarget; + + if (currentTarget && currentTarget.tagName) { + // Firefox screws up the "click" event when moving between radio buttons + // via arrow keys. It also screws up the "load" and "error" events on images, + // reporting the document as the target instead of the original image. + if (type === 'load' || type === 'error' || + (type === 'click' && currentTarget.tagName.toLowerCase() === 'input' + && currentTarget.type === 'radio')) + node = currentTarget; + } + if (node.nodeType == Node.TEXT_NODE) node = node.parentNode; + return Element.extend(node); + }, + + findElement: function(event, expression) { + var element = Event.element(event); + if (!expression) return element; + var elements = [element].concat(element.ancestors()); + return Selector.findElement(elements, expression, 0); + }, + + pointer: function(event) { + var docElement = document.documentElement, + body = document.body || { scrollLeft: 0, scrollTop: 0 }; + return { + x: event.pageX || (event.clientX + + (docElement.scrollLeft || body.scrollLeft) - + (docElement.clientLeft || 0)), + y: event.pageY || (event.clientY + + (docElement.scrollTop || body.scrollTop) - + (docElement.clientTop || 0)) + }; + }, + + pointerX: function(event) { return Event.pointer(event).x }, + pointerY: function(event) { return Event.pointer(event).y }, + + stop: function(event) { + Event.extend(event); + event.preventDefault(); + event.stopPropagation(); + event.stopped = true; + } + }; +})(); + +Event.extend = (function() { + var methods = Object.keys(Event.Methods).inject({ }, function(m, name) { + m[name] = Event.Methods[name].methodize(); + return m; + }); + + if (Prototype.Browser.IE) { + Object.extend(methods, { + stopPropagation: function() { this.cancelBubble = true }, + preventDefault: function() { this.returnValue = false }, + inspect: function() { return "[object Event]" } + }); + + return function(event) { + if (!event) return false; + if (event._extendedByPrototype) return event; + + event._extendedByPrototype = Prototype.emptyFunction; + var pointer = Event.pointer(event); + Object.extend(event, { + target: event.srcElement, + relatedTarget: Event.relatedTarget(event), + pageX: pointer.x, + pageY: pointer.y + }); + return Object.extend(event, methods); + }; + + } else { + Event.prototype = Event.prototype || document.createEvent("HTMLEvents")['__proto__']; + Object.extend(Event.prototype, methods); + return Prototype.K; + } +})(); + +Object.extend(Event, (function() { + var cache = Event.cache; + + function getEventID(element) { + if (element._prototypeEventID) return element._prototypeEventID[0]; + arguments.callee.id = arguments.callee.id || 1; + return element._prototypeEventID = [++arguments.callee.id]; + } + + function getDOMEventName(eventName) { + if (eventName && eventName.include(':')) return "dataavailable"; + return eventName; + } + + function getCacheForID(id) { + return cache[id] = cache[id] || { }; + } + + function getWrappersForEventName(id, eventName) { + var c = getCacheForID(id); + return c[eventName] = c[eventName] || []; + } + + function createWrapper(element, eventName, handler) { + var id = getEventID(element); + var c = getWrappersForEventName(id, eventName); + if (c.pluck("handler").include(handler)) return false; + + var wrapper = function(event) { + if (!Event || !Event.extend || + (event.eventName && event.eventName != eventName)) + return false; + + Event.extend(event); + handler.call(element, event); + }; + + wrapper.handler = handler; + c.push(wrapper); + return wrapper; + } + + function findWrapper(id, eventName, handler) { + var c = getWrappersForEventName(id, eventName); + return c.find(function(wrapper) { return wrapper.handler == handler }); + } + + function destroyWrapper(id, eventName, handler) { + var c = getCacheForID(id); + if (!c[eventName]) return false; + c[eventName] = c[eventName].without(findWrapper(id, eventName, handler)); + } + + function destroyCache() { + for (var id in cache) + for (var eventName in cache[id]) + cache[id][eventName] = null; + } + + + // Internet Explorer needs to remove event handlers on page unload + // in order to avoid memory leaks. + if (window.attachEvent) { + window.attachEvent("onunload", destroyCache); + } + + // Safari has a dummy event handler on page unload so that it won't + // use its bfcache. Safari <= 3.1 has an issue with restoring the "document" + // object when page is returned to via the back button using its bfcache. + if (Prototype.Browser.WebKit) { + window.addEventListener('unload', Prototype.emptyFunction, false); + } + + return { + observe: function(element, eventName, handler) { + element = $(element); + var name = getDOMEventName(eventName); + + var wrapper = createWrapper(element, eventName, handler); + if (!wrapper) return element; + + if (element.addEventListener) { + element.addEventListener(name, wrapper, false); + } else { + element.attachEvent("on" + name, wrapper); + } + + return element; + }, + + stopObserving: function(element, eventName, handler) { + element = $(element); + var id = getEventID(element), name = getDOMEventName(eventName); + + if (!handler && eventName) { + getWrappersForEventName(id, eventName).each(function(wrapper) { + element.stopObserving(eventName, wrapper.handler); + }); + return element; + + } else if (!eventName) { + Object.keys(getCacheForID(id)).each(function(eventName) { + element.stopObserving(eventName); + }); + return element; + } + + var wrapper = findWrapper(id, eventName, handler); + if (!wrapper) return element; + + if (element.removeEventListener) { + element.removeEventListener(name, wrapper, false); + } else { + element.detachEvent("on" + name, wrapper); + } + + destroyWrapper(id, eventName, handler); + + return element; + }, + + fire: function(element, eventName, memo) { + element = $(element); + if (element == document && document.createEvent && !element.dispatchEvent) + element = document.documentElement; + + var event; + if (document.createEvent) { + event = document.createEvent("HTMLEvents"); + event.initEvent("dataavailable", true, true); + } else { + event = document.createEventObject(); + event.eventType = "ondataavailable"; + } + + event.eventName = eventName; + event.memo = memo || { }; + + if (document.createEvent) { + element.dispatchEvent(event); + } else { + element.fireEvent(event.eventType, event); + } + + return Event.extend(event); + } + }; +})()); + +Object.extend(Event, Event.Methods); + +Element.addMethods({ + fire: Event.fire, + observe: Event.observe, + stopObserving: Event.stopObserving +}); + +Object.extend(document, { + fire: Element.Methods.fire.methodize(), + observe: Element.Methods.observe.methodize(), + stopObserving: Element.Methods.stopObserving.methodize(), + loaded: false +}); + +(function() { + /* Support for the DOMContentLoaded event is based on work by Dan Webb, + Matthias Miller, Dean Edwards and John Resig. */ + + var timer; + + function fireContentLoadedEvent() { + if (document.loaded) return; + if (timer) window.clearInterval(timer); + document.fire("dom:loaded"); + document.loaded = true; + } + + if (document.addEventListener) { + if (Prototype.Browser.WebKit) { + timer = window.setInterval(function() { + if (/loaded|complete/.test(document.readyState)) + fireContentLoadedEvent(); + }, 0); + + Event.observe(window, "load", fireContentLoadedEvent); + + } else { + document.addEventListener("DOMContentLoaded", + fireContentLoadedEvent, false); + } + + } else { + document.write("