From 739bf0482b3aa48fc4e95f3d7b929ab64ef6ff34 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 5 Jul 2011 20:31:35 +0000 Subject: [PATCH 01/66] Trans --- htdocs/langs/en_US/main.lang | 1 + htdocs/langs/fr_FR/main.lang | 1 + 2 files changed, 2 insertions(+) diff --git a/htdocs/langs/en_US/main.lang b/htdocs/langs/en_US/main.lang index 173895a6d46..6bc0c120b94 100644 --- a/htdocs/langs/en_US/main.lang +++ b/htdocs/langs/en_US/main.lang @@ -598,6 +598,7 @@ IM=Instant messaging NewAttribute=New attribute AttributeCode=Attribute code OptionalFieldsSetup=Extra attributes setup +URLPhoto=Url of photo/logo # Week day Monday=Monday diff --git a/htdocs/langs/fr_FR/main.lang b/htdocs/langs/fr_FR/main.lang index 86896b0e22b..1e224e608e0 100644 --- a/htdocs/langs/fr_FR/main.lang +++ b/htdocs/langs/fr_FR/main.lang @@ -598,6 +598,7 @@ IM=Messagerie instantannée NewAttribute=Nouvel attribut AttributeCode=Code de l'attribut OptionalFieldsSetup=Configuration des attributs complémentaires +URLPhoto=Url vers photo/logo # Week day Monday=Lundi From 0f483a1654b8cc37f0504ec19817ccecb96ea137 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 5 Jul 2011 20:39:36 +0000 Subject: [PATCH 02/66] Trans --- htdocs/adherents/admin/adherent_extrafields.php | 6 +++--- htdocs/adherents/admin/public.php | 7 +++++-- htdocs/admin/societe_extrafields.php | 6 +++--- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/htdocs/adherents/admin/adherent_extrafields.php b/htdocs/adherents/admin/adherent_extrafields.php index 5b60a5bdbc8..f4f71ae2fa3 100755 --- a/htdocs/adherents/admin/adherent_extrafields.php +++ b/htdocs/adherents/admin/adherent_extrafields.php @@ -22,7 +22,7 @@ * \file htdocs/adherents/admin/adherent_extrafields.php * \ingroup member * \brief Page to setup extra fields of members - * \version $Id$ + * \version $Id: adherent_extrafields.php,v 1.3 2011/07/05 20:39:36 eldy Exp $ */ require("../../main.inc.php"); @@ -174,7 +174,7 @@ llxHeader('',$langs->trans("MembersSetup"),$help_url); $linkback=''.$langs->trans("BackToModuleList").''; -print_fiche_titre($langs->trans("OptionalFieldsSetup"),$linkback,'setup'); +print_fiche_titre($langs->trans("MembersSetup"),$linkback,'setup'); $head = member_admin_prepare_head($adh); @@ -312,5 +312,5 @@ if ($_GET["attrname"] && $action == 'edit') $db->close(); -llxFooter('$Date$ - $Revision$'); +llxFooter('$Date: 2011/07/05 20:39:36 $ - $Revision: 1.3 $'); ?> diff --git a/htdocs/adherents/admin/public.php b/htdocs/adherents/admin/public.php index 6fcf9e8293b..c36ac9337c9 100755 --- a/htdocs/adherents/admin/public.php +++ b/htdocs/adherents/admin/public.php @@ -22,7 +22,7 @@ * \ingroup member * \brief File of main public page for member module * \author Laurent Destailleur - * \version $Id: public.php,v 1.1 2011/07/03 16:00:19 eldy Exp $ + * \version $Id: public.php,v 1.2 2011/07/05 20:39:36 eldy Exp $ */ require("../../main.inc.php"); @@ -30,6 +30,9 @@ require_once(DOL_DOCUMENT_ROOT."/lib/admin.lib.php"); require_once(DOL_DOCUMENT_ROOT."/lib/company.lib.php"); require_once(DOL_DOCUMENT_ROOT."/lib/member.lib.php"); +$langs->load("members"); +$langs->load("admin"); + $action=GETPOST('action'); @@ -184,5 +187,5 @@ print ''; $db->close(); -llxFooter('$Date: 2011/07/03 16:00:19 $ - $Revision: 1.1 $'); +llxFooter('$Date: 2011/07/05 20:39:36 $ - $Revision: 1.2 $'); ?> diff --git a/htdocs/admin/societe_extrafields.php b/htdocs/admin/societe_extrafields.php index fe274220197..61c61669816 100755 --- a/htdocs/admin/societe_extrafields.php +++ b/htdocs/admin/societe_extrafields.php @@ -22,7 +22,7 @@ * \file htdocs/admin/societe_extrafields.php * \ingroup societe * \brief Page to setup extra fields of third party - * \version $Id: societe_extrafields.php,v 1.4 2011/07/04 07:38:22 eldy Exp $ + * \version $Id: societe_extrafields.php,v 1.5 2011/07/05 20:43:25 eldy Exp $ */ require("../main.inc.php"); @@ -174,7 +174,7 @@ llxHeader('',$langs->trans("CompanySetup"),$help_url); $linkback=''.$langs->trans("BackToModuleList").''; -print_fiche_titre($langs->trans("OptionalFieldsSetup"),$linkback,'setup'); +print_fiche_titre($langs->trans("CompanySetup"),$linkback,'setup'); $head = societe_admin_prepare_head($adh); @@ -312,5 +312,5 @@ if ($_GET["attrname"] && $action == 'edit') $db->close(); -llxFooter('$Date: 2011/07/04 07:38:22 $ - $Revision: 1.4 $'); +llxFooter('$Date: 2011/07/05 20:43:25 $ - $Revision: 1.5 $'); ?> From 68029751fd7a6809b31f15bf83d5925f4bf36727 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 5 Jul 2011 22:40:35 +0000 Subject: [PATCH 03/66] Work on ajax file upload component. Component must be used only to make "upload" or multiupload. Output list of files must be independant of upload component because will be enhanced differently. Also, usage of upload component is more clear like that. Still TODO: Once last files was uploaded (when there is several files), we must launch the refresh of page. --- htdocs/core/ajaxfileupload.php | 74 ++++++++++++++++------- htdocs/core/class/html.formfile.class.php | 25 ++++---- htdocs/core/tpl/ajaxfileupload.tpl.php | 20 ++---- htdocs/lib/images.lib.php | 12 ++-- htdocs/societe/document.php | 23 ++++--- 5 files changed, 89 insertions(+), 65 deletions(-) diff --git a/htdocs/core/ajaxfileupload.php b/htdocs/core/ajaxfileupload.php index cc51d9c2836..8af04307419 100644 --- a/htdocs/core/ajaxfileupload.php +++ b/htdocs/core/ajaxfileupload.php @@ -22,11 +22,12 @@ if (! defined('NOREQUIREHTML')) define('NOREQUIREHTML','1'); // If we don't nee //if (! defined("NOLOGIN")) define("NOLOGIN",'1'); // If this page is public (can be called outside logged session) -$res=@include("../../main.inc.php"); // For "custom" directory -if (! $res) $res=@include("../main.inc.php"); // For root directory +$res=@include("../main.inc.php"); // For "root" directory +if (! $res) $res=@include("../../main.inc.php"); // For "custom" directory if (! $res) @include("../../../../dolibarr/htdocs/main.inc.php"); // Used on dev env only require_once(DOL_DOCUMENT_ROOT."/lib/files.lib.php"); +require_once(DOL_DOCUMENT_ROOT."/lib/images.lib.php"); error_reporting(E_ALL | E_STRICT); @@ -37,19 +38,23 @@ error_reporting(E_ALL | E_STRICT); $fk_element = GETPOST('fk_element'); $element = GETPOST('element'); + +/** + * + */ class UploadHandler { private $options; private $fk_elment; private $element; - + function __construct($options=null,$fk_element=null,$element=null) { - + global $conf; - + $this->fk_element=$fk_element; $this->element=$element; - + $this->options = array( 'script_url' => $_SERVER['PHP_SELF'], 'upload_dir' => $conf->$element->dir_output . '/' . $fk_element . '/', @@ -86,7 +91,7 @@ class UploadHandler $this->options = array_merge_recursive($this->options, $options); } } - + private function get_file_object($file_name) { $file_path = $this->options['upload_dir'].$file_name; if (is_file($file_path) && $file_name[0] !== '.') { @@ -97,8 +102,8 @@ class UploadHandler $file->url = $this->options['upload_url'].rawurlencode($file->name); foreach($this->options['image_versions'] as $version => $options) { if (is_file($options['upload_dir'].$file_name)) { - $file->{$version.'_url'} = $options['upload_url'] - .rawurlencode($file->name); + $tmp=explode('.',$file->name); + $file->{$version.'_url'} = $options['upload_url'].rawurlencode($tmp[0].'_mini.'.$tmp[1]); } } $file->delete_url = $this->options['script_url'] @@ -108,7 +113,7 @@ class UploadHandler } return null; } - + private function get_file_objects() { return array_values(array_filter(array_map( array($this, 'get_file_object'), @@ -116,9 +121,14 @@ class UploadHandler ))); } + /** + * Create thumbs + * options is array('max_width', 'max_height') + */ private function create_scaled_image($file_name, $options) { $file_path = $this->options['upload_dir'].$file_name; $new_file_path = $options['upload_dir'].$file_name; + if (create_exdir($options['upload_dir']) >= 0) { list($img_width, $img_height) = @getimagesize($file_path); @@ -134,6 +144,11 @@ class UploadHandler } $new_width = $img_width * $scale; $new_height = $img_height * $scale; + + + $res=true; + $res=vignette($file_path,$options['max_width'],$options['max_height'],'_mini'); + /* Replaced with more efficient function vignette $new_img = @imagecreatetruecolor($new_width, $new_height); switch (strtolower(substr(strrchr($file_name, '.'), 1))) { case 'jpg': @@ -164,14 +179,18 @@ class UploadHandler // Free up memory (imagedestroy does not delete files): @imagedestroy($src_img); @imagedestroy($new_img); - return $success; + */ + + //return $success; + if (preg_match('/error/i',$res)) return false; + return true; } else { return false; } } - + private function has_error($uploaded_file, $file, $error) { if ($error) { return $error; @@ -201,7 +220,7 @@ class UploadHandler } return $error; } - + private function handle_file_upload($uploaded_file, $name, $size, $type, $error) { $file = new stdClass(); $file->name = basename(stripslashes($name)); @@ -238,10 +257,12 @@ class UploadHandler $file_size = filesize($file_path); if ($file_size === $file->size) { $file->url = $this->options['upload_url'].rawurlencode($file->name); - foreach($this->options['image_versions'] as $version => $options) { - if ($this->create_scaled_image($file->name, $options)) { - $file->{$version.'_url'} = $options['upload_url'] - .rawurlencode($file->name); + foreach($this->options['image_versions'] as $version => $options) + { + if ($this->create_scaled_image($file->name, $options)) + { + $tmp=explode('.',$file->name); + $file->{$version.'_url'} = $options['upload_url'].rawurlencode($tmp[0].'_mini.'.$tmp[1]); } } } else if ($this->options['discard_aborted_uploads']) { @@ -257,10 +278,10 @@ class UploadHandler } return $file; } - + public function get() { $file_name = isset($_REQUEST['file']) ? - basename(stripslashes($_REQUEST['file'])) : null; + basename(stripslashes($_REQUEST['file'])) : null; if ($file_name) { $info = $this->get_file_object($file_name); } else { @@ -269,7 +290,7 @@ class UploadHandler header('Content-type: application/json'); echo json_encode($info); } - + public function post() { $upload = isset($_FILES[$this->options['param_name']]) ? $_FILES[$this->options['param_name']] : array( @@ -314,7 +335,7 @@ class UploadHandler } echo json_encode($info); } - + public function delete() { $file_name = isset($_REQUEST['file']) ? basename(stripslashes($_REQUEST['file'])) : null; @@ -333,6 +354,12 @@ class UploadHandler } } + + +/* + * View + */ + $upload_handler = new UploadHandler(null,$fk_element,$element); header('Pragma: no-cache'); @@ -352,5 +379,10 @@ switch ($_SERVER['REQUEST_METHOD']) { break; default: header('HTTP/1.0 405 Method Not Allowed'); + exit; } + + +$db->close(); + ?> \ No newline at end of file diff --git a/htdocs/core/class/html.formfile.class.php b/htdocs/core/class/html.formfile.class.php index c08006e2935..46d699019fc 100644 --- a/htdocs/core/class/html.formfile.class.php +++ b/htdocs/core/class/html.formfile.class.php @@ -22,7 +22,7 @@ * \file htdocs/core/class/html.formfile.class.php * \ingroup core * \brief File of class to offer components to list and upload files - * \version $Id: html.formfile.class.php,v 1.38 2011/07/05 09:14:27 hregis Exp $ + * \version $Id: html.formfile.class.php,v 1.39 2011/07/05 22:40:36 eldy Exp $ */ @@ -738,7 +738,7 @@ class FormFile function form_ajaxfileupload($object) { global $langs; - + // PHP post_max_size $post_max_size = ini_get('post_max_size'); $mul_post_max_size = substr($post_max_size, -1); @@ -751,16 +751,16 @@ class FormFile $upload_max_filesize = $mul_upload_max_filesize*(int)$upload_max_filesize; // Max file size $max_file_size = (($post_max_size < $upload_max_filesize) ? $post_max_size : $upload_max_filesize); - + print ''; - + print '
'; print '
'; print ''; @@ -820,6 +820,7 @@ class FormFile print ''; print '
'; print '
'; + print ''; print ''; print ''; @@ -828,10 +829,12 @@ class FormFile print ''; print ''; print '
'.$langs->trans("Documents2").'
'; + print '
'; + print '
'; print ''; - + // Include template include(DOL_DOCUMENT_ROOT.'/core/tpl/ajaxfileupload.tpl.php'); diff --git a/htdocs/core/tpl/ajaxfileupload.tpl.php b/htdocs/core/tpl/ajaxfileupload.tpl.php index f1d6026c981..cfbfdad12e3 100644 --- a/htdocs/core/tpl/ajaxfileupload.tpl.php +++ b/htdocs/core/tpl/ajaxfileupload.tpl.php @@ -15,7 +15,7 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - * $Id: ajaxfileupload.tpl.php,v 1.6 2011/07/05 09:30:11 hregis Exp $ + * $Id: ajaxfileupload.tpl.php,v 1.7 2011/07/05 22:40:35 eldy Exp $ */ ?> @@ -65,22 +65,12 @@ {{else}}${error} {{/if}} - {{else}} - - - ${name} + + - - {{if thumbnail_url}} - - {{/if}} - - ${sizef} - {{/if}} - - - + +
\ No newline at end of file diff --git a/htdocs/lib/images.lib.php b/htdocs/lib/images.lib.php index 4a91c5af79f..d7841216fdb 100644 --- a/htdocs/lib/images.lib.php +++ b/htdocs/lib/images.lib.php @@ -21,7 +21,7 @@ /** * \file htdocs/lib/images.lib.php * \brief Set of function for manipulating images - * \version $Id$ + * \version $Id: images.lib.php,v 1.22 2011/07/05 22:40:36 eldy Exp $ */ // Define size of logo small and mini @@ -287,10 +287,10 @@ function dol_imageResizeOrCrop($file, $mode, $newWidth, $newHeight, $src_x=0, $s /** * Create a thumbnail from an image file (Supported extensions are gif, jpg, png and bmp). * If file is myfile.jpg, new file may be myfile_small.jpg - * @param file PAth of file to resize + * @param file Path of source file to resize * @param maxWidth Largeur maximum que dois faire la miniature (-1=unchanged, 160 by default) * @param maxHeight Hauteur maximum que dois faire l'image (-1=unchanged, 120 by default) - * @param extName Extension to differenciate thumb file name + * @param extName Extension to differenciate thumb file name ('_small', '_mini') * @param quality Quality of compression (0=worst, 100=best) * @param outdir Directory where to store thumb * @param targetformat New format of target (1,2,3,4 or 0 to keep old format) @@ -327,12 +327,12 @@ function vignette($file, $maxWidth = 160, $maxHeight = 120, $extName='_small', $ elseif(!is_numeric($maxWidth) || empty($maxWidth) || $maxWidth < -1){ // Si la largeur max est incorrecte (n'est pas numerique, est vide, ou est inferieure a 0) dol_syslog('Wrong value for parameter maxWidth',LOG_ERR); - return 'Wrong value for parameter maxWidth'; + return 'Error: Wrong value for parameter maxWidth'; } elseif(!is_numeric($maxHeight) || empty($maxHeight) || $maxHeight < -1){ // Si la hauteur max est incorrecte (n'est pas numerique, est vide, ou est inferieure a 0) dol_syslog('Wrong value for parameter maxHeight',LOG_ERR); - return 'Wrong value for parameter maxHeight'; + return 'Error: Wrong value for parameter maxHeight'; } $fichier = realpath($file); // Chemin canonique absolu de l'image @@ -374,7 +374,7 @@ function vignette($file, $maxWidth = 160, $maxHeight = 120, $extName='_small', $ if (! function_exists($imgfonction)) { // Fonctions de conversion non presente dans ce PHP - return 'Creation de vignette impossible. Ce PHP ne supporte pas les fonctions du module GD '.$imgfonction; + return 'Error: Creation of thumbs not possible. This PHP does not support GD function '.$imgfonction; } } diff --git a/htdocs/societe/document.php b/htdocs/societe/document.php index c5af4de609c..9a6495b4c6e 100644 --- a/htdocs/societe/document.php +++ b/htdocs/societe/document.php @@ -23,7 +23,7 @@ * \file htdocs/societe/document.php * \brief Tab for documents linked to third party * \ingroup societe - * \version $Id: document.php,v 1.33 2011/07/05 17:21:19 hregis Exp $ + * \version $Id: document.php,v 1.34 2011/07/05 22:40:35 eldy Exp $ */ require("../main.inc.php"); @@ -85,7 +85,7 @@ if ( $_POST["sendit"] && ! empty($conf->global->MAIN_UPLOAD_DOC)) // Create small thumbs for company (Ratio is near 16/9) // Used on logon for example $imgThumbSmall = vignette($upload_dir . "/" . $_FILES['userfile']['name'], $maxwidthsmall, $maxheightsmall, '_small', $quality, "thumbs"); - + // Create mini thumbs for company (Ratio is near 16/9) // Used on menu or for setup page for example $imgThumbMini = vignette($upload_dir . "/" . $_FILES['userfile']['name'], $maxwidthmini, $maxheightmini, '_mini', $quality, "thumbs"); @@ -209,23 +209,22 @@ if ($socid > 0) $ret=$html->form_confirm($_SERVER["PHP_SELF"].'?socid='.$socid.'&urlfile='.urldecode($_GET["urlfile"]), $langs->trans('DeleteFile'), $langs->trans('ConfirmDeleteFile'), 'confirm_deletefile', '', 0, 1); if ($ret == 'html') print '
'; } - + $formfile=new FormFile($db); - + + // Show upload form if ($conf->global->MAIN_USE_JQUERY_FILEUPLOAD) { $formfile->form_ajaxfileupload($object); } else { - // Affiche formulaire upload $formfile->form_attach_new_file($_SERVER["PHP_SELF"].'?socid='.$socid,'',0,0,$user->rights->societe->creer); - - - // List of document - $param='&socid='.$object->id; - $formfile->list_of_documents($filearray,$object,'societe',$param); - } + } + + // List of document + $param='&socid='.$object->id; + $formfile->list_of_documents($filearray,$object,'societe',$param); print "

"; @@ -289,6 +288,6 @@ else $db->close(); -llxFooter('$Date: 2011/07/05 17:21:19 $ - $Revision: 1.33 $'); +llxFooter('$Date: 2011/07/05 22:40:35 $ - $Revision: 1.34 $'); ?> From c5c250bf154989d8b29b2742ab678077c1ca7274 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 5 Jul 2011 22:51:24 +0000 Subject: [PATCH 04/66] Work on ajax file upload component. Component must be used only to make "upload" or multiupload. Output list of files must be independant of upload component because will be enhanced differently. Also, usage of upload component is more clear like that. Still TODO: Once last files was uploaded (when there is several files), we must launch the refresh of page. --- htdocs/core/ajaxfileupload.php | 58 ++++------------------------------ htdocs/lib/images.lib.php | 10 +++--- 2 files changed, 12 insertions(+), 56 deletions(-) diff --git a/htdocs/core/ajaxfileupload.php b/htdocs/core/ajaxfileupload.php index 8af04307419..3420bda0a95 100644 --- a/htdocs/core/ajaxfileupload.php +++ b/htdocs/core/ajaxfileupload.php @@ -72,18 +72,14 @@ class UploadHandler // uploaded images. You can also add additional versions with // their own upload directories: /* - 'large' => array( + 'small' => array( 'upload_dir' => dirname(__FILE__).'/files/', - 'upload_url' => dirname($_SERVER['PHP_SELF']).'/files/', - 'max_width' => 1920, - 'max_height' => 1200 + 'upload_url' => dirname($_SERVER['PHP_SELF']).'/files/' ), */ - 'thumbnail' => array( + 'thumbs' => array( 'upload_dir' => $conf->$element->dir_output . '/' . $fk_element . '/thumbs/', - 'upload_url' => DOL_URL_ROOT.'/document.php?modulepart='.$element.'&attachment=1&file=/'.$fk_element.'/thumbs/', - 'max_width' => 40, - 'max_height' => 40 + 'upload_url' => DOL_URL_ROOT.'/document.php?modulepart='.$element.'&attachment=1&file=/'.$fk_element.'/thumbs/' ) ) ); @@ -126,6 +122,7 @@ class UploadHandler * options is array('max_width', 'max_height') */ private function create_scaled_image($file_name, $options) { + global $maxwidthmini, $maxheightmini; $file_path = $this->options['upload_dir'].$file_name; $new_file_path = $options['upload_dir'].$file_name; @@ -135,51 +132,8 @@ class UploadHandler if (!$img_width || !$img_height) { return false; } - $scale = min( - $options['max_width'] / $img_width, - $options['max_height'] / $img_height - ); - if ($scale > 1) { - $scale = 1; - } - $new_width = $img_width * $scale; - $new_height = $img_height * $scale; - - $res=true; - $res=vignette($file_path,$options['max_width'],$options['max_height'],'_mini'); - /* Replaced with more efficient function vignette - $new_img = @imagecreatetruecolor($new_width, $new_height); - switch (strtolower(substr(strrchr($file_name, '.'), 1))) { - case 'jpg': - case 'jpeg': - $src_img = @imagecreatefromjpeg($file_path); - $write_image = 'imagejpeg'; - break; - case 'gif': - $src_img = @imagecreatefromgif($file_path); - $write_image = 'imagegif'; - break; - case 'png': - $src_img = @imagecreatefrompng($file_path); - $write_image = 'imagepng'; - break; - default: - $src_img = $image_method = null; - } - $success = $src_img && @imagecopyresampled( - $new_img, - $src_img, - 0, 0, 0, 0, - $new_width, - $new_height, - $img_width, - $img_height - ) && $write_image($new_img, $new_file_path); - // Free up memory (imagedestroy does not delete files): - @imagedestroy($src_img); - @imagedestroy($new_img); - */ + $res=vignette($file_path,$maxwidthmini,$maxheightmini,'_mini'); //return $success; if (preg_match('/error/i',$res)) return false; diff --git a/htdocs/lib/images.lib.php b/htdocs/lib/images.lib.php index d7841216fdb..f561fd772fb 100644 --- a/htdocs/lib/images.lib.php +++ b/htdocs/lib/images.lib.php @@ -21,7 +21,7 @@ /** * \file htdocs/lib/images.lib.php * \brief Set of function for manipulating images - * \version $Id: images.lib.php,v 1.22 2011/07/05 22:40:36 eldy Exp $ + * \version $Id: images.lib.php,v 1.23 2011/07/05 22:51:24 eldy Exp $ */ // Define size of logo small and mini @@ -275,7 +275,8 @@ function dol_imageResizeOrCrop($file, $mode, $newWidth, $newHeight, $src_x=0, $s // Set permissions on file if (! empty($conf->global->MAIN_UMASK)) @chmod($imgThumbName, octdec($conf->global->MAIN_UMASK)); - // Free memory + // Free memory. This does not delete image. + imagedestroy($img); imagedestroy($imgThumb); clearstatcache(); // File was replaced by a modified one, so we clear file caches. @@ -510,8 +511,9 @@ function vignette($file, $maxWidth = 160, $maxHeight = 120, $extName='_small', $ // Set permissions on file if (! empty($conf->global->MAIN_UMASK)) @chmod($imgThumbName, octdec($conf->global->MAIN_UMASK)); - // Free memory - imagedestroy($imgThumb); + // Free memory. This does not delete image. + imagedestroy($img); + imagedestroy($imgThumb); return $imgThumbName; } From cad381d43c1f29b862bfeda1de11a7dc16cc8638 Mon Sep 17 00:00:00 2001 From: Regis Houssin Date: Wed, 6 Jul 2011 05:08:52 +0000 Subject: [PATCH 05/66] Fix: add old values for triggers --- htdocs/societe/class/societe.class.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/htdocs/societe/class/societe.class.php b/htdocs/societe/class/societe.class.php index 7caf9a3e478..3fa88052e4d 100644 --- a/htdocs/societe/class/societe.class.php +++ b/htdocs/societe/class/societe.class.php @@ -27,7 +27,7 @@ * \file htdocs/societe/class/societe.class.php * \ingroup societe * \brief File for third party class - * \version $Id: societe.class.php,v 1.89 2011/07/04 11:41:05 eldy Exp $ + * \version $Id: societe.class.php,v 1.90 2011/07/06 05:08:52 hregis Exp $ */ require_once(DOL_DOCUMENT_ROOT."/core/class/commonobject.class.php"); @@ -377,6 +377,14 @@ class Societe extends CommonObject dol_syslog("Societe::Update id=".$id." call_trigger=".$call_trigger." allowmodcodeclient=".$allowmodcodeclient." allowmodcodefournisseur=".$allowmodcodefournisseur); + // For triggers + if ($call_trigger) + { + $objectstatic=new Societe($this->db); + $objectstatic->fetch($id); + $this->oldobject = $objectstatic; + } + $now=dol_now(); // Clean parameters @@ -919,7 +927,7 @@ class Societe extends CommonObject // Update link in member table $sql = "UPDATE ".MAIN_DB_PREFIX."adherent"; - $sql.= " SET fk_soc = NULL where fk_soc = " . $id; + $sql.= " SET fk_soc = NULL WHERE fk_soc = " . $id; dol_syslog("Societe::Delete sql=".$sql, LOG_DEBUG); if ($this->db->query($sql)) { From 91542ab3b864f55d2df70094731290b64f89e0fd Mon Sep 17 00:00:00 2001 From: Regis Houssin Date: Wed, 6 Jul 2011 06:21:51 +0000 Subject: [PATCH 06/66] Fix: reload page after file upload done Todo: use jquery template for view files, don't reload page ! --- htdocs/core/ajaxfileupload.php | 31 +++++++--- htdocs/core/class/html.formfile.class.php | 74 ++++++++++++----------- 2 files changed, 61 insertions(+), 44 deletions(-) diff --git a/htdocs/core/ajaxfileupload.php b/htdocs/core/ajaxfileupload.php index 3420bda0a95..68199e8588d 100644 --- a/htdocs/core/ajaxfileupload.php +++ b/htdocs/core/ajaxfileupload.php @@ -1,13 +1,26 @@ + * Copyright (C) 2011 Laurent Destailleur * - * Copyright 2010, Sebastian Tschan - * https://blueimp.net + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. * - * Licensed under the MIT license: - * http://creativecommons.org/licenses/MIT/ + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/** + * \file htdocs/core/ajaxfileupload.php + * \brief File to return Ajax response on file upload + * \version $Id: ajaxfileupload.php,v 1.8 2011/07/06 06:21:51 hregis Exp $ */ //if (! defined('NOREQUIREUSER')) define('NOREQUIREUSER','1'); @@ -22,9 +35,7 @@ if (! defined('NOREQUIREHTML')) define('NOREQUIREHTML','1'); // If we don't nee //if (! defined("NOLOGIN")) define("NOLOGIN",'1'); // If this page is public (can be called outside logged session) -$res=@include("../main.inc.php"); // For "root" directory -if (! $res) $res=@include("../../main.inc.php"); // For "custom" directory -if (! $res) @include("../../../../dolibarr/htdocs/main.inc.php"); // Used on dev env only +require("../main.inc.php"); require_once(DOL_DOCUMENT_ROOT."/lib/files.lib.php"); require_once(DOL_DOCUMENT_ROOT."/lib/images.lib.php"); diff --git a/htdocs/core/class/html.formfile.class.php b/htdocs/core/class/html.formfile.class.php index 46d699019fc..4aa64bed577 100644 --- a/htdocs/core/class/html.formfile.class.php +++ b/htdocs/core/class/html.formfile.class.php @@ -1,5 +1,5 @@ +/* Copyright (c) 2008-2011 Laurent Destailleur * Copyright (C) 2010-2011 Regis Houssin * Copyright (c) 2010 Juanjo Menent * @@ -22,7 +22,7 @@ * \file htdocs/core/class/html.formfile.class.php * \ingroup core * \brief File of class to offer components to list and upload files - * \version $Id: html.formfile.class.php,v 1.39 2011/07/05 22:40:36 eldy Exp $ + * \version $Id: html.formfile.class.php,v 1.40 2011/07/06 06:21:52 hregis Exp $ */ @@ -759,19 +759,46 @@ class FormFile var max_file_size = \''.$max_file_size.'\'; // Initialize the jQuery File Upload widget: - $("#fileupload").fileupload( { maxFileSize: max_file_size} ); + $("#fileupload").fileupload({ + maxFileSize: max_file_size, + done: function (e, data) { + $.ajax(data).success(function () { + location.href=\''.$_SERVER["PHP_SELF"].'?'.$_SERVER["QUERY_STRING"].'\'; + }); + }, + destroy: function (e, data) { + var that = $(this).data("fileupload"); + if ( confirm("Delete this file ?") == true ) { + if (data.url) { + $.ajax(data).success(function () { + that._adjustMaxNumberOfFiles(1); + $(this).fadeOut(function () { + $(this).remove(); + }); + }); + } else { + data.context.fadeOut(function () { + $(this).remove(); + }); + } + } + } + }); // Load existing files: - $.getJSON($("#fileupload form").prop("action"), { fk_element: "'.$object->id.'", element: "'.$object->element.'"}, function (files) { - var fu = $("#fileupload").data("fileupload"); - fu._adjustMaxNumberOfFiles(-files.length); - fu._renderDownload(files) - .appendTo($("#fileupload .files")) - .fadeIn(function () { - // Fix for IE7 and lower: - $(this).show(); - }); - }); + // TODO do not delete + if (1 == 2) { + $.getJSON($("#fileupload form").prop("action"), { fk_element: "'.$object->id.'", element: "'.$object->element.'"}, function (files) { + var fu = $("#fileupload").data("fileupload"); + fu._adjustMaxNumberOfFiles(-files.length); + fu._renderDownload(files) + .appendTo($("#fileupload .files")) + .fadeIn(function () { + // Fix for IE7 and lower: + $(this).show(); + }); + }); + } // Open download dialogs via iframes, // to prevent aborting current uploads: @@ -782,27 +809,6 @@ class FormFile .appendTo("body"); }); - // Confirm delete file - $("#fileupload").fileupload({ - destroy: function (e, data) { - var that = $(this).data("fileupload"); - if ( confirm("Delete this file ?") == true ) { - if (data.url) { - $.ajax(data) - .success(function () { - that._adjustMaxNumberOfFiles(1); - $(this).fadeOut(function () { - $(this).remove(); - }); - }); - } else { - data.context.fadeOut(function () { - $(this).remove(); - }); - } - } - } - }); }); '; From 7bf7b076487e558f5da6535949cbf24ff693412f Mon Sep 17 00:00:00 2001 From: Regis Houssin Date: Wed, 6 Jul 2011 08:09:28 +0000 Subject: [PATCH 07/66] Fix: uniformize code --- .../canvas/individual/tpl/card_create.tpl.php | 4 ++-- .../societe/canvas/individual/tpl/card_view.tpl.php | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/htdocs/societe/canvas/individual/tpl/card_create.tpl.php b/htdocs/societe/canvas/individual/tpl/card_create.tpl.php index 1e0c9f44562..6f3d07fae61 100644 --- a/htdocs/societe/canvas/individual/tpl/card_create.tpl.php +++ b/htdocs/societe/canvas/individual/tpl/card_create.tpl.php @@ -1,5 +1,5 @@ +/* Copyright (C) 2010-2011 Regis Houssin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,7 +15,7 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - * $Id$ + * $Id: card_create.tpl.php,v 1.9 2011/07/06 08:09:28 hregis Exp $ */ ?> diff --git a/htdocs/societe/canvas/individual/tpl/card_view.tpl.php b/htdocs/societe/canvas/individual/tpl/card_view.tpl.php index fb20dfd4038..86504ec6462 100644 --- a/htdocs/societe/canvas/individual/tpl/card_view.tpl.php +++ b/htdocs/societe/canvas/individual/tpl/card_view.tpl.php @@ -15,17 +15,17 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - * $Id$ + * $Id: card_view.tpl.php,v 1.11 2011/07/06 08:09:28 hregis Exp $ */ -$soc = $GLOBALS['objcanvas']->control->object; +$object = $GLOBALS['objcanvas']->control->object; ?> trans("ThirdParty"),0,'company'); @@ -223,13 +223,13 @@ $somethingshown=$formfile->show_documents('company',$socid,$filedir,$urlsource,$ \ No newline at end of file From 445e3062ac6e9a1b851118e092ee91a65669b410 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 6 Jul 2011 09:25:05 +0000 Subject: [PATCH 08/66] Enhancements in file upload --- htdocs/core/ajaxfileupload.php | 4 +- htdocs/core/class/html.formfile.class.php | 44 ++++++++++++------- htdocs/lib/files.lib.php | 16 +++---- htdocs/lib/functions.lib.php | 22 +++++----- htdocs/societe/document.php | 51 +---------------------- htdocs/theme/eldy/style.css.php | 32 +++++++++++++- 6 files changed, 84 insertions(+), 85 deletions(-) diff --git a/htdocs/core/ajaxfileupload.php b/htdocs/core/ajaxfileupload.php index 68199e8588d..2c964aa5a17 100644 --- a/htdocs/core/ajaxfileupload.php +++ b/htdocs/core/ajaxfileupload.php @@ -20,7 +20,7 @@ /** * \file htdocs/core/ajaxfileupload.php * \brief File to return Ajax response on file upload - * \version $Id: ajaxfileupload.php,v 1.8 2011/07/06 06:21:51 hregis Exp $ + * \version $Id: ajaxfileupload.php,v 1.9 2011/07/06 09:25:06 eldy Exp $ */ //if (! defined('NOREQUIREUSER')) define('NOREQUIREUSER','1'); @@ -209,7 +209,7 @@ class UploadHandler FILE_APPEND ); } else { - move_uploaded_file($uploaded_file, $file_path); + dol_move_uploaded_file($uploaded_file, $file_path, 1); } } else { // Non-multipart uploads (PUT method support) diff --git a/htdocs/core/class/html.formfile.class.php b/htdocs/core/class/html.formfile.class.php index 4aa64bed577..b3792c9b612 100644 --- a/htdocs/core/class/html.formfile.class.php +++ b/htdocs/core/class/html.formfile.class.php @@ -22,7 +22,7 @@ * \file htdocs/core/class/html.formfile.class.php * \ingroup core * \brief File of class to offer components to list and upload files - * \version $Id: html.formfile.class.php,v 1.40 2011/07/06 06:21:52 hregis Exp $ + * \version $Id: html.formfile.class.php,v 1.41 2011/07/06 09:25:06 eldy Exp $ */ @@ -548,9 +548,9 @@ class FormFile /** * Show list of documents in a directory - * @param filearray Array of files loaded by dol_dir_list function before calling this function + * @param filearray Array of files loaded by dol_dir_list('files') function before calling this * @param object Object on which document is linked to - * @param modulepart Value for modulepart used by download wrapper + * @param modulepart Value for modulepart used by download or viewimage wrapper * @param param Parameters on sort links * @param forcedownload Force to open dialog box "Save As" when clicking on file * @param relativepath Relative path of docs (autodefined if not provided) @@ -566,7 +566,7 @@ class FormFile global $bc; global $sortfield, $sortorder; - // Affiche liste des documents existant + // Show list of existing files if (empty($useinecm)) print_titre($langs->trans("AttachedFiles")); //else { $bc[true]=''; $bc[false]=''; }; $url=$_SERVER["PHP_SELF"]; @@ -575,14 +575,18 @@ class FormFile print_liste_field_titre($langs->trans("Documents2"),$_SERVER["PHP_SELF"],"name","",$param,'align="left"',$sortfield,$sortorder); print_liste_field_titre($langs->trans("Size"),$_SERVER["PHP_SELF"],"size","",$param,'align="right"',$sortfield,$sortorder); print_liste_field_titre($langs->trans("Date"),$_SERVER["PHP_SELF"],"date","",$param,'align="center"',$sortfield,$sortorder); + if (empty($useinecm)) print_liste_field_titre('',$_SERVER["PHP_SELF"],"","",$param,'align="center"'); print_liste_field_titre('','',''); print ''; + $nboffiles=sizeof($filearray); + + if ($nboffiles > 0) include_once(DOL_DOCUMENT_ROOT.'/lib/images.lib.php'); + $var=true; - foreach($filearray as $key => $file) + foreach($filearray as $key => $file) // filearray must be only files here { - if (!is_dir($dir.$file['name']) - && $file['name'] != '.' + if ($file['name'] != '.' && $file['name'] != '..' && $file['name'] != 'CVS' && ! preg_match('/\.meta$/i',$file['name'])) @@ -603,14 +607,25 @@ class FormFile print "\n"; print ''.dol_print_size($file['size'],1,1).''; print ''.dol_print_date($file['date'],"dayhour").''; + // Preview + if (empty($useinecm)) + { + print ''; + $pdirthumb='thumbs/'; + if (image_format_supported($file['name']) > 0) print ''; + else print ' '; + print ''; + } + // Delete or view link print ''; - if (! empty($useinecm)) print ''.img_view().'   '; + if (! empty($useinecm)) print ''.img_view().'   '; if ($permtodelete) print ''.img_delete().''; else print ' '; - print "\n"; + print ""; + print "\n"; } } - if (sizeof($filearray) == 0) + if ($nboffiles == 0) { print ''; if (empty($textifempty)) print $langs->trans("NoFileFound"); @@ -669,7 +684,7 @@ class FormFile $var=true; foreach($filearray as $key => $file) { - if (!is_dir($dir.$file['name']) + if (!is_dir($file['name']) && $file['name'] != '.' && $file['name'] != '..' && $file['name'] != 'CVS' @@ -828,15 +843,16 @@ class FormFile print '
'; print ''; - print ''; + /*print ''; print ''; print ''; print ''; print ''; - print ''; + print '';*/ print '
'.$langs->trans("Documents2").''.$langs->trans("Preview").''.$langs->trans("Size").'
'; - print '
'; + // We remove this because there is already individual bars. + //print '
'; print '
'; print ''; diff --git a/htdocs/lib/files.lib.php b/htdocs/lib/files.lib.php index 35ce31d0bc8..4e63a2221a5 100644 --- a/htdocs/lib/files.lib.php +++ b/htdocs/lib/files.lib.php @@ -20,7 +20,7 @@ /** * \file htdocs/lib/files.lib.php * \brief Library for file managing functions - * \version $Id: files.lib.php,v 1.64 2011/07/05 16:10:57 hregis Exp $ + * \version $Id: files.lib.php,v 1.65 2011/07/06 09:25:06 eldy Exp $ */ /** @@ -192,6 +192,7 @@ function dol_compare_file($a, $b) * @param mode 0=Return full mime, 1=otherwise short mime string, 2=image for mime type, 3=source language * @return string Return a mime type family * (text/xxx, application/xxx, image/xxx, audio, video, archive) + * @see image_format_supported (images.lib.php) */ function dol_mimetype($file,$default='application/octet-stream',$mode=0) { @@ -459,7 +460,7 @@ function dol_move($srcfile, $destfile, $newmask=0, $overwriteifexists=1) */ function dol_move_uploaded_file($src_file, $dest_file, $allowoverwrite, $disablevirusscan=0, $uploaderrorcode=0, $notrigger=0) { - global $conf, $user, $langs; + global $conf, $user, $langs, $db; global $object; $file_name = $dest_file; @@ -492,7 +493,7 @@ function dol_move_uploaded_file($src_file, $dest_file, $allowoverwrite, $disable } // If we need to make a virus scan - if (empty($disablevirusscan) && file_exists($src_file) && $conf->global->MAIN_ANTIVIRUS_COMMAND) + if (empty($disablevirusscan) && file_exists($src_file) && ! empty($conf->global->MAIN_ANTIVIRUS_COMMAND)) { require_once(DOL_DOCUMENT_ROOT.'/lib/security.lib.php'); require_once(DOL_DOCUMENT_ROOT.'/lib/antivir.class.php'); @@ -554,9 +555,10 @@ function dol_move_uploaded_file($src_file, $dest_file, $allowoverwrite, $disable { if (! empty($conf->global->MAIN_UMASK)) @chmod($file_name_osencoded, octdec($conf->global->MAIN_UMASK)); dol_syslog("Functions.lib::dol_move_uploaded_file Success to move ".$src_file." to ".$file_name." - Umask=".$conf->global->MAIN_UMASK, LOG_DEBUG); - + if (! $notrigger) { + if (! is_object($object)) $object=(object) 'dummy'; $object->src_file=$dest_file; // Appel des triggers @@ -566,7 +568,7 @@ function dol_move_uploaded_file($src_file, $dest_file, $allowoverwrite, $disable if ($result < 0) { $error++; $errors=$interface->errors; } // Fin appel triggers } - + return 1; // Success } else @@ -590,7 +592,7 @@ function dol_delete_file($file,$disableglob=0,$nophperrors=0,$notrigger=0) { global $conf, $user, $langs; global $object; - + //print "x".$file." ".$disableglob; $ok=true; $file_osencoded=dol_osencode($file); // New filename encoded in OS filesystem encoding charset @@ -606,7 +608,7 @@ function dol_delete_file($file,$disableglob=0,$nophperrors=0,$notrigger=0) if (! $notrigger) { $object->src_file=$file; - + // Appel des triggers include_once(DOL_DOCUMENT_ROOT . "/core/class/interfaces.class.php"); $interface=new Interfaces($db); diff --git a/htdocs/lib/functions.lib.php b/htdocs/lib/functions.lib.php index 5cda1373034..deee3fc1ed5 100644 --- a/htdocs/lib/functions.lib.php +++ b/htdocs/lib/functions.lib.php @@ -29,7 +29,7 @@ * \file htdocs/lib/functions.lib.php * \brief A set of functions for Dolibarr * This file contains all frequently used functions. - * \version $Id: functions.lib.php,v 1.538 2011/07/04 10:33:56 eldy Exp $ + * \version $Id: functions.lib.php,v 1.539 2011/07/06 09:25:05 eldy Exp $ */ // For compatibility during upgrade @@ -807,7 +807,7 @@ function dolibarr_mktime($hour,$minute,$second,$month,$day,$year,$gm=false,$chec * @param year Year * @param gm 1=Input informations are GMT values, otherwise local to server TZ * @param check 0=No check on parameters (Can use day 32, etc...) - * @param isdst Dayling saving time + * @param isdst Dayling saving time * @return timestamp Date as a timestamp, '' if error * @see dol_print_date, dol_stringtotime */ @@ -2538,16 +2538,16 @@ function dol_print_error_email() /** * Show title line of an array - * @param name libelle champ - * @param file url pour clic sur tri - * @param field champ de tri - * @param begin ("" par defaut) - * @param options ("" par defaut) - * @param td options de l'attribut td ("" par defaut) - * @param sortfield field currently used to sort - * @param sortorder ordre du tri + * @param name Label of field + * @param file Url used when we click on sort picto + * @param field Field to use for new sorting + * @param begin ("" by defaut) + * @param options ("" by defaut) + * @param td Options of attribute td ("" by defaut) + * @param sortfield Current field used to sort + * @param sortorder Current sort order */ -function print_liste_field_titre($name, $file, $field, $begin="", $options="", $td="", $sortfield="", $sortorder="") +function print_liste_field_titre($name, $file="", $field="", $begin="", $options="", $td="", $sortfield="", $sortorder="") { global $conf; //print "$name, $file, $field, $begin, $options, $td, $sortfield, $sortorder
\n"; diff --git a/htdocs/societe/document.php b/htdocs/societe/document.php index 9a6495b4c6e..5aab83e3302 100644 --- a/htdocs/societe/document.php +++ b/htdocs/societe/document.php @@ -23,7 +23,7 @@ * \file htdocs/societe/document.php * \brief Tab for documents linked to third party * \ingroup societe - * \version $Id: document.php,v 1.34 2011/07/05 22:40:35 eldy Exp $ + * \version $Id: document.php,v 1.35 2011/07/06 09:25:06 eldy Exp $ */ require("../main.inc.php"); @@ -227,53 +227,6 @@ if ($socid > 0) $formfile->list_of_documents($filearray,$object,'societe',$param); print "

"; - - // Courriers - // Les courriers sont des documents speciaux generes par des scripts - // situes dans scripts/courrier. - // Voir Rodo - if (! empty($conf->global->MAIN_MODULE_EDITEUR)) - { - $filearray=array(); - $errorlevel=error_reporting(); - error_reporting(0); - $handle=opendir($courrier_dir); - error_reporting($errorlevel); - if (is_resource($handle)) - { - $i=0; - while (($file = readdir($handle))!==false) - { - if (!is_dir($dir.$file) && substr($file, 0, 1) <> '.' && substr($file, 0, 3) <> 'CVS') - { - $filearray[$i]=$file; - $i++; - } - } - closedir($handle); - } - - print ''; - print ''; - - $var=true; - foreach($filearray as $key => $file) - { - if (!is_dir($dir.$file) && substr($file, 0, 1) <> '.' && substr($file, 0, 3) <> 'CVS') - { - $var=!$var; - print "\n"; - - print ''; - print ''; - print "\n"; - } - } - print "
'.$langs->trans("Courriers").''.$langs->trans("Size").''.$langs->trans("Date").'
"; - $loc = "courrier/".get_exdir($socid); - echo ''.$file.''; - print "'.dol_print_size(dol_filesize($courrier_dir."/".$file)).''.dol_print_date(dol_filemtime($courrier_dir."/".$file),'dayhour').'
"; - } } else { @@ -288,6 +241,6 @@ else $db->close(); -llxFooter('$Date: 2011/07/05 22:40:35 $ - $Revision: 1.34 $'); +llxFooter('$Date: 2011/07/06 09:25:06 $ - $Revision: 1.35 $'); ?> diff --git a/htdocs/theme/eldy/style.css.php b/htdocs/theme/eldy/style.css.php index ba9e6e5e520..bc390481a9c 100644 --- a/htdocs/theme/eldy/style.css.php +++ b/htdocs/theme/eldy/style.css.php @@ -21,7 +21,7 @@ /** * \file htdocs/theme/eldy/style.css.php * \brief Fichier de style CSS du theme Eldy - * \version $Id: style.css.php,v 1.80 2011/07/01 23:08:25 eldy Exp $ + * \version $Id: style.css.php,v 1.81 2011/07/06 09:25:06 eldy Exp $ */ //if (! defined('NOREQUIREUSER')) define('NOREQUIREUSER','1'); // Not disabled cause need to load personalized language @@ -1114,7 +1114,7 @@ padding-right: 2px; } tr.liste_titre { -height: 16px; +height: 18px; background: #7699A9; background-image: url(); background-repeat: repeat-x; @@ -1883,3 +1883,31 @@ A.none, A.none:active, A.none:visited, A.none:hover { } span.cke_skin_kama { padding: 0 !important; } .cke_wrapper { padding: 4px !important; } + + +/* ============================================================================== */ +/* CKEditor */ +/* ============================================================================== */ + +.fileupload-content +{ + padding: 0 !important; +} +.ui-corner-br +{ + border-bottom-right-radius: 0 !important; +} +.ui-corner-bl +{ + border-bottom-left-radius: 0 !important; +} +.files td { + height: 18px !important; + padding: 0; +} +.files { + margin: 0px; +} +.template-upload { + height: 72px !important; +} \ No newline at end of file From 200bdbb25df54a96f2f4ef3a8ab4917f3e33564d Mon Sep 17 00:00:00 2001 From: Juanjo Menent Date: Wed, 6 Jul 2011 09:39:04 +0000 Subject: [PATCH 09/66] Fix: No validate unlined interventions --- htdocs/fichinter/fiche.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/htdocs/fichinter/fiche.php b/htdocs/fichinter/fiche.php index 39c9a121147..cff0a1be34f 100644 --- a/htdocs/fichinter/fiche.php +++ b/htdocs/fichinter/fiche.php @@ -23,7 +23,7 @@ * \file htdocs/fichinter/fiche.php * \brief Fichier fiche intervention * \ingroup ficheinter - * \version $Id: fiche.php,v 1.165 2011/07/02 17:14:58 eldy Exp $ + * \version $Id: fiche.php,v 1.166 2011/07/06 09:39:04 simnandez Exp $ */ require("../main.inc.php"); @@ -1037,7 +1037,7 @@ elseif ($fichinterid) if ($action != 'editdescription') { // Validate - if ($object->statut == 0 && $user->rights->ficheinter->creer) + if ($object->statut == 0 && $user->rights->ficheinter->creer && sizeof($object->lines) > 0) { print ''.$langs->trans("Valid").''; @@ -1178,5 +1178,5 @@ elseif ($fichinterid) $db->close(); -llxFooter('$Date: 2011/07/02 17:14:58 $ - $Revision: 1.165 $'); +llxFooter('$Date: 2011/07/06 09:39:04 $ - $Revision: 1.166 $'); ?> From 47ca2de815467d9cd728ec87e16df71715ad14fa Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 6 Jul 2011 09:42:55 +0000 Subject: [PATCH 10/66] Enhancements in file upload --- htdocs/core/class/html.formfile.class.php | 11 ++++++----- htdocs/theme/auguria/style.css.php | 11 ++++++++++- htdocs/theme/bureau2crea/style.css.php | 18 ++++++++++++++---- htdocs/theme/cameleo/style.css.php | 19 ++++++++----------- htdocs/theme/eldy/style.css.php | 23 ++--------------------- htdocs/theme/freelug/style.css.php | 11 ++++++++++- htdocs/theme/yellow/style.css.php | 11 ++++++++++- 7 files changed, 60 insertions(+), 44 deletions(-) diff --git a/htdocs/core/class/html.formfile.class.php b/htdocs/core/class/html.formfile.class.php index b3792c9b612..3559969f429 100644 --- a/htdocs/core/class/html.formfile.class.php +++ b/htdocs/core/class/html.formfile.class.php @@ -22,7 +22,7 @@ * \file htdocs/core/class/html.formfile.class.php * \ingroup core * \brief File of class to offer components to list and upload files - * \version $Id: html.formfile.class.php,v 1.41 2011/07/06 09:25:06 eldy Exp $ + * \version $Id: html.formfile.class.php,v 1.42 2011/07/06 09:42:55 eldy Exp $ */ @@ -611,8 +611,9 @@ class FormFile if (empty($useinecm)) { print ''; - $pdirthumb='thumbs/'; - if (image_format_supported($file['name']) > 0) print ''; + $tmp=explode('.',$file['name']); + $minifile=$tmp[0].'_mini.'.$tmp[1]; + if (image_format_supported($file['name']) > 0) print ''; else print ' '; print ''; } @@ -774,12 +775,12 @@ class FormFile var max_file_size = \''.$max_file_size.'\'; // Initialize the jQuery File Upload widget: - $("#fileupload").fileupload({ + $("#fileupload").fileupload({ maxFileSize: max_file_size, done: function (e, data) { $.ajax(data).success(function () { location.href=\''.$_SERVER["PHP_SELF"].'?'.$_SERVER["QUERY_STRING"].'\'; - }); + }); }, destroy: function (e, data) { var that = $(this).data("fileupload"); diff --git a/htdocs/theme/auguria/style.css.php b/htdocs/theme/auguria/style.css.php index 3b1ce8cdc3d..debc0f1cef0 100644 --- a/htdocs/theme/auguria/style.css.php +++ b/htdocs/theme/auguria/style.css.php @@ -21,7 +21,7 @@ /** * \file htdocs/theme/auguria/style.css.php * \brief Fichier de style CSS du theme auguria - * \version $Id$ + * \version $Id: style.css.php,v 1.86 2011/07/06 09:42:55 eldy Exp $ */ //if (! defined('NOREQUIREUSER')) define('NOREQUIREUSER','1'); // Not disabled cause need to load personalized language @@ -1890,6 +1890,15 @@ span.cke_skin_kama { padding: 0 ! important; } +/* ============================================================================== */ +/* File upload */ +/* ============================================================================== */ + +.template-upload { + height: 72px !important; +} + + /* ============================================================================== */ /* Test using div instead of tables */ /* ============================================================================== */ diff --git a/htdocs/theme/bureau2crea/style.css.php b/htdocs/theme/bureau2crea/style.css.php index b80af676123..488a3ab72fc 100644 --- a/htdocs/theme/bureau2crea/style.css.php +++ b/htdocs/theme/bureau2crea/style.css.php @@ -23,7 +23,7 @@ /** * \file htdocs/theme/bureau2crea/style.css.php * \brief Fichier de style CSS du theme bureau2crea - * \version $Id: style.css.php,v 1.36 2011/07/01 14:01:46 simnandez Exp $ + * \version $Id: style.css.php,v 1.37 2011/07/06 09:42:55 eldy Exp $ */ //if (! defined('NOREQUIREUSER')) define('NOREQUIREUSER','1'); // Not disabled cause need to load personalized language @@ -468,7 +468,7 @@ li.tmenu a { li.tmenu a:hover { color: #a1ad23; } - + li.tmenu .tmenusel { color: #FFF; padding: 0px 10px 0px 10px; @@ -480,7 +480,7 @@ li.tmenu .tmenusel { border-bottom: 0px solid #555555; border-left: 1px solid #D0D0D0; border-top: 1px solid #D8D8D8; - background: #606060; + background: #606060; } @@ -512,7 +512,7 @@ li.tmenusel a:hover { li.tmenu a.tmenudisabled { color: #CCC; } - + /* --- end nav --- */ @@ -2046,3 +2046,13 @@ div.leftContent { } span.cke_skin_kama { padding: 0 !important; } .cke_wrapper { padding: 4px !important; } + + + +/* ============================================================================== */ +/* File upload */ +/* ============================================================================== */ + +.template-upload { + height: 72px !important; +} diff --git a/htdocs/theme/cameleo/style.css.php b/htdocs/theme/cameleo/style.css.php index 1b7122e80a9..5782c5a3d62 100644 --- a/htdocs/theme/cameleo/style.css.php +++ b/htdocs/theme/cameleo/style.css.php @@ -23,7 +23,7 @@ /** * \file htdocs/theme/eldy/style.css.php * \brief Fichier de style CSS du theme Cameleo - * \version $Id$ + * \version $Id: style.css.php,v 1.5 2011/07/06 09:42:55 eldy Exp $ */ //if (! defined('NOREQUIREUSER')) define('NOREQUIREUSER','1'); // Not disabled cause need to load personalized language @@ -2068,14 +2068,11 @@ A.none, A.none:active, A.none:visited, A.none:hover { span.cke_skin_kama { padding: 0 !important; } .cke_wrapper { padding: 4px !important; } -#label -{ - text-align: right; - color: #666; -} -#value -{ - font-weight: bold; - padding: 0 0px 0 10px; -} +/* ============================================================================== */ +/* File upload */ +/* ============================================================================== */ + +.template-upload { + height: 72px !important; +} \ No newline at end of file diff --git a/htdocs/theme/eldy/style.css.php b/htdocs/theme/eldy/style.css.php index bc390481a9c..ac7eec87a29 100644 --- a/htdocs/theme/eldy/style.css.php +++ b/htdocs/theme/eldy/style.css.php @@ -21,7 +21,7 @@ /** * \file htdocs/theme/eldy/style.css.php * \brief Fichier de style CSS du theme Eldy - * \version $Id: style.css.php,v 1.81 2011/07/06 09:25:06 eldy Exp $ + * \version $Id: style.css.php,v 1.82 2011/07/06 09:42:55 eldy Exp $ */ //if (! defined('NOREQUIREUSER')) define('NOREQUIREUSER','1'); // Not disabled cause need to load personalized language @@ -1886,28 +1886,9 @@ span.cke_skin_kama { padding: 0 !important; } /* ============================================================================== */ -/* CKEditor */ +/* File upload */ /* ============================================================================== */ -.fileupload-content -{ - padding: 0 !important; -} -.ui-corner-br -{ - border-bottom-right-radius: 0 !important; -} -.ui-corner-bl -{ - border-bottom-left-radius: 0 !important; -} -.files td { - height: 18px !important; - padding: 0; -} -.files { - margin: 0px; -} .template-upload { height: 72px !important; } \ No newline at end of file diff --git a/htdocs/theme/freelug/style.css.php b/htdocs/theme/freelug/style.css.php index e0d4c713de8..0e5cb135e87 100644 --- a/htdocs/theme/freelug/style.css.php +++ b/htdocs/theme/freelug/style.css.php @@ -20,7 +20,7 @@ /** * \file htdocs/theme/freelug/style.css.php * \brief Fichier de style CSS du theme Freelug - * \version $Id$ + * \version $Id: style.css.php,v 1.40 2011/07/06 09:42:55 eldy Exp $ */ //if (! defined('NOREQUIREUSER')) define('NOREQUIREUSER','1'); // Not disabled cause need to load personalized language @@ -1469,3 +1469,12 @@ A.none, A.none:active, A.none:visited, A.none:hover { } span.cke_skin_kama { padding: 0 ! important; } + + +/* ============================================================================== */ +/* File upload */ +/* ============================================================================== */ + +.template-upload { + height: 72px !important; +} diff --git a/htdocs/theme/yellow/style.css.php b/htdocs/theme/yellow/style.css.php index 368f80bbd1a..b72b9f67261 100644 --- a/htdocs/theme/yellow/style.css.php +++ b/htdocs/theme/yellow/style.css.php @@ -20,7 +20,7 @@ /** * \file htdocs/theme/yellow/style.css.php * \brief Fichier de style CSS du theme Yellow - * \version $Id$ + * \version $Id: style.css.php,v 1.41 2011/07/06 09:42:55 eldy Exp $ */ //if (! defined('NOREQUIREUSER')) define('NOREQUIREUSER','1'); // Not disabled cause need to load personalized language @@ -1518,3 +1518,12 @@ A.none, A.none:active, A.none:visited, A.none:hover { } span.cke_skin_kama { padding: 0 ! important; } + + +/* ============================================================================== */ +/* File upload */ +/* ============================================================================== */ + +.template-upload { + height: 72px !important; +} From 2069adda43573ded281bff7a2d1f2c3c9d0a0890 Mon Sep 17 00:00:00 2001 From: Juanjo Menent Date: Wed, 6 Jul 2011 09:57:12 +0000 Subject: [PATCH 11/66] Fix: Reduce default length of input file area --- htdocs/core/class/html.formfile.class.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/htdocs/core/class/html.formfile.class.php b/htdocs/core/class/html.formfile.class.php index 3559969f429..e8a5c04db97 100644 --- a/htdocs/core/class/html.formfile.class.php +++ b/htdocs/core/class/html.formfile.class.php @@ -22,7 +22,7 @@ * \file htdocs/core/class/html.formfile.class.php * \ingroup core * \brief File of class to offer components to list and upload files - * \version $Id: html.formfile.class.php,v 1.42 2011/07/06 09:42:55 eldy Exp $ + * \version $Id: html.formfile.class.php,v 1.43 2011/07/06 09:57:12 simnandez Exp $ */ @@ -62,7 +62,7 @@ class FormFile * @param size Length of input file area * @return int <0 ij KO, >0 if OK */ - function form_attach_new_file($url, $title='', $addcancel=0, $sectionid=0, $perm=1, $size=60) + function form_attach_new_file($url, $title='', $addcancel=0, $sectionid=0, $perm=1, $size=50) { global $conf,$langs; From 4d0197115d0824a70037def995a9245ae691e677 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 6 Jul 2011 10:22:56 +0000 Subject: [PATCH 12/66] Look: Minor enhancement --- htdocs/theme/eldy/style.css.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/htdocs/theme/eldy/style.css.php b/htdocs/theme/eldy/style.css.php index ac7eec87a29..3a3a049f95f 100644 --- a/htdocs/theme/eldy/style.css.php +++ b/htdocs/theme/eldy/style.css.php @@ -21,7 +21,7 @@ /** * \file htdocs/theme/eldy/style.css.php * \brief Fichier de style CSS du theme Eldy - * \version $Id: style.css.php,v 1.82 2011/07/06 09:42:55 eldy Exp $ + * \version $Id: style.css.php,v 1.83 2011/07/06 10:22:56 eldy Exp $ */ //if (! defined('NOREQUIREUSER')) define('NOREQUIREUSER','1'); // Not disabled cause need to load personalized language @@ -653,7 +653,7 @@ td.photo { /* ============================================================================== */ .toolbar { - background-image: url(theme.'/img/tmenu2.png' ?>) !important; + background-image: url(theme.'/img/tmenu2.jpg' ?>) !important; background-repeat: repeat-x !important; } @@ -1071,7 +1071,7 @@ border-right-style: solid; border-left-width: 1px; border-left-color: #BBBBBB; border-left-style: solid; -height: 16px; +height: 18px; } table.noborder td { @@ -1114,7 +1114,7 @@ padding-right: 2px; } tr.liste_titre { -height: 18px; +height: 20px !important; background: #7699A9; background-image: url(); background-repeat: repeat-x; @@ -1212,6 +1212,7 @@ padding-bottom: 4px; } tr.box_titre { +height: 20px; background: #7699A9; background-image: url(); background-repeat: repeat-x; @@ -1568,7 +1569,7 @@ table.cal_event td { border: 0px; padding-: 0px; padding- Date: Wed, 6 Jul 2011 10:34:30 +0000 Subject: [PATCH 13/66] Fixme: problem with trigger --- htdocs/core/ajaxfileupload.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/htdocs/core/ajaxfileupload.php b/htdocs/core/ajaxfileupload.php index 2c964aa5a17..70babaa6ca3 100644 --- a/htdocs/core/ajaxfileupload.php +++ b/htdocs/core/ajaxfileupload.php @@ -20,7 +20,7 @@ /** * \file htdocs/core/ajaxfileupload.php * \brief File to return Ajax response on file upload - * \version $Id: ajaxfileupload.php,v 1.9 2011/07/06 09:25:06 eldy Exp $ + * \version $Id: ajaxfileupload.php,v 1.10 2011/07/06 10:34:30 hregis Exp $ */ //if (! defined('NOREQUIREUSER')) define('NOREQUIREUSER','1'); @@ -209,7 +209,8 @@ class UploadHandler FILE_APPEND ); } else { - dol_move_uploaded_file($uploaded_file, $file_path, 1); + // FIXME problem with trigger + dol_move_uploaded_file($uploaded_file, $file_path, 1, 0, 0, 1); } } else { // Non-multipart uploads (PUT method support) From baae17ab55d267199a28e159f3acd3ce634b6790 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 6 Jul 2011 11:40:21 +0000 Subject: [PATCH 14/66] Fix: Some fix in layouts --- htdocs/ecm/index.php | 41 +- .../plugins/layout/jquery.layout-latest.js | 4353 +++++++++++++++++ htdocs/theme/auguria/style.css.php | 6 +- htdocs/theme/bureau2crea/style.css.php | 12 +- htdocs/theme/cameleo/style.css.php | 7 +- htdocs/theme/eldy/style.css.php | 7 +- htdocs/theme/freelug/style.css.php | 7 +- htdocs/theme/yellow/style.css.php | 7 +- 8 files changed, 4398 insertions(+), 42 deletions(-) create mode 100644 htdocs/includes/jquery/plugins/layout/jquery.layout-latest.js diff --git a/htdocs/ecm/index.php b/htdocs/ecm/index.php index 13f1e6a5af5..663aaf6a2d1 100644 --- a/htdocs/ecm/index.php +++ b/htdocs/ecm/index.php @@ -21,7 +21,7 @@ * \file htdocs/ecm/index.php * \ingroup ecm * \brief Main page for ECM section area - * \version $Id$ + * \version $Id: index.php,v 1.99 2011/07/06 11:40:21 eldy Exp $ * \author Laurent Destailleur */ @@ -298,27 +298,19 @@ if (GETPOST("action") == 'refreshmanual') //print "xx".$_SESSION["dol_screenheight"]; $maxheightwin=(isset($_SESSION["dol_screenheight"]) && $_SESSION["dol_screenheight"] > 500)?($_SESSION["dol_screenheight"]-166):660; -$morehead=" -"; +"; + +llxHeader($morecss.$morejs,$langs->trans("ECM"),'','','','','','',0,0); -llxHeader($morehead,$langs->trans("ECM"),'','','','','','',0,0); // Ajout rubriques automatiques $rowspan=0; @@ -382,7 +375,7 @@ if (GETPOST('action') == 'delete') if ($mesg) { print $mesg."
"; } // Toolbar -$head = ecm_prepare_head_fm($fac); +//$head = ecm_prepare_head_fm($fac); //dol_fiche_head($head, 'file_manager', '', 1); @@ -393,7 +386,7 @@ if ($conf->use_javascript_ajax) { ?>
-
+
use_javascript_ajax) ?>
-
+ -
+ - - - -
@@ -898,5 +887,5 @@ else // End of page $db->close(); -llxFooter('$Date$ - $Revision$'); +llxFooter('$Date: 2011/07/06 11:40:21 $ - $Revision: 1.99 $'); ?> diff --git a/htdocs/includes/jquery/plugins/layout/jquery.layout-latest.js b/htdocs/includes/jquery/plugins/layout/jquery.layout-latest.js new file mode 100644 index 00000000000..c9340418f98 --- /dev/null +++ b/htdocs/includes/jquery/plugins/layout/jquery.layout-latest.js @@ -0,0 +1,4353 @@ +/** + * @preserve jquery.layout 1.3.0 - Release Candidate 29.15 + * $Date: 2011/07/06 11:40:21 $ + * $Rev: 302915 $ + * + * Copyright (c) 2010 + * Fabrizio Balliano (http://www.fabrizioballiano.net) + * Kevin Dalman (http://allpro.net) + * + * Dual licensed under the GPL (http://www.gnu.org/licenses/gpl.html) + * and MIT (http://www.opensource.org/licenses/mit-license.php) licenses. + * + * Changelog: http://layout.jquery-dev.net/changelog.cfm#1.3.0.rc29.15 + * + * Docs: http://layout.jquery-dev.net/documentation.html + * Tips: http://layout.jquery-dev.net/tips.html + * Help: http://groups.google.com/group/jquery-ui-layout + */ + +// NOTE: For best readability, view with a fixed-width font and tabs equal to 4-chars + +;(function ($) { + +/* + * GENERIC $.layout METHODS - used by all layouts + */ +$.layout = { + + version: "1.3.rc29.15" +, revision: 0.032915 // 1.3.0 final = 1.0300 - major(n+).minor(nn)+patch(nn+) + + // LANGUAGE CUSTOMIZATION +, language: { + // Tips and messages for resizers, togglers, custom buttons, etc. + Open: "Open" // eg: "Open Pane" + , Close: "Close" + , Resize: "Resize" + , Slide: "Slide Open" + , Pin: "Pin" + , Unpin: "Un-Pin" + , noRoomToOpenTip: "Not enough room to show this pane." + // Developer error messages + , pane: "pane" // description of "layout pane element" + , selector: "selector" // description of "jQuery-selector" + , errButton: "Error Adding Button \n\nInvalid " + , errContainerMissing: "UI Layout Initialization Error\n\nThe specified layout-container does not exist." + , errCenterPaneMissing: "UI Layout Initialization Error\n\nThe center-pane element does not exist.\n\nThe center-pane is a required element." + , errContainerHeight: "UI Layout Initialization Warning\n\nThe layout-container \"CONTAINER\" has no height.\n\nTherefore the layout is 0-height and hence 'invisible'!" + } + + // can update code here if $.browser is phased out +, browser: { + mozilla: !!$.browser.mozilla + , webkit: !!$.browser.webkit || !!$.browser.safari // webkit = jQ 1.4 + , msie: !!$.browser.msie + , isIE6: !!$.browser.msie && $.browser.version == 6 + , boxModel: false // page must load first, so will be updated set by _create + //, version: $.browser.version - not used + } + + /* + * GENERIC UTILITY METHODS + */ + + // calculate and return the scrollbar width, as an integer +, scrollbarWidth: function () { return window.scrollbarWidth || $.layout.getScrollbarSize('width'); } +, scrollbarHeight: function () { return window.scrollbarHeight || $.layout.getScrollbarSize('height'); } +, getScrollbarSize: function (dim) { + var $c = $('
').appendTo("body"); + var d = { width: $c.width() - $c[0].clientWidth, height: $c.height() - $c[0].clientHeight }; + $c.remove(); + window.scrollbarWidth = d.width; + window.scrollbarHeight = d.height; + return dim.match(/^(width|height)$/i) ? d[dim] : d; + } + + + /** + * Returns hash container 'display' and 'visibility' + * + * @see $.swap() - swaps CSS, runs callback, resets CSS + */ +, showInvisibly: function ($E, force) { + if (!$E) return {}; + if (!$E.jquery) $E = $($E); + var CSS = { + display: $E.css('display') + , visibility: $E.css('visibility') + }; + if (force || CSS.display == "none") { // only if not *already hidden* + $E.css({ display: "block", visibility: "hidden" }); // show element 'invisibly' so can be measured + return CSS; + } + else return {}; + } + + /** + * Returns data for setting size of an element (container or a pane). + * + * @see _create(), onWindowResize() for container, plus others for pane + * @return JSON Returns a hash of all dimensions: top, bottom, left, right, outerWidth, innerHeight, etc + */ +, getElementDimensions: function ($E) { + var + d = {} // dimensions hash + , x = d.css = {} // CSS hash + , i = {} // TEMP insets + , b, p // TEMP border, padding + , N = $.layout.cssNum + , off = $E.offset() + ; + d.offsetLeft = off.left; + d.offsetTop = off.top; + + $.each("Left,Right,Top,Bottom".split(","), function (idx, e) { // e = edge + b = x["border" + e] = $.layout.borderWidth($E, e); + p = x["padding"+ e] = $.layout.cssNum($E, "padding"+e); + i[e] = b + p; // total offset of content from outer side + d["inset"+ e] = p; + }); + + d.offsetWidth = $E.innerWidth(); + d.offsetHeight = $E.innerHeight(); + d.outerWidth = $E.outerWidth(); + d.outerHeight = $E.outerHeight(); + d.innerWidth = Math.max(0, d.outerWidth - i.Left - i.Right); + d.innerHeight = Math.max(0, d.outerHeight - i.Top - i.Bottom); + + x.width = $E.width(); + x.height = $E.height(); + x.top = N($E,"top",true); + x.bottom = N($E,"bottom",true); + x.left = N($E,"left",true); + x.right = N($E,"right",true); + + //d.visible = $E.is(":visible");// && x.width > 0 && x.height > 0; + + return d; + } + +, getElementCSS: function ($E, list) { + var + CSS = {} + , style = $E[0].style + , props = list.split(",") + , sides = "Top,Bottom,Left,Right".split(",") + , attrs = "Color,Style,Width".split(",") + , p, s, a, i, j, k + ; + for (i=0; i < props.length; i++) { + p = props[i]; + if (p.match(/(border|padding|margin)$/)) + for (j=0; j < 4; j++) { + s = sides[j]; + if (p == "border") + for (k=0; k < 3; k++) { + a = attrs[k]; + CSS[p+s+a] = style[p+s+a]; + } + else + CSS[p+s] = style[p+s]; + } + else + CSS[p] = style[p]; + }; + return CSS + } + + /** + * Contains logic to check boxModel & browser, and return the correct width/height for the current browser/doctype + * + * @see initPanes(), sizeMidPanes(), initHandles(), sizeHandles() + * @param {Array.} $E Must pass a jQuery object - first element is processed + * @param {number=} outerWidth/outerHeight (optional) Can pass a width, allowing calculations BEFORE element is resized + * @return {number} Returns the innerWidth/Height of the elem by subtracting padding and borders + */ +, cssWidth: function ($E, outerWidth) { + var + b = $.layout.borderWidth + , n = $.layout.cssNum + ; + // a 'calculated' outerHeight can be passed so borders and/or padding are removed if needed + if (outerWidth <= 0) return 0; + + if (!$.layout.browser.boxModel) return outerWidth; + + // strip border and padding from outerWidth to get CSS Width + var W = outerWidth + - b($E, "Left") + - b($E, "Right") + - n($E, "paddingLeft") + - n($E, "paddingRight") + ; + + return Math.max(0,W); + } + +, cssHeight: function ($E, outerHeight) { + var + b = $.layout.borderWidth + , n = $.layout.cssNum + ; + // a 'calculated' outerHeight can be passed so borders and/or padding are removed if needed + if (outerHeight <= 0) return 0; + + if (!$.layout.browser.boxModel) return outerHeight; + + // strip border and padding from outerHeight to get CSS Height + var H = outerHeight + - b($E, "Top") + - b($E, "Bottom") + - n($E, "paddingTop") + - n($E, "paddingBottom") + ; + + return Math.max(0,H); + } + + /** + * Returns the 'current CSS numeric value' for a CSS property - 0 if property does not exist + * + * @see Called by many methods + * @param {Array.} $E Must pass a jQuery object - first element is processed + * @param {string} prop The name of the CSS property, eg: top, width, etc. + * @param {boolean=} allowAuto true = return 'auto' if that is value; false = return 0 + * @return {(string|number)} Usually used to get an integer value for position (top, left) or size (height, width) + */ +, cssNum: function ($E, prop, allowAuto) { + if (!$E.jquery) $E = $($E); + var CSS = $.layout.showInvisibly($E) + , p = $.curCSS($E[0], prop, true) + , v = allowAuto && p=="auto" ? p : (parseInt(p, 10) || 0); + $E.css( CSS ); // RESET + return v; + } + +, borderWidth: function (el, side) { + if (el.jquery) el = el[0]; + var b = "border"+ side.substr(0,1).toUpperCase() + side.substr(1); // left => Left + return $.curCSS(el, b+"Style", true) == "none" ? 0 : (parseInt($.curCSS(el, b+"Width", true), 10) || 0); + } + + + /** + * UTLITY for mouse tracking - FUTURE REFERENCE + * + * init: if (!window.mouse) { + * window.mouse = { x: 0, y: 0 }; + * $(document).mousemove( $.layout.trackMouse ); + * } + * + * @param {Object} evt + * +, trackMouse: function (evt) { + window.mouse = { x: evt.clientX, y: evt.clientY }; + } + */ + + /** + * SUBROUTINE for preventPrematureSlideClose option + * + * @param {Object} evt + * @param {Object=} el + */ +, isMouseOverElem: function (evt, el) { + var + $E = $(el || this) + , d = $E.offset() + , T = d.top + , L = d.left + , R = L + $E.outerWidth() + , B = T + $E.outerHeight() + , x = evt.pageX // evt.clientX ? + , y = evt.pageY // evt.clientY ? + ; + // if X & Y are < 0, probably means is over an open SELECT + return ($.layout.browser.msie && x < 0 && y < 0) || ((x >= L && x <= R) && (y >= T && y <= B)); + } + +}; + +$.fn.layout = function (opts) { + +/* + * ########################### + * WIDGET CONFIG & OPTIONS + * ########################### + */ + var + + // LANGUAGE - for tips & messages + lang = $.layout.language // internal alias + + // DEFAULT OPTIONS - CHANGE IF DESIRED +, options = { + name: "" // Not required, but useful for buttons and used for the state-cookie + , containerClass: "ui-layout-container" // layout-container element + , scrollToBookmarkOnLoad: true // after creating a layout, scroll to bookmark in URL (.../page.htm#myBookmark) + , resizeWithWindow: true // bind thisLayout.resizeAll() to the window.resize event + , resizeWithWindowDelay: 200 // delay calling resizeAll because makes window resizing very jerky + , resizeWithWindowMaxDelay: 0 // 0 = none - force resize every XX ms while window is being resized + , onresizeall_start: null // CALLBACK when resizeAll() STARTS - NOT pane-specific + , onresizeall_end: null // CALLBACK when resizeAll() ENDS - NOT pane-specific + , onload_start: null // CALLBACK when Layout inits - after options initialized, but before elements + , onload_end: null // CALLBACK when Layout inits - after EVERYTHING has been initialized + , onunload_start: null // CALLBACK when Layout is destroyed OR onWindowUnload + , onunload_end: null // CALLBACK when Layout is destroyed OR onWindowUnload + , autoBindCustomButtons: false // search for buttons with ui-layout-button class and auto-bind them + , zIndex: null // the PANE zIndex - resizers and masks will be +1 + , initPanes: true // false = DO NOT initialize the panes onLoad - will init later + , showErrorMessages: true // enables fatal error messages to warn developers of common errors + // PANE SETTINGS + , defaults: { // default options for 'all panes' - will be overridden by 'per-pane settings' + applyDemoStyles: false // NOTE: renamed from applyDefaultStyles for clarity + , closable: true // pane can open & close + , resizable: true // when open, pane can be resized + , slidable: true // when closed, pane can 'slide open' over other panes - closes on mouse-out + , initClosed: false // true = init pane as 'closed' + , initHidden: false // true = init pane as 'hidden' - no resizer-bar/spacing + // SELECTORS + //, paneSelector: "" // MUST be pane-specific - jQuery selector for pane + , contentSelector: ".ui-layout-content" // INNER div/element to auto-size so only it scrolls, not the entire pane! + , contentIgnoreSelector: ".ui-layout-ignore" // element(s) to 'ignore' when measuring 'content' + , findNestedContent: false // true = $P.find(contentSelector), false = $P.children(contentSelector) + // GENERIC ROOT-CLASSES - for auto-generated classNames + , paneClass: "ui-layout-pane" // border-Pane - default: 'ui-layout-pane' + , resizerClass: "ui-layout-resizer" // Resizer Bar - default: 'ui-layout-resizer' + , togglerClass: "ui-layout-toggler" // Toggler Button - default: 'ui-layout-toggler' + , buttonClass: "ui-layout-button" // CUSTOM Buttons - default: 'ui-layout-button-toggle/-open/-close/-pin' + // ELEMENT SIZE & SPACING + //, size: 100 // MUST be pane-specific -initial size of pane + , minSize: 0 // when manually resizing a pane + , maxSize: 0 // ditto, 0 = no limit + , spacing_open: 6 // space between pane and adjacent panes - when pane is 'open' + , spacing_closed: 6 // ditto - when pane is 'closed' + , togglerLength_open: 50 // Length = WIDTH of toggler button on north/south sides - HEIGHT on east/west sides + , togglerLength_closed: 50 // 100% OR -1 means 'full height/width of resizer bar' - 0 means 'hidden' + , togglerAlign_open: "center" // top/left, bottom/right, center, OR... + , togglerAlign_closed: "center" // 1 => nn = offset from top/left, -1 => -nn == offset from bottom/right + , togglerTip_open: lang.Close // Toggler tool-tip (title) + , togglerTip_closed: lang.Open // ditto + , togglerContent_open: "" // text or HTML to put INSIDE the toggler + , togglerContent_closed: "" // ditto + // RESIZING OPTIONS + , resizerDblClickToggle: true // + , autoResize: true // IF size is 'auto' or a percentage, then recalc 'pixel size' whenever the layout resizes + , autoReopen: true // IF a pane was auto-closed due to noRoom, reopen it when there is room? False = leave it closed + , resizerDragOpacity: 1 // option for ui.draggable + //, resizerCursor: "" // MUST be pane-specific - cursor when over resizer-bar + , maskIframesOnResize: true // true = all iframes OR = iframe-selector(s) - adds masking-div during resizing/dragging + , resizeNestedLayout: true // true = trigger nested.resizeAll() when a 'pane' of this layout is the 'container' for another + , resizeWhileDragging: false // true = LIVE Resizing as resizer is dragged + , resizeContentWhileDragging: false // true = re-measure header/footer heights as resizer is dragged + // TIPS & MESSAGES - also see lang object + , noRoomToOpenTip: lang.noRoomToOpenTip + , resizerTip: lang.Resize // Resizer tool-tip (title) + , sliderTip: lang.Slide // resizer-bar triggers 'sliding' when pane is closed + , sliderCursor: "pointer" // cursor when resizer-bar will trigger 'sliding' + , slideTrigger_open: "click" // click, dblclick, mouseenter + , slideTrigger_close: "mouseleave"// click, mouseleave + , slideDelay_open: 300 // applies only for mouseenter event - 0 = instant open + , slideDelay_close: 300 // applies only for mouseleave event (300ms is the minimum!) + , hideTogglerOnSlide: false // when pane is slid-open, should the toggler show? + , preventQuickSlideClose: $.layout.browser.webkit // Chrome triggers slideClosed as it is opening + , preventPrematureSlideClose: false + // HOT-KEYS & MISC + , showOverflowOnHover: false // will bind allowOverflow() utility to pane.onMouseOver + , enableCursorHotkey: true // enabled 'cursor' hotkeys + //, customHotkey: "" // MUST be pane-specific - EITHER a charCode OR a character + , customHotkeyModifier: "SHIFT" // either 'SHIFT', 'CTRL' or 'CTRL+SHIFT' - NOT 'ALT' + // PANE ANIMATION + // NOTE: fxSss_open & fxSss_close options (eg: fxName_open) are auto-generated if not passed + , fxName: "slide" // ('none' or blank), slide, drop, scale + , fxSpeed: null // slow, normal, fast, 200, nnn - if passed, will OVERRIDE fxSettings.duration + , fxSettings: {} // can be passed, eg: { easing: "easeOutBounce", duration: 1500 } + , fxOpacityFix: true // tries to fix opacity in IE to restore anti-aliasing after animation + // CALLBACKS + , triggerEventsOnLoad: false // true = trigger onopen OR onclose callbacks when layout initializes + , triggerEventsWhileDragging: true // true = trigger onresize callback REPEATEDLY if resizeWhileDragging==true + , onshow_start: null // CALLBACK when pane STARTS to Show - BEFORE onopen/onhide_start + , onshow_end: null // CALLBACK when pane ENDS being Shown - AFTER onopen/onhide_end + , onhide_start: null // CALLBACK when pane STARTS to Close - BEFORE onclose_start + , onhide_end: null // CALLBACK when pane ENDS being Closed - AFTER onclose_end + , onopen_start: null // CALLBACK when pane STARTS to Open + , onopen_end: null // CALLBACK when pane ENDS being Opened + , onclose_start: null // CALLBACK when pane STARTS to Close + , onclose_end: null // CALLBACK when pane ENDS being Closed + , onresize_start: null // CALLBACK when pane STARTS being Resized ***FOR ANY REASON*** + , onresize_end: null // CALLBACK when pane ENDS being Resized ***FOR ANY REASON*** + , onsizecontent_start: null // CALLBACK when sizing of content-element STARTS + , onsizecontent_end: null // CALLBACK when sizing of content-element ENDS + , onswap_start: null // CALLBACK when pane STARTS to Swap + , onswap_end: null // CALLBACK when pane ENDS being Swapped + , ondrag_start: null // CALLBACK when pane STARTS being ***MANUALLY*** Resized + , ondrag_end: null // CALLBACK when pane ENDS being ***MANUALLY*** Resized + } + , north: { + paneSelector: ".ui-layout-north" + , size: "auto" // eg: "auto", "30%", 200 + , resizerCursor: "n-resize" // custom = url(myCursor.cur) + , customHotkey: "" // EITHER a charCode OR a character + } + , south: { + paneSelector: ".ui-layout-south" + , size: "auto" + , resizerCursor: "s-resize" + , customHotkey: "" + } + , east: { + paneSelector: ".ui-layout-east" + , size: 200 + , resizerCursor: "e-resize" + , customHotkey: "" + } + , west: { + paneSelector: ".ui-layout-west" + , size: 200 + , resizerCursor: "w-resize" + , customHotkey: "" + } + , center: { + paneSelector: ".ui-layout-center" + , minWidth: 0 + , minHeight: 0 + } + + // STATE MANAGMENT + , useStateCookie: false // Enable cookie-based state-management - can fine-tune with cookie.autoLoad/autoSave + , cookie: { + name: "" // If not specified, will use Layout.name, else just "Layout" + , autoSave: true // Save a state cookie when page exits? + , autoLoad: true // Load the state cookie when Layout inits? + // Cookie Options + , domain: "" + , path: "" + , expires: "" // 'days' to keep cookie - leave blank for 'session cookie' + , secure: false + // List of options to save in the cookie - must be pane-specific + , keys: "north.size,south.size,east.size,west.size,"+ + "north.isClosed,south.isClosed,east.isClosed,west.isClosed,"+ + "north.isHidden,south.isHidden,east.isHidden,west.isHidden" + } + } + + + // PREDEFINED EFFECTS / DEFAULTS +, effects = { // LIST *PREDEFINED EFFECTS* HERE, even if effect has no settings + slide: { + all: { duration: "fast" } // eg: duration: 1000, easing: "easeOutBounce" + , north: { direction: "up" } + , south: { direction: "down" } + , east: { direction: "right"} + , west: { direction: "left" } + } + , drop: { + all: { duration: "slow" } // eg: duration: 1000, easing: "easeOutQuint" + , north: { direction: "up" } + , south: { direction: "down" } + , east: { direction: "right"} + , west: { direction: "left" } + } + , scale: { + all: { duration: "fast" } + } + } + + + // DYNAMIC DATA - IS READ-ONLY EXTERNALLY! +, state = { + // generate unique ID to use for event.namespace so can unbind only events added by 'this layout' + id: "layout"+ new Date().getTime() // code uses alias: sID + , initialized: false + , container: {} // init all keys + , north: {} + , south: {} + , east: {} + , west: {} + , center: {} + , cookie: {} // State Managment data storage + } + + + // INTERNAL CONFIG DATA - DO NOT CHANGE THIS! +, _c = { + allPanes: "north,south,west,east,center" + , borderPanes: "north,south,west,east" + , altSide: { + north: "south" + , south: "north" + , east: "west" + , west: "east" + } + // CSS used in multiple places + , hidden: { visibility: "hidden" } + , visible: { visibility: "visible" } + // layout element settings + , zIndex: { // set z-index values here + pane_normal: 1 // normal z-index for panes + , resizer_normal: 2 // normal z-index for resizer-bars + , iframe_mask: 2 // overlay div used to mask pane(s) during resizing + , pane_sliding: 100 // applied to *BOTH* the pane and its resizer when a pane is 'slid open' + , pane_animate: 1000 // applied to the pane when being animated - not applied to the resizer + , resizer_drag: 10000 // applied to the CLONED resizer-bar when being 'dragged' + } + , resizers: { + cssReq: { + position: "absolute" + , padding: 0 + , margin: 0 + , fontSize: "1px" + , textAlign: "left" // to counter-act "center" alignment! + , overflow: "hidden" // prevent toggler-button from overflowing + // SEE c.zIndex.resizer_normal + } + , cssDemo: { // DEMO CSS - applied if: options.PANE.applyDemoStyles=true + background: "#DDD" + , border: "none" + } + } + , togglers: { + cssReq: { + position: "absolute" + , display: "block" + , padding: 0 + , margin: 0 + , overflow: "hidden" + , textAlign: "center" + , fontSize: "1px" + , cursor: "pointer" + , zIndex: 1 + } + , cssDemo: { // DEMO CSS - applied if: options.PANE.applyDemoStyles=true + background: "#AAA" + } + } + , content: { + cssReq: { + position: "relative" /* contain floated or positioned elements */ + } + , cssDemo: { // DEMO CSS - applied if: options.PANE.applyDemoStyles=true + overflow: "auto" + , padding: "10px" + } + , cssDemoPane: { // DEMO CSS - REMOVE scrolling from 'pane' when it has a content-div + overflow: "hidden" + , padding: 0 + } + } + , panes: { // defaults for ALL panes - overridden by 'per-pane settings' below + cssReq: { + position: "absolute" + , margin: 0 + // SEE c.zIndex.pane_normal + } + , cssDemo: { // DEMO CSS - applied if: options.PANE.applyDemoStyles=true + padding: "10px" + , background: "#FFF" + , border: "1px solid #BBB" + , overflow: "auto" + } + } + , north: { + side: "Top" + , sizeType: "Height" + , dir: "horz" + , cssReq: { + top: 0 + , bottom: "auto" + , left: 0 + , right: 0 + , width: "auto" + // height: DYNAMIC + } + , pins: [] // array of 'pin buttons' to be auto-updated on open/close (classNames) + } + , south: { + side: "Bottom" + , sizeType: "Height" + , dir: "horz" + , cssReq: { + top: "auto" + , bottom: 0 + , left: 0 + , right: 0 + , width: "auto" + // height: DYNAMIC + } + , pins: [] + } + , east: { + side: "Right" + , sizeType: "Width" + , dir: "vert" + , cssReq: { + left: "auto" + , right: 0 + , top: "auto" // DYNAMIC + , bottom: "auto" // DYNAMIC + , height: "auto" + // width: DYNAMIC + } + , pins: [] + } + , west: { + side: "Left" + , sizeType: "Width" + , dir: "vert" + , cssReq: { + left: 0 + , right: "auto" + , top: "auto" // DYNAMIC + , bottom: "auto" // DYNAMIC + , height: "auto" + // width: DYNAMIC + } + , pins: [] + } + , center: { + dir: "center" + , cssReq: { + left: "auto" // DYNAMIC + , right: "auto" // DYNAMIC + , top: "auto" // DYNAMIC + , bottom: "auto" // DYNAMIC + , height: "auto" + , width: "auto" + } + } + } + + +/* + * ########################### + * INTERNAL HELPER FUNCTIONS + * ########################### + */ + + /** + * Manages all internal timers + */ +, timer = { + data: {} + , set: function (s, fn, ms) { timer.clear(s); timer.data[s] = setTimeout(fn, ms); } + , clear: function (s) { var t=timer.data; if (t[s]) {clearTimeout(t[s]); delete t[s];} } + } + + /** + * Returns true if passed param is EITHER a simple string OR a 'string object' - otherwise returns false + */ +, isStr = function (o) { + try { return typeof o == "string" + || (typeof o == "object" && o.constructor.toString().match(/string/i) !== null); } + catch (e) { return false; } + } + + /** + * Returns a simple string if passed EITHER a simple string OR a 'string object', + * else returns the original object + */ +, str = function (o) { // trim converts 'String object' to a simple string + return isStr(o) ? $.trim(o) : o == undefined || o == null ? "" : o; + } + + /** + * min / max + * + * Aliases for Math methods to simplify coding + */ +, min = function (x,y) { return Math.min(x,y); } +, max = function (x,y) { return Math.max(x,y); } + + /** + * Processes the options passed in and transforms them into the format used by layout() + * Missing keys are added, and converts the data if passed in 'flat-format' (no sub-keys) + * In flat-format, pane-specific-settings are prefixed like: north__optName (2-underscores) + * To update effects, options MUST use nested-keys format, with an effects key ??? + * + * @see initOptions() + * @param {Object} d Data/options passed by user - may be a single level or nested levels + * @return {Object} Creates a data struture that perfectly matches 'options', ready to be imported + */ +, _transformData = function (d) { + var a, json = { cookie:{}, defaults:{fxSettings:{}}, north:{fxSettings:{}}, south:{fxSettings:{}}, east:{fxSettings:{}}, west:{fxSettings:{}}, center:{fxSettings:{}} }; + d = d || {}; + if (d.effects || d.cookie || d.defaults || d.north || d.south || d.west || d.east || d.center) + json = $.extend( true, json, d ); // already in json format - add to base keys + else + // convert 'flat' to 'nest-keys' format - also handles 'empty' user-options + $.each( d, function (key,val) { + a = key.split("__"); + if (!a[1] || json[a[0]]) // check for invalid keys + json[ a[1] ? a[0] : "defaults" ][ a[1] ? a[1] : a[0] ] = val; + }); + return json; + } + + /** + * Set an INTERNAL callback to avoid simultaneous animation + * Runs only if needed and only if all callbacks are not 'already set' + * Called by open() and close() when isLayoutBusy=true + * + * @param {string} action Either 'open' or 'close' + * @param {string} pane A valid border-pane name, eg 'west' + * @param {boolean=} param Extra param for callback (optional) + */ +, _queue = function (action, pane, param) { + var tried = []; + + // if isLayoutBusy, then some pane must be 'moving' + $.each(_c.borderPanes.split(","), function (i, p) { + if (_c[p].isMoving) { + bindCallback(p); // TRY to bind a callback + return false; // BREAK + } + }); + + // if pane does NOT have a callback, then add one, else follow the callback chain... + function bindCallback (p) { + var c = _c[p]; + if (!c.doCallback) { + c.doCallback = true; + c.callback = action +","+ pane +","+ (param ? 1 : 0); + } + else { // try to 'chain' this callback + tried.push(p); + var cbPane = c.callback.split(",")[1]; // 2nd param of callback is 'pane' + // ensure callback target NOT 'itself' and NOT 'target pane' and NOT already tried (avoid loop) + if (cbPane != pane && !$.inArray(cbPane, tried) >= 0) + bindCallback(cbPane); // RECURSE + } + } + } + + /** + * RUN the INTERNAL callback for this pane - if one exists + * + * @param {string} pane A valid border-pane name, eg 'west' + */ +, _dequeue = function (pane) { + var c = _c[pane]; + + // RESET flow-control flags + _c.isLayoutBusy = false; + delete c.isMoving; + if (!c.doCallback || !c.callback) return; + + c.doCallback = false; // RESET logic flag + + // EXECUTE the callback + var + cb = c.callback.split(",") + , param = (cb[2] > 0 ? true : false) + ; + if (cb[0] == "open") + open( cb[1], param ); + else if (cb[0] == "close") + close( cb[1], param ); + + if (!c.doCallback) c.callback = null; // RESET - unless callback above enabled it again! + } + + /** + * Executes a Callback function after a trigger event, like resize, open or close + * + * @param {?string} pane This is passed only so we can pass the 'pane object' to the callback + * @param {(string|function())} v_fn Accepts a function name, OR a comma-delimited array: [0]=function name, [1]=argument + */ +, _execCallback = function (pane, v_fn) { + if (!v_fn) return; + var fn; + try { + if (typeof v_fn == "function") + fn = v_fn; + else if (!isStr(v_fn)) + return; + else if (v_fn.match(/,/)) { + // function name cannot contain a comma, so must be a function name AND a 'name' parameter + var args = v_fn.split(","); + fn = eval(args[0]); + if (typeof fn=="function" && args.length > 1) + return fn(args[1]); // pass the argument parsed from 'list' + } + else // just the name of an external function? + fn = eval(v_fn); + + if (typeof fn=="function") { + if (pane && $Ps[pane]) + // pass data: pane-name, pane-element, pane-state, pane-options, and layout-name + return fn( pane, $Ps[pane], state[pane], options[pane], options.name ); + else // must be a layout/container callback - pass suitable info + return fn( Instance, state, options, options.name ); + } + } + catch (ex) {} + } + + /** + * cure iframe display issues in IE & other browsers + */ +, _fixIframe = function (pane) { + if ($.layout.browser.mozilla) return; // skip FireFox - it auto-refreshes iframes onShow + var $P = $Ps[pane]; + // if the 'pane' is an iframe, do it + if (state[pane].tagName == "IFRAME") + $P.css(_c.hidden).css(_c.visible); + else // ditto for any iframes INSIDE the pane + $P.find('IFRAME').css(_c.hidden).css(_c.visible); + } + + /** + * cssW / cssH / cssSize / cssMinDims + * + * Contains logic to check boxModel & browser, and return the correct width/height for the current browser/doctype + * + * @see initPanes(), sizeMidPanes(), initHandles(), sizeHandles() + * @param {(string|!Object)} el Can accept a 'pane' (east, west, etc) OR a DOM object OR a jQuery object + * @param {number=} outerWidth (optional) Can pass a width, allowing calculations BEFORE element is resized + * @return {number} Returns the innerWidth of el by subtracting padding and borders + */ +, cssW = function (el, outerWidth) { + var str = isStr(el) + , $E = str ? $Ps[el] : $(el) + ; + if (!$E.length) return 0; + if (isNaN(outerWidth)) // not specified + outerWidth = str ? getPaneSize(el) : $E.outerWidth(); + return $.layout.cssWidth($E, outerWidth); + } + + /** + * @param {(string|!Object)} el Can accept a 'pane' (east, west, etc) OR a DOM object OR a jQuery object + * @param {number=} outerHeight (optional) Can pass a width, allowing calculations BEFORE element is resized + * @return {number} Returns the innerHeight el by subtracting padding and borders + */ +, cssH = function (el, outerHeight) { + var str = isStr(el) + , $E = str ? $Ps[el] : $(el) + ; + if (!$E.length) return 0; + if (isNaN(outerHeight)) // not specified + outerHeight = str ? getPaneSize(el) : $E.outerHeight(); + return $.layout.cssHeight($E, outerHeight); + } + + /** + * @param {string} pane Can accept ONLY a 'pane' (east, west, etc) + * @param {number=} outerSize (optional) Can pass a width, allowing calculations BEFORE element is resized + * @return {number} Returns the innerHeight/Width of el by subtracting padding and borders + */ +, cssSize = function (pane, outerSize) { + if (_c[pane].dir=="horz") // pane = north or south + return cssH(pane, outerSize); + else // pane = east or west + return cssW(pane, outerSize); + } + + /** + * @param {string} pane Can accept ONLY a 'pane' (east, west, etc) + * @return {Object} Returns hash of minWidth & minHeight + */ +, cssMinDims = function (pane) { + // minWidth/Height means CSS width/height = 1px + var + dir = _c[pane].dir + , d = { + minWidth: 1001 - cssW(pane, 1000) + , minHeight: 1001 - cssH(pane, 1000) + } + ; + if (dir == "horz") d.minSize = d.minHeight; + if (dir == "vert") d.minSize = d.minWidth; + return d; + } + + // TODO: see if these methods can be made more useful... + // TODO: *maybe* return cssW/H from these so caller can use this info + + /** + * @param {(string|!Object)} el + * @param {number=} outerWidth + * @param {boolean=} autoHide + */ +, setOuterWidth = function (el, outerWidth, autoHide) { + var $E = el, w; + if (isStr(el)) $E = $Ps[el]; // west + else if (!el.jquery) $E = $(el); + w = cssW($E, outerWidth); + $E.css({ width: w }); + if (w > 0) { + if (autoHide && $E.data('autoHidden') && $E.innerHeight() > 0) { + $E.show().data('autoHidden', false); + if (!$.layout.browser.mozilla) // FireFox refreshes iframes - IE does not + // make hidden, then visible to 'refresh' display after animation + $E.css(_c.hidden).css(_c.visible); + } + } + else if (autoHide && !$E.data('autoHidden')) + $E.hide().data('autoHidden', true); + } + + /** + * @param {(string|!Object)} el + * @param {number=} outerHeight + * @param {boolean=} autoHide + */ +, setOuterHeight = function (el, outerHeight, autoHide) { + var $E = el, h; + if (isStr(el)) $E = $Ps[el]; // west + else if (!el.jquery) $E = $(el); + h = cssH($E, outerHeight); + $E.css({ height: h, visibility: "visible" }); // may have been 'hidden' by sizeContent + if (h > 0 && $E.innerWidth() > 0) { + if (autoHide && $E.data('autoHidden')) { + $E.show().data('autoHidden', false); + if (!$.layout.browser.mozilla) // FireFox refreshes iframes - IE does not + $E.css(_c.hidden).css(_c.visible); + } + } + else if (autoHide && !$E.data('autoHidden')) + $E.hide().data('autoHidden', true); + } + + /** + * @param {(string|!Object)} el + * @param {number=} outerSize + * @param {boolean=} autoHide + */ +, setOuterSize = function (el, outerSize, autoHide) { + if (_c[pane].dir=="horz") // pane = north or south + setOuterHeight(el, outerSize, autoHide); + else // pane = east or west + setOuterWidth(el, outerSize, autoHide); + } + + + /** + * Converts any 'size' params to a pixel/integer size, if not already + * If 'auto' or a decimal/percentage is passed as 'size', a pixel-size is calculated + * + /** + * @param {string} pane + * @param {(string|number)=} size + * @param {string=} dir + * @return {number} + */ +, _parseSize = function (pane, size, dir) { + if (!dir) dir = _c[pane].dir; + + if (isStr(size) && size.match(/%/)) + size = parseInt(size, 10) / 100; // convert % to decimal + + if (size === 0) + return 0; + else if (size >= 1) + return parseInt(size, 10); + else if (size > 0) { // percentage, eg: .25 + var o = options, avail; + if (dir=="horz") // north or south or center.minHeight + avail = sC.innerHeight - ($Ps.north ? o.north.spacing_open : 0) - ($Ps.south ? o.south.spacing_open : 0); + else if (dir=="vert") // east or west or center.minWidth + avail = sC.innerWidth - ($Ps.west ? o.west.spacing_open : 0) - ($Ps.east ? o.east.spacing_open : 0); + return Math.floor(avail * size); + } + else if (pane=="center") + return 0; + else { // size < 0 || size=='auto' || size==Missing || size==Invalid + // auto-size the pane + var + $P = $Ps[pane] + , dim = (dir == "horz" ? "height" : "width") + , vis = $.layout.showInvisibly($P) // show pane invisibly if hidden + , s = $P.css(dim); // SAVE current size + ; + $P.css(dim, "auto"); + size = (dim == "height") ? $P.outerHeight() : $P.outerWidth(); // MEASURE + $P.css(dim, s).css(vis); // RESET size & visibility + return size; + } + } + + /** + * Calculates current 'size' (outer-width or outer-height) of a border-pane - optionally with 'pane-spacing' added + * + * @param {(string|!Object)} pane + * @param {boolean=} inclSpace + * @return {number} Returns EITHER Width for east/west panes OR Height for north/south panes - adjusted for boxModel & browser + */ +, getPaneSize = function (pane, inclSpace) { + var + $P = $Ps[pane] + , o = options[pane] + , s = state[pane] + , oSp = (inclSpace ? o.spacing_open : 0) + , cSp = (inclSpace ? o.spacing_closed : 0) + ; + if (!$P || s.isHidden) + return 0; + else if (s.isClosed || (s.isSliding && inclSpace)) + return cSp; + else if (_c[pane].dir == "horz") + return $P.outerHeight() + oSp; + else // dir == "vert" + return $P.outerWidth() + oSp; + } + + /** + * Calculate min/max pane dimensions and limits for resizing + * + * @param {string} pane + * @param {boolean=} slide + */ +, setSizeLimits = function (pane, slide) { + if (!isInitialized()) return; + var + o = options[pane] + , s = state[pane] + , c = _c[pane] + , dir = c.dir + , side = c.side.toLowerCase() + , type = c.sizeType.toLowerCase() + , isSliding = (slide != undefined ? slide : s.isSliding) // only open() passes 'slide' param + , $P = $Ps[pane] + , paneSpacing = o.spacing_open + // measure the pane on the *opposite side* from this pane + , altPane = _c.altSide[pane] + , altS = state[altPane] + , $altP = $Ps[altPane] + , altPaneSize = (!$altP || altS.isVisible===false || altS.isSliding ? 0 : (dir=="horz" ? $altP.outerHeight() : $altP.outerWidth())) + , altPaneSpacing = ((!$altP || altS.isHidden ? 0 : options[altPane][ altS.isClosed !== false ? "spacing_closed" : "spacing_open" ]) || 0) + // limitSize prevents this pane from 'overlapping' opposite pane + , containerSize = (dir=="horz" ? sC.innerHeight : sC.innerWidth) + , minCenterDims = cssMinDims("center") + , minCenterSize = dir=="horz" ? max(options.center.minHeight, minCenterDims.minHeight) : max(options.center.minWidth, minCenterDims.minWidth) + // if pane is 'sliding', then ignore center and alt-pane sizes - because 'overlays' them + , limitSize = (containerSize - paneSpacing - (isSliding ? 0 : (_parseSize("center", minCenterSize, dir) + altPaneSize + altPaneSpacing))) + , minSize = s.minSize = max( _parseSize(pane, o.minSize), cssMinDims(pane).minSize ) + , maxSize = s.maxSize = min( (o.maxSize ? _parseSize(pane, o.maxSize) : 100000), limitSize ) + , r = s.resizerPosition = {} // used to set resizing limits + , top = sC.insetTop + , left = sC.insetLeft + , W = sC.innerWidth + , H = sC.innerHeight + , rW = o.spacing_open // subtract resizer-width to get top/left position for south/east + ; + switch (pane) { + case "north": r.min = top + minSize; + r.max = top + maxSize; + break; + case "west": r.min = left + minSize; + r.max = left + maxSize; + break; + case "south": r.min = top + H - maxSize - rW; + r.max = top + H - minSize - rW; + break; + case "east": r.min = left + W - maxSize - rW; + r.max = left + W - minSize - rW; + break; + }; + } + + /** + * Returns data for setting the size/position of center pane. Also used to set Height for east/west panes + * + * @return JSON Returns a hash of all dimensions: top, bottom, left, right, (outer) width and (outer) height + */ +, calcNewCenterPaneDims = function () { + var d = { + top: getPaneSize("north", true) // true = include 'spacing' value for pane + , bottom: getPaneSize("south", true) + , left: getPaneSize("west", true) + , right: getPaneSize("east", true) + , width: 0 + , height: 0 + }; + + // NOTE: sC = state.container + // calc center-pane outer dimensions + d.width = sC.innerWidth - d.left - d.right; // outerWidth + d.height = sC.innerHeight - d.bottom - d.top; // outerHeight + // add the 'container border/padding' to get final positions relative to the container + d.top += sC.insetTop; + d.bottom += sC.insetBottom; + d.left += sC.insetLeft; + d.right += sC.insetRight; + + return d; + } + + + /** + * Returns data for setting size of an element (container or a pane). + * + * @see _create(), onWindowResize() for container, plus others for pane + * @return JSON Returns a hash of all dimensions: top, bottom, left, right, outerWidth, innerHeight, etc + */ +, elDims = function ($E) { return $.layout.getElementDimensions($E); } + +, elCSS = function ($E, list) { return $.layout.getElementCSS($E, list); } + + + /** + * @param {!Object} el + * @param {boolean=} allStates + */ +, getHoverClasses = function (el, allStates) { + var + $El = $(el) + , type = $El.data("layoutRole") + , pane = $El.data("layoutEdge") + , o = options[pane] + , root = o[type +"Class"] + , _pane = "-"+ pane // eg: "-west" + , _open = "-open" + , _closed = "-closed" + , _slide = "-sliding" + , _hover = "-hover " // NOTE the trailing space + , _state = $El.hasClass(root+_closed) ? _closed : _open + , _alt = _state == _closed ? _open : _closed + , classes = (root+_hover) + (root+_pane+_hover) + (root+_state+_hover) + (root+_pane+_state+_hover) + ; + if (allStates) // when 'removing' classes, also remove alternate-state classes + classes += (root+_alt+_hover) + (root+_pane+_alt+_hover); + + if (type=="resizer" && $El.hasClass(root+_slide)) + classes += (root+_slide+_hover) + (root+_pane+_slide+_hover); + + return $.trim(classes); + } +, addHover = function (evt, el) { + var $E = $(el || this); + if (evt && $E.data("layoutRole") == "toggler") + evt.stopPropagation(); // prevent triggering 'slide' on Resizer-bar + $E.addClass( getHoverClasses($E) ); + } +, removeHover = function (evt, el) { + var $E = $(el || this); + $E.removeClass( getHoverClasses($E, true) ); + } + +, onResizerEnter = function (evt) { + $('body').disableSelection(); + addHover(evt, this); + } +, onResizerLeave = function (evt, el) { + var + e = el || this // el is only passed when called by the timer + , pane = $(e).data("layoutEdge") + , name = pane +"ResizerLeave" + ; + timer.clear(pane+"_openSlider"); // cancel slideOpen timer, if set + timer.clear(name); // cancel enableSelection timer - may re/set below + if (!el) { // 1st call - mouseleave event + removeHover(evt, this); // do this on initial call + // this method calls itself on a timer because it needs to allow + // enough time for dragging to kick-in and set the isResizing flag + // dragging has a 100ms delay set, so this delay must be higher + timer.set(name, function(){ onResizerLeave(evt, e); }, 200); + } + // if user is resizing, then dragStop will enableSelection() when done + else if (!state[pane].isResizing) // 2nd call - by timer + $('body').enableSelection(); + } + +/* + * ########################### + * INITIALIZATION METHODS + * ########################### + */ + + /** + * Initialize the layout - called automatically whenever an instance of layout is created + * + * @see none - triggered onInit + * @return mixed true = fully initialized | false = panes not initialized (yet) | 'cancel' = abort + */ +, _create = function () { + // initialize config/options + initOptions(); + var o = options; + + $.layout.browser.boxModel = $.support.boxModel; + + // update options with saved state, if option enabled + if (o.useStateCookie && o.cookie.autoLoad) + loadCookie(); // Update options from state-cookie + + // TEMP state so isInitialized returns true during init process + state.creatingLayout = true; + + // options & state have been initialized, so now run beforeLoad callback + // onload will CANCEL layout creation if it returns false + if (false === _execCallback(null, o.onload_start)) + return 'cancel'; + + // initialize the container element + _initContainer(); + + // bind hotkey function - keyDown - if required + initHotkeys(); + + // search for and bind custom-buttons + if (o.autoBindCustomButtons) initButtons(); + + // bind window.onunload + $(window).bind("unload."+ sID, unload); + + // if layout elements are hidden, then layout WILL NOT complete initialization! + // initLayoutElements will set initialized=true and run the onload callback IF successful + if (o.initPanes) _initLayoutElements(); + + delete state.creatingLayout; + + return state.initialized; + } + + /** + * Initialize the layout IF not already + * + * @see All methods in Instance run this test + * @return boolean true = layoutElements have been initialized | false = panes are not initialized (yet) + */ +, isInitialized = function () { + if (state.initialized || state.creatingLayout) return true; // already initialized + else return _initLayoutElements(); // try to init panes NOW + } + + /** + * Initialize the layout - called automatically whenever an instance of layout is created + * + * @see _create() & isInitialized + * @return An object pointer to the instance created + */ +, _initLayoutElements = function () { + // initialize config/options + var o = options; + + // CANNOT init panes inside a hidden container! + if (!$N.is(":visible")) + return false; + // a center pane is required, so make sure it exists + if (!getPane('center').length) { + if (o.showErrorMessages) alert( lang.errCenterPaneMissing ); + return false; + } + + // TEMP state so isInitialized returns true during init process + state.creatingLayout = true; + + // update Container dims + $.extend(sC, elDims( $N )); + + // initialize all layout elements + initPanes(); // size & position panes - calls initHandles() - which calls initResizable() + sizeContent(); // AFTER panes & handles have been initialized, size 'content' divs + + if (o.scrollToBookmarkOnLoad) { + var l = self.location; + if (l.hash) l.replace( l.hash ); // scrollTo Bookmark + } + + // bind resizeAll() for 'this layout instance' to window.resize event + if (o.resizeWithWindow && !$N.data("layoutRole")) // skip if 'nested' inside a pane + $(window).bind("resize."+ sID, windowResize); + + delete state.creatingLayout; + state.initialized = true; + + _execCallback(null, o.onload_end || o.onload); + + return true; // elements initialized successfully + } + + +, windowResize = function () { + var delay = Number(options.resizeWithWindowDelay); + if (delay < 10) delay = 100; // MUST have a delay! + // resizing uses a delay-loop because the resize event fires repeatly - except in FF, but delay anyway + timer.clear("winResize"); // if already running + timer.set("winResize", function(){ + timer.clear("winResize"); + timer.clear("winResizeRepeater"); + var dims = elDims( $N ); + // only trigger resizeAll() if container has changed size + if (dims.innerWidth !== sC.innerWidth || dims.innerHeight !== sC.innerHeight) + resizeAll(); + }, delay); + // ALSO set fixed-delay timer, if not already running + if (!timer.data["winResizeRepeater"]) setWindowResizeRepeater(); + } + +, setWindowResizeRepeater = function () { + var delay = Number(options.resizeWithWindowMaxDelay); + if (delay > 0) + timer.set("winResizeRepeater", function(){ setWindowResizeRepeater(); resizeAll(); }, delay); + } + +, unload = function () { + var o = options; + state.cookie = getState(); // save state in case onunload has custom state-management + _execCallback(null, o.onunload_start); + if (o.useStateCookie && o.cookie.autoSave) saveCookie(); + _execCallback(null, o.onunload_end || o.onunload); + } + + /** + * Validate and initialize container CSS and events + * + * @see _create() + */ +, _initContainer = function () { + var + tag = sC.tagName = $N[0].tagName + , o = options + , fullPage= (tag == "BODY") + , props = "overflow,position,margin,padding,border" + , CSS = {} + , hid = "hidden" // used A LOT! + , isVis = $N.is(":visible") + ; + // sC -> state.container + sC.selector = $N.selector.split(".slice")[0]; + sC.ref = tag +"/"+ sC.selector; // used in messages + + $N .data("layout", Instance) + .data("layoutContainer", sID) // unique identifier for internal use + .addClass(o.containerClass) + ; + + // SAVE original container CSS for use in destroy() + if (!$N.data("layoutCSS")) { + // handle props like overflow different for BODY & HTML - has 'system default' values + if (fullPage) { + CSS = $.extend( elCSS($N, props), { + height: $N.css("height") + , overflow: $N.css("overflow") + , overflowX: $N.css("overflowX") + , overflowY: $N.css("overflowY") + }); + // ALSO SAVE CSS + var $H = $("html"); + $H.data("layoutCSS", { + height: "auto" // FF would return a fixed px-size! + , overflow: $H.css("overflow") + , overflowX: $H.css("overflowX") + , overflowY: $H.css("overflowY") + }); + } + else // handle props normally for non-body elements + CSS = elCSS($N, props+",top,bottom,left,right,width,height,overflow,overflowX,overflowY"); + + $N.data("layoutCSS", CSS); + } + + try { // format html/body if this is a full page layout + if (fullPage) { + $("html").css({ + height: "100%" + , overflow: hid + , overflowX: hid + , overflowY: hid + }); + $("body").css({ + position: "relative" + , height: "100%" + , overflow: hid + , overflowX: hid + , overflowY: hid + , margin: 0 + , padding: 0 // TODO: test whether body-padding could be handled? + , border: "none" // a body-border creates problems because it cannot be measured! + }); + + // set current layout-container dimensions + $.extend(sC, elDims( $N )); + } + else { // set required CSS for overflow and position + // ENSURE container will not 'scroll' + CSS = { overflow: hid, overflowX: hid, overflowY: hid } + var + p = $N.css("position") + , h = $N.css("height") + ; + // if this is a NESTED layout, then container/outer-pane ALREADY has position and height + if (!$N.data("layoutRole")) { + if (!p || !p.match(/fixed|absolute|relative/)) + CSS.position = "relative"; // container MUST have a 'position' + /* + if (!h || h=="auto") + CSS.height = "100%"; // container MUST have a 'height' + */ + } + $N.css( CSS ); + + // set current layout-container dimensions + if (isVis) { + $.extend(sC, elDims( $N )); + if (o.showErrorMessages && sC.innerHeight < 2) + alert( lang.errContainerHeight.replace(/CONTAINER/, sC.ref) ); + } + } + } catch (ex) {} + } + + /** + * Bind layout hotkeys - if options enabled + * + * @see _create() and addPane() + * @param {string=} panes The edge(s) to process, blank = all + */ +, initHotkeys = function (panes) { + if (!panes || panes == "all") panes = _c.borderPanes; + // bind keyDown to capture hotkeys, if option enabled for ANY pane + $.each(panes.split(","), function (i, pane) { + var o = options[pane]; + if (o.enableCursorHotkey || o.customHotkey) { + $(document).bind("keydown."+ sID, keyDown); // only need to bind this ONCE + return false; // BREAK - binding was done + } + }); + } + + /** + * Build final OPTIONS data + * + * @see _create() + */ +, initOptions = function () { + // simplify logic by making sure passed 'opts' var has basic keys + opts = _transformData( opts ); + + // TODO: create a compatibility add-on for new UI widget that will transform old option syntax + var newOpts = { + applyDefaultStyles: "applyDemoStyles" + }; + renameOpts(opts.defaults); + $.each(_c.allPanes.split(","), function (i, pane) { + renameOpts(opts[pane]); + }); + + // update default effects, if case user passed key + if (opts.effects) { + $.extend( effects, opts.effects ); + delete opts.effects; + } + $.extend( options.cookie, opts.cookie ); + + // see if any 'global options' were specified + var globals = "name,containerClass,zIndex,scrollToBookmarkOnLoad,resizeWithWindow,resizeWithWindowDelay,resizeWithWindowMaxDelay,"+ + "onresizeall,onresizeall_start,onresizeall_end,onload,onload_start,onload_end,onunload,onunload_start,onunload_end,autoBindCustomButtons,useStateCookie"; + $.each(globals.split(","), function (i, key) { + if (opts[key] !== undefined) + options[key] = opts[key]; + else if (opts.defaults[key] !== undefined) { + options[key] = opts.defaults[key]; + delete opts.defaults[key]; + } + }); + + // remove any 'defaults' that MUST be set 'per-pane' + $.each("paneSelector,resizerCursor,customHotkey".split(","), + function (i, key) { delete opts.defaults[key]; } // is OK if key does not exist + ); + + // now update options.defaults + $.extend( true, options.defaults, opts.defaults ); + + // merge config for 'center-pane' - border-panes handled in the loop below + _c.center = $.extend( true, {}, _c.panes, _c.center ); + // update config.zIndex values if zIndex option specified + var z = options.zIndex; + if (z === 0 || z > 0) { + _c.zIndex.pane_normal = z; + _c.zIndex.resizer_normal = z+1; + _c.zIndex.iframe_mask = z+1; + } + + // merge options for 'center-pane' - border-panes handled in the loop below + $.extend( options.center, opts.center ); + // Most 'default options' do not apply to 'center', so add only those that DO + var o_Center = $.extend( true, {}, options.defaults, opts.defaults, options.center ); // TEMP data + var optionsCenter = ("paneClass,contentSelector,applyDemoStyles,triggerEventsOnLoad,showOverflowOnHover," + + "onresize,onresize_start,onresize_end,resizeNestedLayout,resizeContentWhileDragging," + + "onsizecontent,onsizecontent_start,onsizecontent_end").split(","); + $.each(optionsCenter, + function (i, key) { options.center[key] = o_Center[key]; } + ); + + var o, defs = options.defaults; + + // create a COMPLETE set of options for EACH border-pane + $.each(_c.borderPanes.split(","), function (i, pane) { + + // apply 'pane-defaults' to CONFIG.[PANE] + _c[pane] = $.extend( true, {}, _c.panes, _c[pane] ); + + // apply 'pane-defaults' + user-options to OPTIONS.PANE + o = options[pane] = $.extend( true, {}, options.defaults, options[pane], opts.defaults, opts[pane] ); + + // make sure we have base-classes + if (!o.paneClass) o.paneClass = "ui-layout-pane"; + if (!o.resizerClass) o.resizerClass = "ui-layout-resizer"; + if (!o.togglerClass) o.togglerClass = "ui-layout-toggler"; + + // create FINAL fx options for each pane, ie: options.PANE.fxName/fxSpeed/fxSettings[_open|_close] + $.each(["_open","_close",""], function (i,n) { + var + sName = "fxName"+n + , sSpeed = "fxSpeed"+n + , sSettings = "fxSettings"+n + ; + // recalculate fxName according to specificity rules + o[sName] = + opts[pane][sName] // opts.west.fxName_open + || opts[pane].fxName // opts.west.fxName + || opts.defaults[sName] // opts.defaults.fxName_open + || opts.defaults.fxName // opts.defaults.fxName + || o[sName] // options.west.fxName_open + || o.fxName // options.west.fxName + || defs[sName] // options.defaults.fxName_open + || defs.fxName // options.defaults.fxName + || "none" + ; + // validate fxName to be sure is a valid effect + var fxName = o[sName]; + if (fxName == "none" || !$.effects || !$.effects[fxName] || (!effects[fxName] && !o[sSettings] && !o.fxSettings)) + fxName = o[sName] = "none"; // effect not loaded, OR undefined FX AND fxSettings not passed + // set vars for effects subkeys to simplify logic + var + fx = effects[fxName] || {} // effects.slide + , fx_all = fx.all || {} // effects.slide.all + , fx_pane = fx[pane] || {} // effects.slide.west + ; + // RECREATE the fxSettings[_open|_close] keys using specificity rules + o[sSettings] = $.extend( + {} + , fx_all // effects.slide.all + , fx_pane // effects.slide.west + , defs.fxSettings || {} // options.defaults.fxSettings + , defs[sSettings] || {} // options.defaults.fxSettings_open + , o.fxSettings // options.west.fxSettings + , o[sSettings] // options.west.fxSettings_open + , opts.defaults.fxSettings // opts.defaults.fxSettings + , opts.defaults[sSettings] || {} // opts.defaults.fxSettings_open + , opts[pane].fxSettings // opts.west.fxSettings + , opts[pane][sSettings] || {} // opts.west.fxSettings_open + ); + // recalculate fxSpeed according to specificity rules + o[sSpeed] = + opts[pane][sSpeed] // opts.west.fxSpeed_open + || opts[pane].fxSpeed // opts.west.fxSpeed (pane-default) + || opts.defaults[sSpeed] // opts.defaults.fxSpeed_open + || opts.defaults.fxSpeed // opts.defaults.fxSpeed + || o[sSpeed] // options.west.fxSpeed_open + || o[sSettings].duration // options.west.fxSettings_open.duration + || o.fxSpeed // options.west.fxSpeed + || o.fxSettings.duration // options.west.fxSettings.duration + || defs.fxSpeed // options.defaults.fxSpeed + || defs.fxSettings.duration// options.defaults.fxSettings.duration + || fx_pane.duration // effects.slide.west.duration + || fx_all.duration // effects.slide.all.duration + || "normal" // DEFAULT + ; + }); + + }); + + function renameOpts (O) { + for (var key in newOpts) { + if (O[key] != undefined) { + O[newOpts[key]] = O[key]; + delete O[key]; + } + } + } + } + + /** + * Initialize module objects, styling, size and position for all panes + * + * @see _create() + * @param {string} pane The pane to process + */ +, getPane = function (pane) { + var sel = options[pane].paneSelector + if (sel.substr(0,1)==="#") // ID selector + // NOTE: elements selected 'by ID' DO NOT have to be 'children' + return $N.find(sel).eq(0); + else { // class or other selector + var $P = $N.children(sel).eq(0); + // look for the pane nested inside a 'form' element + return $P.length ? $P : $N.children("form:first").children(sel).eq(0); + } + } +, initPanes = function () { + // NOTE: do north & south FIRST so we can measure their height - do center LAST + $.each(_c.allPanes.split(","), function (idx, pane) { + addPane( pane, true ); + }); + + // init the pane-handles NOW in case we have to hide or close the pane below + initHandles(); + + // now that all panes have been initialized and initially-sized, + // make sure there is really enough space available for each pane + $.each(_c.borderPanes.split(","), function (i, pane) { + if ($Ps[pane] && state[pane].isVisible) { // pane is OPEN + setSizeLimits(pane); + makePaneFit(pane); // pane may be Closed, Hidden or Resized by makePaneFit() + } + }); + // size center-pane AGAIN in case we 'closed' a border-pane in loop above + sizeMidPanes("center"); + + // Chrome fires callback BEFORE it completes resizing, so add a delay before handling children + setTimeout(function(){ + $.each(_c.allPanes.split(","), function (i, pane) { + var o = options[pane]; + if ($Ps[pane] && state[pane].isVisible) { // pane is OPEN + // trigger onResize callbacks for all panes with triggerEventsOnLoad = true + if (o.triggerEventsOnLoad) + _execCallback(pane, o.onresize_end || o.onresize); + resizeNestedLayout(pane); + } + }); + }, 50 ); // 50ms delay is enough + + if (options.showErrorMessages && $N.innerHeight() < 2) + alert( lang.errContainerHeight.replace(/CONTAINER/, sC.ref) ); + } + + /** + * Remove a pane from the layout - subroutine of destroy() + * + * @see initPanes() + * @param {string} pane The pane to process + */ +, addPane = function (pane, force) { + if (!force && !isInitialized()) return; + var + o = options[pane] + , s = state[pane] + , c = _c[pane] + , fx = s.fx + , dir = c.dir + , spacing = o.spacing_open || 0 + , isCenter = (pane == "center") + , CSS = {} + , $P = $Ps[pane] + , size, minSize, maxSize + ; + + // if pane-pointer already exists, remove the old one first + if ($P) + removePane( pane ); + else + $Cs[pane] = false; // init + + $P = $Ps[pane] = getPane(pane); + if (!$P.length) { + $Ps[pane] = false; // logic + return; + } + + // SAVE original Pane CSS + if (!$P.data("layoutCSS")) { + var props = "position,top,left,bottom,right,width,height,overflow,zIndex,display,backgroundColor,padding,margin,border"; + $P.data("layoutCSS", elCSS($P, props)); + } + + // add basic classes & attributes + $P + .data("parentLayout", Instance) + .data("layoutRole", "pane") + .data("layoutEdge", pane) + .css(c.cssReq).css("zIndex", _c.zIndex.pane_normal) + .css(o.applyDemoStyles ? c.cssDemo : {}) // demo styles + .addClass( o.paneClass +" "+ o.paneClass+"-"+pane ) // default = "ui-layout-pane ui-layout-pane-west" - may be a dupe of 'paneSelector' + .bind("mouseenter."+ sID, addHover ) + .bind("mouseleave."+ sID, removeHover ) + ; + + // see if this pane has a 'scrolling-content element' + initContent(pane, false); // false = do NOT sizeContent() - called later + + if (!isCenter) { + // call _parseSize AFTER applying pane classes & styles - but before making visible (if hidden) + // if o.size is auto or not valid, then MEASURE the pane and use that as its 'size' + size = s.size = _parseSize(pane, o.size); + minSize = _parseSize(pane,o.minSize) || 1; + maxSize = _parseSize(pane,o.maxSize) || 100000; + if (size > 0) size = max(min(size, maxSize), minSize); + + // state for border-panes + s.isClosed = false; // true = pane is closed + s.isSliding = false; // true = pane is currently open by 'sliding' over adjacent panes + s.isResizing= false; // true = pane is in process of being resized + s.isHidden = false; // true = pane is hidden - no spacing, resizer or toggler is visible! + } + // state common to ALL panes + s.tagName = $P[0].tagName; + s.edge = pane // useful if pane is (or about to be) 'swapped' - easy find out where it is (or is going) + s.noRoom = false; // true = pane 'automatically' hidden due to insufficient room - will unhide automatically + s.isVisible = true; // false = pane is invisible - closed OR hidden - simplify logic + + // set css-position to account for container borders & padding + switch (pane) { + case "north": CSS.top = sC.insetTop; + CSS.left = sC.insetLeft; + CSS.right = sC.insetRight; + break; + case "south": CSS.bottom = sC.insetBottom; + CSS.left = sC.insetLeft; + CSS.right = sC.insetRight; + break; + case "west": CSS.left = sC.insetLeft; // top, bottom & height set by sizeMidPanes() + break; + case "east": CSS.right = sC.insetRight; // ditto + break; + case "center": // top, left, width & height set by sizeMidPanes() + } + + if (dir == "horz") // north or south pane + CSS.height = max(1, cssH(pane, size)); + else if (dir == "vert") // east or west pane + CSS.width = max(1, cssW(pane, size)); + //else if (isCenter) {} + + $P.css(CSS); // apply size -- top, bottom & height will be set by sizeMidPanes + if (dir != "horz") sizeMidPanes(pane, true); // true = skipCallback + + // close or hide the pane if specified in settings + if (o.initClosed && o.closable && !o.initHidden) + close(pane, true, true); // true, true = force, noAnimation + else if (o.initHidden || o.initClosed) + hide(pane); // will be completely invisible - no resizer or spacing + else if (!s.noRoom) + // make the pane visible - in case was initially hidden + $P.css("display","block"); + // ELSE setAsOpen() - called later by initHandles() + + // RESET visibility now - pane will appear IF display:block + $P.css("visibility","visible"); + + // check option for auto-handling of pop-ups & drop-downs + if (o.showOverflowOnHover) + $P.hover( allowOverflow, resetOverflow ); + + // if adding a pane AFTER initialization, then... + if (state.initialized) { + initHandles( pane ); + initHotkeys( pane ); + resizeAll(); // will sizeContent if pane is visible + if (s.isVisible) { // pane is OPEN + if (o.triggerEventsOnLoad) + _execCallback(pane, o.onresize_end || o.onresize); + resizeNestedLayout(pane); + } + } + } + + /** + * Initialize module objects, styling, size and position for all resize bars and toggler buttons + * + * @see _create() + * @param {string=} panes The edge(s) to process, blank = all + */ +, initHandles = function (panes) { + if (!panes || panes == "all") panes = _c.borderPanes; + + // create toggler DIVs for each pane, and set object pointers for them, eg: $R.north = north toggler DIV + $.each(panes.split(","), function (i, pane) { + var $P = $Ps[pane]; + $Rs[pane] = false; // INIT + $Ts[pane] = false; + if (!$P) return; // pane does not exist - skip + + var + o = options[pane] + , s = state[pane] + , c = _c[pane] + , rClass = o.resizerClass + , tClass = o.togglerClass + , side = c.side.toLowerCase() + , spacing = (s.isVisible ? o.spacing_open : o.spacing_closed) + , _pane = "-"+ pane // used for classNames + , _state = (s.isVisible ? "-open" : "-closed") // used for classNames + // INIT RESIZER BAR + , $R = $Rs[pane] = $("
") + // INIT TOGGLER BUTTON + , $T = (o.closable ? $Ts[pane] = $("
") : false) + ; + + //if (s.isVisible && o.resizable) ... handled by initResizable + if (!s.isVisible && o.slidable) + $R.attr("title", o.sliderTip).css("cursor", o.sliderCursor); + + $R + // if paneSelector is an ID, then create a matching ID for the resizer, eg: "#paneLeft" => "paneLeft-resizer" + .attr("id", (o.paneSelector.substr(0,1)=="#" ? o.paneSelector.substr(1) + "-resizer" : "")) + .data("parentLayout", Instance) + .data("layoutRole", "resizer") + .data("layoutEdge", pane) + .css(_c.resizers.cssReq).css("zIndex", _c.zIndex.resizer_normal) + .css(o.applyDemoStyles ? _c.resizers.cssDemo : {}) // add demo styles + .addClass(rClass +" "+ rClass+_pane) + .appendTo($N) // append DIV to container + ; + + if ($T) { + $T + // if paneSelector is an ID, then create a matching ID for the resizer, eg: "#paneLeft" => "#paneLeft-toggler" + .attr("id", (o.paneSelector.substr(0,1)=="#" ? o.paneSelector.substr(1) + "-toggler" : "")) + .data("parentLayout", Instance) + .data("layoutRole", "toggler") + .data("layoutEdge", pane) + .css(_c.togglers.cssReq) // add base/required styles + .css(o.applyDemoStyles ? _c.togglers.cssDemo : {}) // add demo styles + .addClass(tClass +" "+ tClass+_pane) + .appendTo($R) // append SPAN to resizer DIV + ; + // ADD INNER-SPANS TO TOGGLER + if (o.togglerContent_open) // ui-layout-open + $(""+ o.togglerContent_open +"") + .data("layoutRole", "togglerContent") + .data("layoutEdge", pane) + .addClass("content content-open") + .css("display","none") + .appendTo( $T ) + //.hover( addHover, removeHover ) // use ui-layout-toggler-west-hover .content-open instead! + ; + if (o.togglerContent_closed) // ui-layout-closed + $(""+ o.togglerContent_closed +"") + .data("layoutRole", "togglerContent") + .data("layoutEdge", pane) + .addClass("content content-closed") + .css("display","none") + .appendTo( $T ) + //.hover( addHover, removeHover ) // use ui-layout-toggler-west-hover .content-closed instead! + ; + // ADD TOGGLER.click/.hover + enableClosable(pane); + } + + // add Draggable events + initResizable(pane); + + // ADD CLASSNAMES & SLIDE-BINDINGS - eg: class="resizer resizer-west resizer-open" + if (s.isVisible) + setAsOpen(pane); // onOpen will be called, but NOT onResize + else { + setAsClosed(pane); // onClose will be called + bindStartSlidingEvent(pane, true); // will enable events IF option is set + } + + }); + + // SET ALL HANDLE DIMENSIONS + sizeHandles("all"); + } + + + /** + * Initialize scrolling ui-layout-content div - if exists + * + * @see initPane() - or externally after an Ajax injection + * @param {string} pane The pane to process + * @param {boolean=} resize Size content after init, default = true + */ +, initContent = function (pane, resize) { + if (!isInitialized()) return; + var + o = options[pane] + , sel = o.contentSelector + , $P = $Ps[pane] + , $C + ; + if (sel) $C = $Cs[pane] = (o.findNestedContent) + ? $P.find(sel).eq(0) // match 1-element only + : $P.children(sel).eq(0) + ; + if ($C && $C.length) { + // SAVE original Pane CSS + if (!$C.data("layoutCSS")) + $C.data("layoutCSS", elCSS($C, "height")); + $C.css( _c.content.cssReq ); + if (o.applyDemoStyles) { + $C.css( _c.content.cssDemo ); // add padding & overflow: auto to content-div + $P.css( _c.content.cssDemoPane ); // REMOVE padding/scrolling from pane + } + state[pane].content = {}; // init content state + if (resize !== false) sizeContent(pane); + // sizeContent() is called AFTER init of all elements + } + else + $Cs[pane] = false; + } + + + /** + * Searches for .ui-layout-button-xxx elements and auto-binds them as layout-buttons + * + * @see _create() + */ +, initButtons = function () { + var pre = "ui-layout-button-", name; + $.each("toggle,open,close,pin,toggle-slide,open-slide".split(","), function (i, action) { + $.each(_c.borderPanes.split(","), function (ii, pane) { + $("."+pre+action+"-"+pane).each(function(){ + // if button was previously 'bound', data.layoutName was set, but is blank if layout has no 'name' + name = $(this).data("layoutName") || $(this).attr("layoutName"); + if (name == undefined || name == options.name) + bindButton(this, action, pane); + }); + }); + }); + } + + /** + * Add resize-bars to all panes that specify it in options + * -dependancy: $.fn.resizable - will skip if not found + * + * @see _create() + * @param {string=} panes The edge(s) to process, blank = all + */ +, initResizable = function (panes) { + var + draggingAvailable = (typeof $.fn.draggable == "function") + , $Frames, side // set in start() + ; + if (!panes || panes == "all") panes = _c.borderPanes; + + $.each(panes.split(","), function (idx, pane) { + var + o = options[pane] + , s = state[pane] + , c = _c[pane] + , side = (c.dir=="horz" ? "top" : "left") + , r, live // set in start because may change + ; + if (!draggingAvailable || !$Ps[pane] || !o.resizable) { + o.resizable = false; + return true; // skip to next + } + + var + $P = $Ps[pane] + , $R = $Rs[pane] + , base = o.resizerClass + // 'drag' classes are applied to the ORIGINAL resizer-bar while dragging is in process + , resizerClass = base+"-drag" // resizer-drag + , resizerPaneClass = base+"-"+pane+"-drag" // resizer-north-drag + // 'helper' class is applied to the CLONED resizer-bar while it is being dragged + , helperClass = base+"-dragging" // resizer-dragging + , helperPaneClass = base+"-"+pane+"-dragging" // resizer-north-dragging + , helperLimitClass = base+"-dragging-limit" // resizer-drag + , helperPaneLimitClass = base+"-"+pane+"-dragging-limit" // resizer-north-drag + , helperClassesSet = false // logic var + ; + + if (!s.isClosed) + $R + .attr("title", o.resizerTip) + .css("cursor", o.resizerCursor) // n-resize, s-resize, etc + ; + + $R.bind("mouseenter."+ sID, onResizerEnter) + .bind("mouseleave."+ sID, onResizerLeave); + + $R.draggable({ + containment: $N[0] // limit resizing to layout container + , axis: (c.dir=="horz" ? "y" : "x") // limit resizing to horz or vert axis + , delay: 0 + , distance: 1 + // basic format for helper - style it using class: .ui-draggable-dragging + , helper: "clone" + , opacity: o.resizerDragOpacity + , addClasses: false // avoid ui-state-disabled class when disabled + //, iframeFix: o.draggableIframeFix // TODO: consider using when bug is fixed + , zIndex: _c.zIndex.resizer_drag + + , start: function (e, ui) { + // REFRESH options & state pointers in case we used swapPanes + o = options[pane]; + s = state[pane]; + // re-read options + live = o.resizeWhileDragging; + + // ondrag_start callback - will CANCEL hide if returns false + // TODO: dragging CANNOT be cancelled like this, so see if there is a way? + if (false === _execCallback(pane, o.ondrag_start)) return false; + + _c.isLayoutBusy = true; // used by sizePane() logic during a liveResize + s.isResizing = true; // prevent pane from closing while resizing + timer.clear(pane+"_closeSlider"); // just in case already triggered + + // SET RESIZER LIMITS - used in drag() + setSizeLimits(pane); // update pane/resizer state + r = s.resizerPosition; + + $R.addClass( resizerClass +" "+ resizerPaneClass ); // add drag classes + helperClassesSet = false; // reset logic var - see drag() + + // MASK PANES WITH IFRAMES OR OTHER TROUBLESOME ELEMENTS + $Frames = $(o.maskIframesOnResize === true ? "iframe" : o.maskIframesOnResize).filter(":visible"); + var id, i=0; // ID incrementer - used when 'resizing' masks during dynamic resizing + $Frames.each(function() { + id = "ui-layout-mask-"+ (++i); + $(this).data("layoutMaskID", id); // tag iframe with corresponding maskID + $('
') + .css({ + background: "#fff" + , opacity: "0.001" + , zIndex: _c.zIndex.iframe_mask + , position: "absolute" + , width: this.offsetWidth+"px" + , height: this.offsetHeight+"px" + }) + .css($(this).position()) // top & left -- changed from offset() + .appendTo(this.parentNode) // put mask-div INSIDE pane to avoid zIndex issues + ; + }); + + // DISABLE TEXT SELECTION (probably already done by resizer.mouseOver) + $('body').disableSelection(); + } + + , drag: function (e, ui) { + if (!helperClassesSet) { // can only add classes after clone has been added to the DOM + //$(".ui-draggable-dragging") + ui.helper + .addClass( helperClass +" "+ helperPaneClass ) // add helper classes + .css({ right: "auto", bottom: "auto" }) // fix dir="rtl" issue + .children().css("visibility","hidden") // hide toggler inside dragged resizer-bar + ; + helperClassesSet = true; + // draggable bug!? RE-SET zIndex to prevent E/W resize-bar showing through N/S pane! + if (s.isSliding) $Ps[pane].css("zIndex", _c.zIndex.pane_sliding); + } + // CONTAIN RESIZER-BAR TO RESIZING LIMITS + var limit = 0; + if (ui.position[side] < r.min) { + ui.position[side] = r.min; + limit = -1; + } + else if (ui.position[side] > r.max) { + ui.position[side] = r.max; + limit = 1; + } + // ADD/REMOVE dragging-limit CLASS + if (limit) { + ui.helper.addClass( helperLimitClass +" "+ helperPaneLimitClass ); // at dragging-limit + window.defaultStatus = "Panel has reached its " + + ((limit>0 && pane.match(/north|west/)) || (limit<0 && pane.match(/south|east/)) ? "maximum" : "minimum") +" size"; + } + else { + ui.helper.removeClass( helperLimitClass +" "+ helperPaneLimitClass ); // not at dragging-limit + window.defaultStatus = ""; + } + // DYNAMICALLY RESIZE PANES IF OPTION ENABLED + if (live) resizePanes(e, ui, pane); + } + + , stop: function (e, ui) { + $('body').enableSelection(); // RE-ENABLE TEXT SELECTION + window.defaultStatus = ""; // clear 'resizing limit' message from statusbar + $R.removeClass( resizerClass +" "+ resizerPaneClass ); // remove drag classes from Resizer + s.isResizing = false; + _c.isLayoutBusy = false; // set BEFORE resizePanes so other logic can pick it up + resizePanes(e, ui, pane, true); // true = resizingDone + } + + }); + + /** + * resizePanes + * + * Sub-routine called from stop() and optionally drag() + * + * @param {!Object} evt + * @param {!Object} ui + * @param {string} pane + * @param {boolean=} resizingDone + */ + var resizePanes = function (evt, ui, pane, resizingDone) { + var + dragPos = ui.position + , c = _c[pane] + , resizerPos, newSize + , i = 0 // ID incrementer + ; + switch (pane) { + case "north": resizerPos = dragPos.top; break; + case "west": resizerPos = dragPos.left; break; + case "south": resizerPos = sC.offsetHeight - dragPos.top - o.spacing_open; break; + case "east": resizerPos = sC.offsetWidth - dragPos.left - o.spacing_open; break; + }; + + if (resizingDone) { + // Remove OR Resize MASK(S) created in drag.start + $("div.ui-layout-mask").each(function() { this.parentNode.removeChild(this); }); + //$("div.ui-layout-mask").remove(); // TODO: Is this less efficient? + + // ondrag_start callback - will CANCEL hide if returns false + if (false === _execCallback(pane, o.ondrag_end || o.ondrag)) return false; + } + else + $Frames.each(function() { + $("#"+ $(this).data("layoutMaskID")) // get corresponding mask by ID + .css($(this).position()) // update top & left + .css({ // update width & height + width: this.offsetWidth +"px" + , height: this.offsetHeight+"px" + }) + ; + }); + + // remove container margin from resizer position to get the pane size + newSize = resizerPos - sC["inset"+ c.side]; + manualSizePane(pane, newSize); + } + }); + } + + + /** + * Destroy this layout and reset all elements + */ +, destroy = function () { + // UNBIND layout events and remove global object + $(window).unbind("."+ sID); + $(document).unbind("."+ sID); + + // loop all panes to remove layout classes, attributes and bindings + $.each(_c.allPanes.split(","), function (i, pane) { + removePane( pane, false, true ); // true = skipResize + }); + + // reset layout-container + $N .removeData("layout") + .removeData("layoutContainer") + .removeClass(options.containerClass) + ; + + // do NOT reset container CSS if is a 'pane' in an outer-layout - ie, THIS layout is 'nested' + if (!$N.data("layoutEdge") && $N.data("layoutCSS")) // RESET CSS + $N.css( $N.data("layoutCSS") ).removeData("layoutCSS"); + + // for full-page layouts, also reset the CSS + if (sC.tagName == "BODY" && ($N = $("html")).data("layoutCSS")) // RESET CSS + $N.css( $N.data("layoutCSS") ).removeData("layoutCSS"); + + // trigger state-management and onunload callback + unload(); + } + + /** + * Remove a pane from the layout - subroutine of destroy() + * + * @see destroy() + * @param {string} pane The pane to process + * @param {boolean=} remove Remove the DOM element? default = false + * @param {boolean=} skipResize Skip calling resizeAll()? default = false + */ +, removePane = function (pane, remove, skipResize) { + if (!isInitialized()) return; + if (!$Ps[pane]) return; // NO SUCH PANE + var + $P = $Ps[pane] + , $C = $Cs[pane] + , $R = $Rs[pane] + , $T = $Ts[pane] + // create list of ALL pane-classes that need to be removed + , _open = "-open" + , _sliding= "-sliding" + , _closed = "-closed" + , root = options[pane].paneClass // default="ui-layout-pane" + , pRoot = root +"-"+ pane // eg: "ui-layout-pane-west" + , classes = [ root, root+_open, root+_closed, root+_sliding, // generic classes + pRoot, pRoot+_open, pRoot+_closed, pRoot+_sliding ] // pane-specific classes + ; + $.merge(classes, getHoverClasses($P, true)); // ADD hover-classes + + if (!$P || !$P.length) { + } // pane has already been deleted! + else if (remove && !$P.data("layoutContainer") && (!$C || !$C.length || !$C.data("layoutContainer"))) + $P.remove(); + else { + $P .removeClass( classes.join(" ") ) // remove ALL pane-classes + .removeData("layoutParent") + .removeData("layoutRole") + .removeData("layoutEdge") + .removeData("autoHidden") // in case set + .unbind("."+ sID) // remove ALL Layout events + // TODO: remove these extra unbind commands when jQuery is fixed + //.unbind("mouseenter"+ sID) + //.unbind("mouseleave"+ sID) + ; + // do NOT reset CSS if this pane is STILL the container of a nested layout! + // the nested layout will reset its 'container' when/if it is destroyed + if (!$P.data("layoutContainer")) + $P.css( $P.data("layoutCSS") ).removeData("layoutCSS"); + // DITTO for the Content elem + if ($C && $C.length && !$C.data("layoutContainer")) + $C.css( $C.data("layoutCSS") ).removeData("layoutCSS"); + } + + // REMOVE pane resizer and toggler elements + if ($T && $T.length) $T.remove(); + if ($R && $R.length) $R.remove(); + + // CLEAR all pointers and data + $Ps[pane] = $Cs[pane] = $Rs[pane] = $Ts[pane] = false; + + // skip resize & state-clear when called from destroy() + if (!skipResize) { + resizeAll(); + state[pane] = {}; + } + } + + +/* + * ########################### + * ACTION METHODS + * ########################### + */ + + /** + * Completely 'hides' a pane, including its spacing - as if it does not exist + * The pane is not actually 'removed' from the source, so can use 'show' to un-hide it + * + * @param {string} pane The pane being hidden, ie: north, south, east, or west + * @param {boolean=} noAnimation + */ +, hide = function (pane, noAnimation) { + if (!isInitialized()) return; + var + o = options[pane] + , s = state[pane] + , $P = $Ps[pane] + , $R = $Rs[pane] + ; + if (!$P || s.isHidden) return; // pane does not exist OR is already hidden + + // onhide_start callback - will CANCEL hide if returns false + if (state.initialized && false === _execCallback(pane, o.onhide_start)) return; + + s.isSliding = false; // just in case + + // now hide the elements + if ($R) $R.hide(); // hide resizer-bar + if (!state.initialized || s.isClosed) { + s.isClosed = true; // to trigger open-animation on show() + s.isHidden = true; + s.isVisible = false; + $P.hide(); // no animation when loading page + sizeMidPanes(_c[pane].dir == "horz" ? "all" : "center"); + if (state.initialized || o.triggerEventsOnLoad) + _execCallback(pane, o.onhide_end || o.onhide); + } + else { + s.isHiding = true; // used by onclose + close(pane, false, noAnimation); // adjust all panes to fit + } + } + + /** + * Show a hidden pane - show as 'closed' by default unless openPane = true + * + * @param {string} pane The pane being opened, ie: north, south, east, or west + * @param {boolean=} openPane + * @param {boolean=} noAnimation + * @param {boolean=} noAlert + */ +, show = function (pane, openPane, noAnimation, noAlert) { + if (!isInitialized()) return; + var + o = options[pane] + , s = state[pane] + , $P = $Ps[pane] + , $R = $Rs[pane] + ; + if (!$P || !s.isHidden) return; // pane does not exist OR is not hidden + + // onshow_start callback - will CANCEL show if returns false + if (false === _execCallback(pane, o.onshow_start)) return; + + s.isSliding = false; // just in case + s.isShowing = true; // used by onopen/onclose + //s.isHidden = false; - will be set by open/close - if not cancelled + + // now show the elements + //if ($R) $R.show(); - will be shown by open/close + if (openPane === false) + close(pane, true); // true = force + else + open(pane, false, noAnimation, noAlert); // adjust all panes to fit + } + + + /** + * Toggles a pane open/closed by calling either open or close + * + * @param {string} pane The pane being toggled, ie: north, south, east, or west + * @param {boolean=} slide + */ +, toggle = function (pane, slide) { + if (!isInitialized()) return; + if (!isStr(pane)) { + pane.stopImmediatePropagation(); // pane = event + pane = $(this).data("layoutEdge"); // bound to $R.dblclick + } + var s = state[str(pane)]; + if (s.isHidden) + show(pane); // will call 'open' after unhiding it + else if (s.isClosed) + open(pane, !!slide); + else + close(pane); + } + + + /** + * Utility method used during init or other auto-processes + * + * @param {string} pane The pane being closed + * @param {boolean=} setHandles + */ +, _closePane = function (pane, setHandles) { + var + $P = $Ps[pane] + , s = state[pane] + ; + $P.hide(); + s.isClosed = true; + s.isVisible = false; + // UNUSED: if (setHandles) setAsClosed(pane, true); // true = force + } + + /** + * Close the specified pane (animation optional), and resize all other panes as needed + * + * @param {string} pane The pane being closed, ie: north, south, east, or west + * @param {boolean=} force + * @param {boolean=} noAnimation + * @param {boolean=} skipCallback + */ +, close = function (pane, force, noAnimation, skipCallback) { + if (!state.initialized && $Ps[pane]) { + _closePane(pane); // INIT pane as closed + return; + } + if (!isInitialized()) return; + var + $P = $Ps[pane] + , $R = $Rs[pane] + , $T = $Ts[pane] + , o = options[pane] + , s = state[pane] + , doFX = !noAnimation && !s.isClosed && (o.fxName_close != "none") + // transfer logic vars to temp vars + , isShowing = s.isShowing + , isHiding = s.isHiding + , wasSliding = s.isSliding + ; + // now clear the logic vars + delete s.isShowing; + delete s.isHiding; + + if (!$P || (!o.closable && !isShowing && !isHiding)) return; // invalid request // (!o.resizable && !o.closable) ??? + else if (!force && s.isClosed && !isShowing) return; // already closed + + if (_c.isLayoutBusy) { // layout is 'busy' - probably with an animation + _queue("close", pane, force); // set a callback for this action, if possible + return; // ABORT + } + + // onclose_start callback - will CANCEL hide if returns false + // SKIP if just 'showing' a hidden pane as 'closed' + if (!isShowing && false === _execCallback(pane, o.onclose_start)) return; + + // SET flow-control flags + _c[pane].isMoving = true; + _c.isLayoutBusy = true; + + s.isClosed = true; + s.isVisible = false; + // update isHidden BEFORE sizing panes + if (isHiding) s.isHidden = true; + else if (isShowing) s.isHidden = false; + + if (s.isSliding) // pane is being closed, so UNBIND trigger events + bindStopSlidingEvents(pane, false); // will set isSliding=false + else // resize panes adjacent to this one + sizeMidPanes(_c[pane].dir == "horz" ? "all" : "center", false); // false = NOT skipCallback + + // if this pane has a resizer bar, move it NOW - before animation + setAsClosed(pane); + + // CLOSE THE PANE + if (doFX) { // animate the close + lockPaneForFX(pane, true); // need to set left/top so animation will work + $P.hide( o.fxName_close, o.fxSettings_close, o.fxSpeed_close, function () { + lockPaneForFX(pane, false); // undo + close_2(); + }); + } + else { // hide the pane without animation + $P.hide(); + close_2(); + }; + + // SUBROUTINE + function close_2 () { + if (s.isClosed) { // make sure pane was not 'reopened' before animation finished! + + bindStartSlidingEvent(pane, true); // will enable if o.slidable = true + + // if opposite-pane was autoClosed, see if it can be autoOpened now + var altPane = _c.altSide[pane]; + if (state[ altPane ].noRoom) { + setSizeLimits( altPane ); + makePaneFit( altPane ); + } + + if (!skipCallback && (state.initialized || o.triggerEventsOnLoad)) { + // onclose callback - UNLESS just 'showing' a hidden pane as 'closed' + if (!isShowing) _execCallback(pane, o.onclose_end || o.onclose); + // onhide OR onshow callback + if (isShowing) _execCallback(pane, o.onshow_end || o.onshow); + if (isHiding) _execCallback(pane, o.onhide_end || o.onhide); + } + } + // execute internal flow-control callback + _dequeue(pane); + } + } + + /** + * @param {string} pane The pane just closed, ie: north, south, east, or west + */ +, setAsClosed = function (pane) { + var + $P = $Ps[pane] + , $R = $Rs[pane] + , $T = $Ts[pane] + , o = options[pane] + , s = state[pane] + , side = _c[pane].side.toLowerCase() + , inset = "inset"+ _c[pane].side + , rClass = o.resizerClass + , tClass = o.togglerClass + , _pane = "-"+ pane // used for classNames + , _open = "-open" + , _sliding= "-sliding" + , _closed = "-closed" + ; + $R + .css(side, sC[inset]) // move the resizer + .removeClass( rClass+_open +" "+ rClass+_pane+_open ) + .removeClass( rClass+_sliding +" "+ rClass+_pane+_sliding ) + .addClass( rClass+_closed +" "+ rClass+_pane+_closed ) + .unbind("dblclick."+ sID) + ; + // DISABLE 'resizing' when closed - do this BEFORE bindStartSlidingEvent? + if (o.resizable && typeof $.fn.draggable == "function") + $R + .draggable("disable") + .removeClass("ui-state-disabled") // do NOT apply disabled styling - not suitable here + .css("cursor", "default") + .attr("title","") + ; + + // if pane has a toggler button, adjust that too + if ($T) { + $T + .removeClass( tClass+_open +" "+ tClass+_pane+_open ) + .addClass( tClass+_closed +" "+ tClass+_pane+_closed ) + .attr("title", o.togglerTip_closed) // may be blank + ; + // toggler-content - if exists + $T.children(".content-open").hide(); + $T.children(".content-closed").css("display","block"); + } + + // sync any 'pin buttons' + syncPinBtns(pane, false); + + if (state.initialized) { + // resize 'length' and position togglers for adjacent panes + sizeHandles("all"); + } + } + + /** + * Open the specified pane (animation optional), and resize all other panes as needed + * + * @param {string} pane The pane being opened, ie: north, south, east, or west + * @param {boolean=} slide + * @param {boolean=} noAnimation + * @param {boolean=} noAlert + */ +, open = function (pane, slide, noAnimation, noAlert) { + if (!isInitialized()) return; + var + $P = $Ps[pane] + , $R = $Rs[pane] + , $T = $Ts[pane] + , o = options[pane] + , s = state[pane] + , doFX = !noAnimation && s.isClosed && (o.fxName_open != "none") + // transfer logic var to temp var + , isShowing = s.isShowing + ; + // now clear the logic var + delete s.isShowing; + + if (!$P || (!o.resizable && !o.closable && !isShowing)) return; // invalid request + else if (s.isVisible && !s.isSliding) return; // already open + + // pane can ALSO be unhidden by just calling show(), so handle this scenario + if (s.isHidden && !isShowing) { + show(pane, true); + return; + } + + if (_c.isLayoutBusy) { // layout is 'busy' - probably with an animation + _queue("open", pane, slide); // set a callback for this action, if possible + return; // ABORT + } + + setSizeLimits(pane, slide); // update pane-state + + // onopen_start callback - will CANCEL hide if returns false + if (false === _execCallback(pane, o.onopen_start)) return; + + // make sure there is enough space available to open the pane + setSizeLimits(pane, slide); // update pane-state + if (s.minSize > s.maxSize) { // INSUFFICIENT ROOM FOR PANE TO OPEN! + syncPinBtns(pane, false); // make sure pin-buttons are reset + if (!noAlert && o.noRoomToOpenTip) + alert(o.noRoomToOpenTip); + return; // ABORT + } + + // SET flow-control flags + _c[pane].isMoving = true; + _c.isLayoutBusy = true; + + if (slide) // START Sliding - will set isSliding=true + bindStopSlidingEvents(pane, true); // BIND trigger events to close sliding-pane + else if (s.isSliding) // PIN PANE (stop sliding) - open pane 'normally' instead + bindStopSlidingEvents(pane, false); // UNBIND trigger events - will set isSliding=false + else if (o.slidable) + bindStartSlidingEvent(pane, false); // UNBIND trigger events + + s.noRoom = false; // will be reset by makePaneFit if 'noRoom' + makePaneFit(pane); + + s.isVisible = true; + s.isClosed = false; + // update isHidden BEFORE sizing panes - WHY??? Old? + if (isShowing) s.isHidden = false; + + if (doFX) { // ANIMATE + lockPaneForFX(pane, true); // need to set left/top so animation will work + $P.show( o.fxName_open, o.fxSettings_open, o.fxSpeed_open, function() { + lockPaneForFX(pane, false); // undo + open_2(); // continue + }); + } + else {// no animation + $P.show(); // just show pane and... + open_2(); // continue + }; + + // SUBROUTINE + function open_2 () { + if (s.isVisible) { // make sure pane was not closed or hidden before animation finished! + + // cure iframe display issues + _fixIframe(pane); + + // NOTE: if isSliding, then other panes are NOT 'resized' + if (!s.isSliding) // resize all panes adjacent to this one + sizeMidPanes(_c[pane].dir=="vert" ? "center" : "all", false); // false = NOT skipCallback + + // set classes, position handles and execute callbacks... + setAsOpen(pane); + } + + // internal flow-control callback + _dequeue(pane); + }; + + } + + /** + * @param {string} pane The pane just opened, ie: north, south, east, or west + * @param {boolean=} skipCallback + */ +, setAsOpen = function (pane, skipCallback) { + var + $P = $Ps[pane] + , $R = $Rs[pane] + , $T = $Ts[pane] + , o = options[pane] + , s = state[pane] + , side = _c[pane].side.toLowerCase() + , inset = "inset"+ _c[pane].side + , rClass = o.resizerClass + , tClass = o.togglerClass + , _pane = "-"+ pane // used for classNames + , _open = "-open" + , _closed = "-closed" + , _sliding= "-sliding" + ; + $R + .css(side, sC[inset] + getPaneSize(pane)) // move the resizer + .removeClass( rClass+_closed +" "+ rClass+_pane+_closed ) + .addClass( rClass+_open +" "+ rClass+_pane+_open ) + ; + if (s.isSliding) + $R.addClass( rClass+_sliding +" "+ rClass+_pane+_sliding ) + else // in case 'was sliding' + $R.removeClass( rClass+_sliding +" "+ rClass+_pane+_sliding ) + + if (o.resizerDblClickToggle) + $R.bind("dblclick", toggle ); + removeHover( 0, $R ); // remove hover classes + if (o.resizable && typeof $.fn.draggable == "function") + $R + .draggable("enable") + .css("cursor", o.resizerCursor) + .attr("title", o.resizerTip) + ; + else if (!s.isSliding) + $R.css("cursor", "default"); // n-resize, s-resize, etc + + // if pane also has a toggler button, adjust that too + if ($T) { + $T + .removeClass( tClass+_closed +" "+ tClass+_pane+_closed ) + .addClass( tClass+_open +" "+ tClass+_pane+_open ) + .attr("title", o.togglerTip_open) // may be blank + ; + removeHover( 0, $T ); // remove hover classes + // toggler-content - if exists + $T.children(".content-closed").hide(); + $T.children(".content-open").css("display","block"); + } + + // sync any 'pin buttons' + syncPinBtns(pane, !s.isSliding); + + // update pane-state dimensions - BEFORE resizing content + $.extend(s, elDims($P)); + + if (state.initialized) { + // resize resizer & toggler sizes for all panes + sizeHandles("all"); + // resize content every time pane opens - to be sure + sizeContent(pane, true); // true = remeasure headers/footers, even if 'isLayoutBusy' + } + + if (!skipCallback && (state.initialized || o.triggerEventsOnLoad) && $P.is(":visible")) { + // onopen callback + _execCallback(pane, o.onopen_end || o.onopen); + // onshow callback - TODO: should this be here? + if (s.isShowing) _execCallback(pane, o.onshow_end || o.onshow); + // ALSO call onresize because layout-size *may* have changed while pane was closed + if (state.initialized) { + _execCallback(pane, o.onresize_end || o.onresize); + resizeNestedLayout(pane); + } + } + } + + + /** + * slideOpen / slideClose / slideToggle + * + * Pass-though methods for sliding + */ +, slideOpen = function (evt_or_pane) { + if (!isInitialized()) return; + var + evt = isStr(evt_or_pane) ? null : evt_or_pane + , pane = evt ? $(this).data("layoutEdge") : evt_or_pane + , s = state[pane] + , delay = options[pane].slideDelay_open + ; + // prevent event from triggering on NEW resizer binding created below + if (evt) evt.stopImmediatePropagation(); + + if (s.isClosed && evt && evt.type == "mouseenter" && delay > 0) + // trigger = mouseenter - use a delay + timer.set(pane+"_openSlider", open_NOW, delay); + else + open_NOW(); // will unbind events if is already open + + /** + * SUBROUTINE for timed open + */ + function open_NOW (evt) { + if (!s.isClosed) // skip if no longer closed! + bindStopSlidingEvents(pane, true); // BIND trigger events to close sliding-pane + else if (!_c[pane].isMoving) + open(pane, true); // true = slide - open() will handle binding + }; + } + +, slideClose = function (evt_or_pane) { + if (!isInitialized()) return; + var + evt = isStr(evt_or_pane) ? null : evt_or_pane + , pane = evt ? $(this).data("layoutEdge") : evt_or_pane + , o = options[pane] + , s = state[pane] + , delay = _c[pane].isMoving ? 1000 : 300 // MINIMUM delay - option may override + ; + + if (s.isClosed || s.isResizing) + return; // skip if already closed OR in process of resizing + else if (o.slideTrigger_close == "click") + close_NOW(); // close immediately onClick + else if (o.preventQuickSlideClose && _c.isLayoutBusy) + return; // handle Chrome quick-close on slide-open + else if (o.preventPrematureSlideClose && evt && $.layout.isMouseOverElem(evt, $Ps[pane])) + return; // handle incorrect mouseleave trigger, like when over a SELECT-list in IE + else if (evt) // trigger = mouseleave - use a delay + // 1 sec delay if 'opening', else .3 sec + timer.set(pane+"_closeSlider", close_NOW, max(o.slideDelay_close, delay)); + else // called programically + close_NOW(); + + /** + * SUBROUTINE for timed close + */ + function close_NOW () { + if (s.isClosed) // skip 'close' if already closed! + bindStopSlidingEvents(pane, false); // UNBIND trigger events - TODO: is this needed here? + else if (!_c[pane].isMoving) + close(pane); // close will handle unbinding + }; + } + +, slideToggle = function (pane) { toggle(pane, true); } + + + /** + * Must set left/top on East/South panes so animation will work properly + * + * @param {string} pane The pane to lock, 'east' or 'south' - any other is ignored! + * @param {boolean} doLock true = set left/top, false = remove + */ +, lockPaneForFX = function (pane, doLock) { + var $P = $Ps[pane]; + if (doLock) { + $P.css({ zIndex: _c.zIndex.pane_animate }); // overlay all elements during animation + if (pane=="south") + $P.css({ top: sC.insetTop + sC.innerHeight - $P.outerHeight() }); + else if (pane=="east") + $P.css({ left: sC.insetLeft + sC.innerWidth - $P.outerWidth() }); + } + else { // animation DONE - RESET CSS + // TODO: see if this can be deleted. It causes a quick-close when sliding in Chrome + $P.css({ zIndex: (state[pane].isSliding ? _c.zIndex.pane_sliding : _c.zIndex.pane_normal) }); + if (pane=="south") + $P.css({ top: "auto" }); + else if (pane=="east") + $P.css({ left: "auto" }); + // fix anti-aliasing in IE - only needed for animations that change opacity + var o = options[pane]; + if ($.layout.browser.msie && o.fxOpacityFix && o.fxName_open != "slide" && $P.css("filter") && $P.css("opacity") == 1) + $P[0].style.removeAttribute('filter'); + } + } + + + /** + * Toggle sliding functionality of a specific pane on/off by adding removing 'slide open' trigger + * + * @see open(), close() + * @param {string} pane The pane to enable/disable, 'north', 'south', etc. + * @param {boolean} enable Enable or Disable sliding? + */ +, bindStartSlidingEvent = function (pane, enable) { + var + o = options[pane] + , $P = $Ps[pane] + , $R = $Rs[pane] + , trigger = o.slideTrigger_open.toLowerCase() + ; + if (!$R || (enable && !o.slidable)) return; + + // make sure we have a valid event + if (trigger.match(/mouseover/)) + trigger = o.slideTrigger_open = "mouseenter"; + else if (!trigger.match(/click|dblclick|mouseenter/)) + trigger = o.slideTrigger_open = "click"; + + $R + // add or remove trigger event + [enable ? "bind" : "unbind"](trigger +'.'+ sID, slideOpen) + // set the appropriate cursor & title/tip + .css("cursor", enable ? o.sliderCursor : "default") + .attr("title", enable ? o.sliderTip : "") + ; + } + + /** + * Add or remove 'mouseleave' events to 'slide close' when pane is 'sliding' open or closed + * Also increases zIndex when pane is sliding open + * See bindStartSlidingEvent for code to control 'slide open' + * + * @see slideOpen(), slideClose() + * @param {string} pane The pane to process, 'north', 'south', etc. + * @param {boolean} enable Enable or Disable events? + */ +, bindStopSlidingEvents = function (pane, enable) { + var + o = options[pane] + , s = state[pane] + , z = _c.zIndex + , trigger = o.slideTrigger_close.toLowerCase() + , action = (enable ? "bind" : "unbind") + , $P = $Ps[pane] + , $R = $Rs[pane] + ; + s.isSliding = enable; // logic + timer.clear(pane+"_closeSlider"); // just in case + + // remove 'slideOpen' trigger event from resizer + // ALSO will raise the zIndex of the pane & resizer + if (enable) bindStartSlidingEvent(pane, false); + + // RE/SET zIndex - increases when pane is sliding-open, resets to normal when not + $P.css("zIndex", enable ? z.pane_sliding : z.pane_normal); + $R.css("zIndex", enable ? z.pane_sliding : z.resizer_normal); + + // make sure we have a valid event + if (!trigger.match(/click|mouseleave/)) + trigger = o.slideTrigger_close = "mouseleave"; // also catches 'mouseout' + + // add/remove slide triggers + $R[action](trigger, slideClose); // base event on resize + // need extra events for mouseleave + if (trigger == "mouseleave") { + // also close on pane.mouseleave + $P[action]("mouseleave."+ sID, slideClose); + // cancel timer when mouse moves between 'pane' and 'resizer' + $R[action]("mouseenter."+ sID, cancelMouseOut); + $P[action]("mouseenter."+ sID, cancelMouseOut); + } + + if (!enable) + timer.clear(pane+"_closeSlider"); + else if (trigger == "click" && !o.resizable) { + // IF pane is not resizable (which already has a cursor and tip) + // then set the a cursor & title/tip on resizer when sliding + $R.css("cursor", enable ? o.sliderCursor : "default"); + $R.attr("title", enable ? o.togglerTip_open : ""); // use Toggler-tip, eg: "Close Pane" + } + + // SUBROUTINE for mouseleave timer clearing + function cancelMouseOut (evt) { + timer.clear(pane+"_closeSlider"); + evt.stopPropagation(); + } + } + + + /** + * Hides/closes a pane if there is insufficient room - reverses this when there is room again + * MUST have already called setSizeLimits() before calling this method + * + * @param {string} pane The pane being resized + * @param {boolean=} isOpening Called from onOpen? + * @param {boolean=} skipCallback Should the onresize callback be run? + * @param {boolean=} force + */ +, makePaneFit = function (pane, isOpening, skipCallback, force) { + var + o = options[pane] + , s = state[pane] + , c = _c[pane] + , $P = $Ps[pane] + , $R = $Rs[pane] + , isSidePane = c.dir=="vert" + , hasRoom = false + ; + + // special handling for center & east/west panes + if (pane == "center" || (isSidePane && s.noVerticalRoom)) { + // see if there is enough room to display the pane + // ERROR: hasRoom = s.minHeight <= s.maxHeight && (isSidePane || s.minWidth <= s.maxWidth); + hasRoom = (s.maxHeight > 0); + if (hasRoom && s.noRoom) { // previously hidden due to noRoom, so show now + $P.show(); + if ($R) $R.show(); + s.isVisible = true; + s.noRoom = false; + if (isSidePane) s.noVerticalRoom = false; + _fixIframe(pane); + } + else if (!hasRoom && !s.noRoom) { // not currently hidden, so hide now + $P.hide(); + if ($R) $R.hide(); + s.isVisible = false; + s.noRoom = true; + } + } + + // see if there is enough room to fit the border-pane + if (pane == "center") { + // ignore center in this block + } + else if (s.minSize <= s.maxSize) { // pane CAN fit + hasRoom = true; + if (s.size > s.maxSize) // pane is too big - shrink it + sizePane(pane, s.maxSize, skipCallback, force); + else if (s.size < s.minSize) // pane is too small - enlarge it + sizePane(pane, s.minSize, skipCallback, force); + else if ($R && $P.is(":visible")) { + // make sure resizer-bar is positioned correctly + // handles situation where nested layout was 'hidden' when initialized + var + side = c.side.toLowerCase() + , pos = s.size + sC["inset"+ c.side] + ; + if ($.layout.cssNum($R, side) != pos) $R.css( side, pos ); + } + + // if was previously hidden due to noRoom, then RESET because NOW there is room + if (s.noRoom) { + // s.noRoom state will be set by open or show + if (s.wasOpen && o.closable) { + if (o.autoReopen) + open(pane, false, true, true); // true = noAnimation, true = noAlert + else // leave the pane closed, so just update state + s.noRoom = false; + } + else + show(pane, s.wasOpen, true, true); // true = noAnimation, true = noAlert + } + } + else { // !hasRoom - pane CANNOT fit + if (!s.noRoom) { // pane not set as noRoom yet, so hide or close it now... + s.noRoom = true; // update state + s.wasOpen = !s.isClosed && !s.isSliding; + if (s.isClosed){} // SKIP + else if (o.closable) // 'close' if possible + close(pane, true, true); // true = force, true = noAnimation + else // 'hide' pane if cannot just be closed + hide(pane, true); // true = noAnimation + } + } + } + + + /** + * sizePane / manualSizePane + * sizePane is called only by internal methods whenever a pane needs to be resized + * manualSizePane is an exposed flow-through method allowing extra code when pane is 'manually resized' + * + * @param {string} pane The pane being resized + * @param {number} size The *desired* new size for this pane - will be validated + * @param {boolean=} skipCallback Should the onresize callback be run? + */ +, manualSizePane = function (pane, size, skipCallback) { + if (!isInitialized()) return; + // ANY call to sizePane will disabled autoResize + var + o = options[pane] + // if resizing callbacks have been delayed and resizing is now DONE, force resizing to complete... + , forceResize = o.resizeWhileDragging && !_c.isLayoutBusy // && !o.triggerEventsWhileDragging + ; + o.autoResize = false; + // flow-through... + sizePane(pane, size, skipCallback, forceResize); + } + + /** + * @param {string} pane The pane being resized + * @param {number} size The *desired* new size for this pane - will be validated + * @param {boolean=} skipCallback Should the onresize callback be run? + * @param {boolean=} force Force resizing even if does not seem necessary + */ +, sizePane = function (pane, size, skipCallback, force) { + if (!isInitialized()) return; + var + o = options[pane] + , s = state[pane] + , $P = $Ps[pane] + , $R = $Rs[pane] + , side = _c[pane].side.toLowerCase() + , dimName = _c[pane].sizeType.toLowerCase() + , inset = "inset"+ _c[pane].side + , skipResizeWhileDragging = _c.isLayoutBusy && !o.triggerEventsWhileDragging + , oldSize + ; + // calculate 'current' min/max sizes + setSizeLimits(pane); // update pane-state + oldSize = s.size; + + size = _parseSize(pane, size); // handle percentages & auto + size = max(size, _parseSize(pane, o.minSize)); + size = min(size, s.maxSize); + if (size < s.minSize) { // not enough room for pane! + makePaneFit(pane, false, skipCallback); // will hide or close pane + return; + } + + // IF newSize is same as oldSize, then nothing to do - abort + if (!force && size == oldSize) return; + + // onresize_start callback CANNOT cancel resizing because this would break the layout! + if (!skipCallback && state.initialized && s.isVisible) + _execCallback(pane, o.onresize_start); + + // resize the pane, and make sure its visible + $P.css( dimName, max(1, cssSize(pane, size)) ); + +/* +var + edge = _c[pane].sizeType.toLowerCase() +, test = [{ + target: size + , attempt: size + , actual: edge=='width' ? $P.outerWidth() : $P.outerHeight() + }] +, lastTest = test[0] +, thisTest = {} +; +while (lastTest.actual != size) { + test.push( {} ); + thisTest = test[ test.length - 1 ]; + + if (lastTest.actual > size) + thisTest.attempt = Math.max(1, lastTest.attempt - (lastTest.actual - size)); + else // lastTest.actual < size + thisTest.attempt = Math.max(1, lastTest.attempt + (size - lastTest.actual)); + + $P.css( edge, cssSize(pane, thisTest.attempt) ); + + thisTest.actual = edge=='width' ? $P.outerWidth() : $P.outerHeight() + + // after 3 tries, is as close as its gonna get! + if (test.length == 3) break; + else lastTest = thisTest; +} +debugData( test, pane ); +*/ + + // update pane-state dimensions + s.size = size; + $.extend(s, elDims($P)); + + // reposition the resizer-bar + if ($R && $P.is(":visible")) $R.css( side, size + sC[inset] ); + + sizeContent(pane); + + if (!skipCallback && !skipResizeWhileDragging && state.initialized && s.isVisible) { + _execCallback(pane, o.onresize_end || o.onresize); + resizeNestedLayout(pane); + } + + // resize all the adjacent panes, and adjust their toggler buttons + // when skipCallback passed, it means the controlling method will handle 'other panes' + if (!skipCallback) { + // also no callback if live-resize is in progress and NOT triggerEventsWhileDragging + if (!s.isSliding) sizeMidPanes(_c[pane].dir=="horz" ? "all" : "center", skipResizeWhileDragging, force); + sizeHandles("all"); + } + + // if opposite-pane was autoClosed, see if it can be autoOpened now + var altPane = _c.altSide[pane]; + if (size < oldSize && state[ altPane ].noRoom) { + setSizeLimits( altPane ); + makePaneFit( altPane, false, skipCallback ); + } + } + + /** + * @see initPanes(), sizePane(), resizeAll(), open(), close(), hide() + * @param {string} panes The pane(s) being resized, comma-delmited string + * @param {boolean=} skipCallback Should the onresize callback be run? + * @param {boolean=} force + */ +, sizeMidPanes = function (panes, skipCallback, force) { + if (!panes || panes == "all") panes = "east,west,center"; + + $.each(panes.split(","), function (i, pane) { + if (!$Ps[pane]) return; // NO PANE - skip + var + o = options[pane] + , s = state[pane] + , $P = $Ps[pane] + , $R = $Rs[pane] + , isCenter= (pane=="center") + , hasRoom = true + , CSS = {} + , newCenter = calcNewCenterPaneDims() + ; + // update pane-state dimensions + $.extend(s, elDims($P)); + + if (pane == "center") { + if (!force && s.isVisible && newCenter.width == s.outerWidth && newCenter.height == s.outerHeight) + return true; // SKIP - pane already the correct size + // set state for makePaneFit() logic + $.extend(s, cssMinDims(pane), { + maxWidth: newCenter.width + , maxHeight: newCenter.height + }); + CSS = newCenter; + // convert OUTER width/height to CSS width/height + CSS.width = cssW(pane, CSS.width); + CSS.height = cssH(pane, CSS.height); + hasRoom = CSS.width > 0 && CSS.height > 0; + // during layout init, try to shrink east/west panes to make room for center + if (!hasRoom && !state.initialized && o.minWidth > 0) { + var + reqPx = o.minWidth - s.outerWidth + , minE = options.east.minSize || 0 + , minW = options.west.minSize || 0 + , sizeE = state.east.size + , sizeW = state.west.size + , newE = sizeE + , newW = sizeW + ; + if (reqPx > 0 && state.east.isVisible && sizeE > minE) { + newE = max( sizeE-minE, sizeE-reqPx ); + reqPx -= sizeE-newE; + } + if (reqPx > 0 && state.west.isVisible && sizeW > minW) { + newW = max( sizeW-minW, sizeW-reqPx ); + reqPx -= sizeW-newW; + } + // IF we found enough extra space, then resize the border panes as calculated + if (reqPx == 0) { + if (sizeE != minE) + sizePane('east', newE, true); // true = skipCallback - initPanes will handle when done + if (sizeW != minW) + sizePane('west', newW, true); + // now start over! + sizeMidPanes('center', skipCallback, force); + return; // abort this loop + } + } + } + else { // for east and west, set only the height, which is same as center height + // set state.min/maxWidth/Height for makePaneFit() logic + if (s.isVisible && !s.noVerticalRoom) + $.extend(s, elDims($P), cssMinDims(pane)) + if (!force && !s.noVerticalRoom && newCenter.height == s.outerHeight) + return true; // SKIP - pane already the correct size + // east/west have same top, bottom & height as center + CSS.top = newCenter.top; + CSS.bottom = newCenter.bottom; + CSS.height = cssH(pane, newCenter.height); + s.maxHeight = max(0, CSS.height); + hasRoom = (s.maxHeight > 0); + if (!hasRoom) s.noVerticalRoom = true; // makePaneFit() logic + } + + if (hasRoom) { + // resizeAll passes skipCallback because it triggers callbacks after ALL panes are resized + if (!skipCallback && state.initialized) + _execCallback(pane, o.onresize_start); + + $P.css(CSS); // apply the CSS to pane + if (s.noRoom && !s.isClosed && !s.isHidden) + makePaneFit(pane); // will re-open/show auto-closed/hidden pane + if (s.isVisible) { + $.extend(s, elDims($P)); // update pane dimensions + if (state.initialized) sizeContent(pane); // also resize the contents, if exists + } + } + else if (!s.noRoom && s.isVisible) // no room for pane + makePaneFit(pane); // will hide or close pane + + if (!s.isVisible) + return true; // DONE - next pane + + /* + * Extra CSS for IE6 or IE7 in Quirks-mode - add 'width' to NORTH/SOUTH panes + * Normally these panes have only 'left' & 'right' positions so pane auto-sizes + * ALSO required when pane is an IFRAME because will NOT default to 'full width' + */ + if (pane == "center") { // finished processing midPanes + var b = $.layout.browser; + var fix = b.isIE6 || (b.msie && !b.boxModel); + if ($Ps.north && (fix || state.north.tagName=="IFRAME")) + $Ps.north.css("width", cssW($Ps.north, sC.innerWidth)); + if ($Ps.south && (fix || state.south.tagName=="IFRAME")) + $Ps.south.css("width", cssW($Ps.south, sC.innerWidth)); + } + + // resizeAll passes skipCallback because it triggers callbacks after ALL panes are resized + if (!skipCallback && state.initialized) { + _execCallback(pane, o.onresize_end || o.onresize); + resizeNestedLayout(pane); + } + }); + } + + + /** + * @see window.onresize(), callbacks or custom code + */ +, resizeAll = function () { + if (!state.initialized) { + _initLayoutElements(); + return; // no need to resize since we just initialized! + } + var oldW = sC.innerWidth + , oldH = sC.innerHeight + ; + // cannot size layout when 'container' is hidden or collapsed + if (!$N.is(":visible:") ) return; + $.extend( state.container, elDims( $N ) ); // UPDATE container dimensions + if (!sC.outerHeight) return; + + // onresizeall_start will CANCEL resizing if returns false + // state.container has already been set, so user can access this info for calcuations + if (false === _execCallback(null, options.onresizeall_start)) return false; + + var // see if container is now 'smaller' than before + shrunkH = (sC.innerHeight < oldH) + , shrunkW = (sC.innerWidth < oldW) + , $P, o, s, dir + ; + // NOTE special order for sizing: S-N-E-W + $.each(["south","north","east","west"], function (i, pane) { + if (!$Ps[pane]) return; // no pane - SKIP + s = state[pane]; + o = options[pane]; + dir = _c[pane].dir; + + if (o.autoResize && s.size != o.size) // resize pane to original size set in options + sizePane(pane, o.size, true, true); // true=skipCallback, true=forceResize + else { + setSizeLimits(pane); + makePaneFit(pane, false, true, true); // true=skipCallback, true=forceResize + } + }); + + sizeMidPanes("all", true, true); // true=skipCallback, true=forceResize + sizeHandles("all"); // reposition the toggler elements + + // trigger all individual pane callbacks AFTER layout has finished resizing + o = options; // reuse alias + $.each(_c.allPanes.split(","), function (i, pane) { + $P = $Ps[pane]; + if (!$P) return; // SKIP + if (state[pane].isVisible) { // undefined for non-existent panes + _execCallback(pane, o[pane].onresize_end || o[pane].onresize); // callback - if exists + resizeNestedLayout(pane); + } + }); + + _execCallback(null, o.onresizeall_end || o.onresizeall); // onresizeall callback, if exists + } + + + /** + * Whenever a pane resizes or opens that has a nested layout, trigger resizeAll + * + * @param {string} pane The pane just resized or opened + */ +, resizeNestedLayout = function (pane) { + var + $P = $Ps[pane] + , $C = $Cs[pane] + , d = "layoutContainer" + ; + if (options[pane].resizeNestedLayout) { + if ($P.data( d )) + $P.layout().resizeAll(); + else if ($C && $C.data( d )) + $C.layout().resizeAll(); + } + } + + + /** + * IF pane has a content-div, then resize all elements inside pane to fit pane-height + * + * @param {string=} panes The pane(s) being resized + * @param {boolean=} remeasure Should the content (header/footer) be remeasured? + */ +, sizeContent = function (panes, remeasure) { + if (!isInitialized()) return; + if (!panes || panes == "all") panes = _c.allPanes; + $.each(panes.split(","), function (idx, pane) { + var + $P = $Ps[pane] + , $C = $Cs[pane] + , o = options[pane] + , s = state[pane] + , m = s.content // m = measurements + ; + if (!$P || !$C || !$P.is(":visible")) return true; // NOT VISIBLE - skip + + // onsizecontent_start will CANCEL resizing if returns false + if (false === _execCallback(null, o.onsizecontent_start)) return; + + // skip re-measuring offsets if live-resizing + if (!_c.isLayoutBusy || m.top == undefined || remeasure || o.resizeContentWhileDragging) { + _measure(); + // if any footers are below pane-bottom, they may not measure correctly, + // so allow pane overflow and re-measure + if (m.hiddenFooters > 0 && $P.css("overflow") == "hidden") { + $P.css("overflow", "visible"); + _measure(); // remeasure while overflowing + $P.css("overflow", "hidden"); + } + } + // NOTE: spaceAbove/Below *includes* the pane paddingTop/Bottom, but not pane.borders + var newH = s.innerHeight - (m.spaceAbove - s.css.paddingTop) - (m.spaceBelow - s.css.paddingBottom); + if (!$C.is(":visible") || m.height != newH) { + // size the Content element to fit new pane-size - will autoHide if not enough room + setOuterHeight($C, newH, true); // true=autoHide + m.height = newH; // save new height + }; + + if (state.initialized) { + _execCallback(pane, o.onsizecontent_end || o.onsizecontent); + resizeNestedLayout(pane); + } + + + function _below ($E) { + return max(s.css.paddingBottom, (parseInt($E.css("marginBottom"), 10) || 0)); + }; + + function _measure () { + var + ignore = options[pane].contentIgnoreSelector + , $Fs = $C.nextAll().not(ignore || ':lt(0)') // not :lt(0) = ALL + , $Fs_vis = $Fs.filter(':visible') + , $F = $Fs_vis.filter(':last') + ; + m = { + top: $C[0].offsetTop + , height: $C.outerHeight() + , numFooters: $Fs.length + , hiddenFooters: $Fs.length - $Fs_vis.length + , spaceBelow: 0 // correct if no content footer ($E) + } + m.spaceAbove = m.top; // just for state - not used in calc + m.bottom = m.top + m.height; + if ($F.length) + //spaceBelow = (LastFooter.top + LastFooter.height) [footerBottom] - Content.bottom + max(LastFooter.marginBottom, pane.paddingBotom) + m.spaceBelow = ($F[0].offsetTop + $F.outerHeight()) - m.bottom + _below($F); + else // no footer - check marginBottom on Content element itself + m.spaceBelow = _below($C); + }; + }); + } + + + /** + * Called every time a pane is opened, closed, or resized to slide the togglers to 'center' and adjust their length if necessary + * + * @see initHandles(), open(), close(), resizeAll() + * @param {string=} panes The pane(s) being resized + */ +, sizeHandles = function (panes) { + if (!panes || panes == "all") panes = _c.borderPanes; + + $.each(panes.split(","), function (i, pane) { + var + o = options[pane] + , s = state[pane] + , $P = $Ps[pane] + , $R = $Rs[pane] + , $T = $Ts[pane] + , $TC + ; + if (!$P || !$R) return; + + var + dir = _c[pane].dir + , _state = (s.isClosed ? "_closed" : "_open") + , spacing = o["spacing"+ _state] + , togAlign = o["togglerAlign"+ _state] + , togLen = o["togglerLength"+ _state] + , paneLen + , offset + , CSS = {} + ; + + if (spacing == 0) { + $R.hide(); + return; + } + else if (!s.noRoom && !s.isHidden) // skip if resizer was hidden for any reason + $R.show(); // in case was previously hidden + + // Resizer Bar is ALWAYS same width/height of pane it is attached to + if (dir == "horz") { // north/south + paneLen = $P.outerWidth(); // s.outerWidth || + s.resizerLength = paneLen; + $R.css({ + width: max(1, cssW($R, paneLen)) // account for borders & padding + , height: max(0, cssH($R, spacing)) // ditto + , left: $.layout.cssNum($P, "left") + }); + } + else { // east/west + paneLen = $P.outerHeight(); // s.outerHeight || + s.resizerLength = paneLen; + $R.css({ + height: max(1, cssH($R, paneLen)) // account for borders & padding + , width: max(0, cssW($R, spacing)) // ditto + , top: sC.insetTop + getPaneSize("north", true) // TODO: what if no North pane? + //, top: $.layout.cssNum($Ps["center"], "top") + }); + } + + // remove hover classes + removeHover( o, $R ); + + if ($T) { + if (togLen == 0 || (s.isSliding && o.hideTogglerOnSlide)) { + $T.hide(); // always HIDE the toggler when 'sliding' + return; + } + else + $T.show(); // in case was previously hidden + + if (!(togLen > 0) || togLen == "100%" || togLen > paneLen) { + togLen = paneLen; + offset = 0; + } + else { // calculate 'offset' based on options.PANE.togglerAlign_open/closed + if (isStr(togAlign)) { + switch (togAlign) { + case "top": + case "left": offset = 0; + break; + case "bottom": + case "right": offset = paneLen - togLen; + break; + case "middle": + case "center": + default: offset = Math.floor((paneLen - togLen) / 2); // 'default' catches typos + } + } + else { // togAlign = number + var x = parseInt(togAlign, 10); // + if (togAlign >= 0) offset = x; + else offset = paneLen - togLen + x; // NOTE: x is negative! + } + } + + if (dir == "horz") { // north/south + var width = cssW($T, togLen); + $T.css({ + width: max(0, width) // account for borders & padding + , height: max(1, cssH($T, spacing)) // ditto + , left: offset // TODO: VERIFY that toggler positions correctly for ALL values + , top: 0 + }); + // CENTER the toggler content SPAN + $T.children(".content").each(function(){ + $TC = $(this); + $TC.css("marginLeft", Math.floor((width-$TC.outerWidth())/2)); // could be negative + }); + } + else { // east/west + var height = cssH($T, togLen); + $T.css({ + height: max(0, height) // account for borders & padding + , width: max(1, cssW($T, spacing)) // ditto + , top: offset // POSITION the toggler + , left: 0 + }); + // CENTER the toggler content SPAN + $T.children(".content").each(function(){ + $TC = $(this); + $TC.css("marginTop", Math.floor((height-$TC.outerHeight())/2)); // could be negative + }); + } + + // remove ALL hover classes + removeHover( 0, $T ); + } + + // DONE measuring and sizing this resizer/toggler, so can be 'hidden' now + if (!state.initialized && (o.initHidden || s.noRoom)) { + $R.hide(); + if ($T) $T.hide(); + } + }); + } + + +, enableClosable = function (pane) { + if (!isInitialized()) return; + var $T = $Ts[pane], o = options[pane]; + if (!$T) return; + o.closable = true; + $T .bind("click."+ sID, function(evt){ evt.stopPropagation(); toggle(pane); }) + .bind("mouseenter."+ sID, addHover) + .bind("mouseleave."+ sID, removeHover) + .css("visibility", "visible") + .css("cursor", "pointer") + .attr("title", state[pane].isClosed ? o.togglerTip_closed : o.togglerTip_open) // may be blank + .show() + ; + } + +, disableClosable = function (pane, hide) { + if (!isInitialized()) return; + var $T = $Ts[pane]; + if (!$T) return; + options[pane].closable = false; + // is closable is disable, then pane MUST be open! + if (state[pane].isClosed) open(pane, false, true); + $T .unbind("."+ sID) + .css("visibility", hide ? "hidden" : "visible") // instead of hide(), which creates logic issues + .css("cursor", "default") + .attr("title", "") + ; + } + + +, enableSlidable = function (pane) { + if (!isInitialized()) return; + var $R = $Rs[pane], o = options[pane]; + if (!$R || !$R.data('draggable')) return; + options[pane].slidable = true; + if (s.isClosed) + bindStartSlidingEvent(pane, true); + } + +, disableSlidable = function (pane) { + if (!isInitialized()) return; + var $R = $Rs[pane]; + if (!$R) return; + options[pane].slidable = false; + if (state[pane].isSliding) + close(pane, false, true); + else { + bindStartSlidingEvent(pane, false); + $R .css("cursor", "default") + .attr("title", "") + ; + removeHover(null, $R[0]); // in case currently hovered + } + } + + +, enableResizable = function (pane) { + if (!isInitialized()) return; + var $R = $Rs[pane], o = options[pane]; + if (!$R || !$R.data('draggable')) return; + o.resizable = true; + $R .draggable("enable") + .bind("mouseenter."+ sID, onResizerEnter) + .bind("mouseleave."+ sID, onResizerLeave) + ; + if (!state[pane].isClosed) + $R .css("cursor", o.resizerCursor) + .attr("title", o.resizerTip) + ; + } + +, disableResizable = function (pane) { + if (!isInitialized()) return; + var $R = $Rs[pane]; + if (!$R || !$R.data('draggable')) return; + options[pane].resizable = false; + $R .draggable("disable") + .unbind("."+ sID) + .css("cursor", "default") + .attr("title", "") + ; + removeHover(null, $R[0]); // in case currently hovered + } + + + /** + * Move a pane from source-side (eg, west) to target-side (eg, east) + * If pane exists on target-side, move that to source-side, ie, 'swap' the panes + * + * @param {string} pane1 The pane/edge being swapped + * @param {string} pane2 ditto + */ +, swapPanes = function (pane1, pane2) { + if (!isInitialized()) return; + // change state.edge NOW so callbacks can know where pane is headed... + state[pane1].edge = pane2; + state[pane2].edge = pane1; + // run these even if NOT state.initialized + var cancelled = false; + if (false === _execCallback(pane1, options[pane1].onswap_start)) cancelled = true; + if (!cancelled && false === _execCallback(pane2, options[pane2].onswap_start)) cancelled = true; + if (cancelled) { + state[pane1].edge = pane1; // reset + state[pane2].edge = pane2; + return; + } + + var + oPane1 = copy( pane1 ) + , oPane2 = copy( pane2 ) + , sizes = {} + ; + sizes[pane1] = oPane1 ? oPane1.state.size : 0; + sizes[pane2] = oPane2 ? oPane2.state.size : 0; + + // clear pointers & state + $Ps[pane1] = false; + $Ps[pane2] = false; + state[pane1] = {}; + state[pane2] = {}; + + // ALWAYS remove the resizer & toggler elements + if ($Ts[pane1]) $Ts[pane1].remove(); + if ($Ts[pane2]) $Ts[pane2].remove(); + if ($Rs[pane1]) $Rs[pane1].remove(); + if ($Rs[pane2]) $Rs[pane2].remove(); + $Rs[pane1] = $Rs[pane2] = $Ts[pane1] = $Ts[pane2] = false; + + // transfer element pointers and data to NEW Layout keys + move( oPane1, pane2 ); + move( oPane2, pane1 ); + + // cleanup objects + oPane1 = oPane2 = sizes = null; + + // make panes 'visible' again + if ($Ps[pane1]) $Ps[pane1].css(_c.visible); + if ($Ps[pane2]) $Ps[pane2].css(_c.visible); + + // fix any size discrepancies caused by swap + resizeAll(); + + // run these even if NOT state.initialized + _execCallback(pane1, options[pane1].onswap_end || options[pane1].onswap); + _execCallback(pane2, options[pane2].onswap_end || options[pane2].onswap); + + return; + + function copy (n) { // n = pane + var + $P = $Ps[n] + , $C = $Cs[n] + ; + return !$P ? false : { + pane: n + , P: $P ? $P[0] : false + , C: $C ? $C[0] : false + , state: $.extend({}, state[n]) + , options: $.extend({}, options[n]) + } + }; + + function move (oPane, pane) { + if (!oPane) return; + var + P = oPane.P + , C = oPane.C + , oldPane = oPane.pane + , c = _c[pane] + , side = c.side.toLowerCase() + , inset = "inset"+ c.side + // save pane-options that should be retained + , s = $.extend({}, state[pane]) + , o = options[pane] + // RETAIN side-specific FX Settings - more below + , fx = { resizerCursor: o.resizerCursor } + , re, size, pos + ; + $.each("fxName,fxSpeed,fxSettings".split(","), function (i, k) { + fx[k] = o[k]; + fx[k +"_open"] = o[k +"_open"]; + fx[k +"_close"] = o[k +"_close"]; + }); + + // update object pointers and attributes + $Ps[pane] = $(P) + .data("layoutEdge", pane) + .css(_c.hidden) + .css(c.cssReq) + ; + $Cs[pane] = C ? $(C) : false; + + // set options and state + options[pane] = $.extend({}, oPane.options, fx); + state[pane] = $.extend({}, oPane.state); + + // change classNames on the pane, eg: ui-layout-pane-east ==> ui-layout-pane-west + re = new RegExp(o.paneClass +"-"+ oldPane, "g"); + P.className = P.className.replace(re, o.paneClass +"-"+ pane); + + // ALWAYS regenerate the resizer & toggler elements + initHandles(pane); // create the required resizer & toggler + + // if moving to different orientation, then keep 'target' pane size + if (c.dir != _c[oldPane].dir) { + size = sizes[pane] || 0; + setSizeLimits(pane); // update pane-state + size = max(size, state[pane].minSize); + // use manualSizePane to disable autoResize - not useful after panes are swapped + manualSizePane(pane, size, true); // true = skipCallback + } + else // move the resizer here + $Rs[pane].css(side, sC[inset] + (state[pane].isVisible ? getPaneSize(pane) : 0)); + + + // ADD CLASSNAMES & SLIDE-BINDINGS + if (oPane.state.isVisible && !s.isVisible) + setAsOpen(pane, true); // true = skipCallback + else { + setAsClosed(pane); + bindStartSlidingEvent(pane, true); // will enable events IF option is set + } + + // DESTROY the object + oPane = null; + }; + } + + +; // END var DECLARATIONS + + /** + * Capture keys when enableCursorHotkey - toggle pane if hotkey pressed + * + * @see document.keydown() + */ + function keyDown (evt) { + if (!evt) return true; + var code = evt.keyCode; + if (code < 33) return true; // ignore special keys: ENTER, TAB, etc + + var + PANE = { + 38: "north" // Up Cursor - $.ui.keyCode.UP + , 40: "south" // Down Cursor - $.ui.keyCode.DOWN + , 37: "west" // Left Cursor - $.ui.keyCode.LEFT + , 39: "east" // Right Cursor - $.ui.keyCode.RIGHT + } + , ALT = evt.altKey // no worky! + , SHIFT = evt.shiftKey + , CTRL = evt.ctrlKey + , CURSOR = (CTRL && code >= 37 && code <= 40) + , o, k, m, pane + ; + + if (CURSOR && options[PANE[code]].enableCursorHotkey) // valid cursor-hotkey + pane = PANE[code]; + else if (CTRL || SHIFT) // check to see if this matches a custom-hotkey + $.each(_c.borderPanes.split(","), function (i, p) { // loop each pane to check its hotkey + o = options[p]; + k = o.customHotkey; + m = o.customHotkeyModifier; // if missing or invalid, treated as "CTRL+SHIFT" + if ((SHIFT && m=="SHIFT") || (CTRL && m=="CTRL") || (CTRL && SHIFT)) { // Modifier matches + if (k && code == (isNaN(k) || k <= 9 ? k.toUpperCase().charCodeAt(0) : k)) { // Key matches + pane = p; + return false; // BREAK + } + } + }); + + // validate pane + if (!pane || !$Ps[pane] || !options[pane].closable || state[pane].isHidden) + return true; + + toggle(pane); + + evt.stopPropagation(); + evt.returnValue = false; // CANCEL key + return false; + }; + + +/* + * ###################################### + * UTILITY METHODS + * called externally or by initButtons + * ###################################### + */ + + /** + * Change/reset a pane overflow setting & zIndex to allow popups/drop-downs to work + * + * @param {Object=} el (optional) Can also be 'bound' to a click, mouseOver, or other event + */ + function allowOverflow (el) { + if (!isInitialized()) return; + if (this && this.tagName) el = this; // BOUND to element + var $P; + if (isStr(el)) + $P = $Ps[el]; + else if ($(el).data("layoutRole")) + $P = $(el); + else + $(el).parents().each(function(){ + if ($(this).data("layoutRole")) { + $P = $(this); + return false; // BREAK + } + }); + if (!$P || !$P.length) return; // INVALID + + var + pane = $P.data("layoutEdge") + , s = state[pane] + ; + + // if pane is already raised, then reset it before doing it again! + // this would happen if allowOverflow is attached to BOTH the pane and an element + if (s.cssSaved) + resetOverflow(pane); // reset previous CSS before continuing + + // if pane is raised by sliding or resizing, or its closed, then abort + if (s.isSliding || s.isResizing || s.isClosed) { + s.cssSaved = false; + return; + } + + var + newCSS = { zIndex: (_c.zIndex.pane_normal + 2) } + , curCSS = {} + , of = $P.css("overflow") + , ofX = $P.css("overflowX") + , ofY = $P.css("overflowY") + ; + // determine which, if any, overflow settings need to be changed + if (of != "visible") { + curCSS.overflow = of; + newCSS.overflow = "visible"; + } + if (ofX && !ofX.match(/visible|auto/)) { + curCSS.overflowX = ofX; + newCSS.overflowX = "visible"; + } + if (ofY && !ofY.match(/visible|auto/)) { + curCSS.overflowY = ofX; + newCSS.overflowY = "visible"; + } + + // save the current overflow settings - even if blank! + s.cssSaved = curCSS; + + // apply new CSS to raise zIndex and, if necessary, make overflow 'visible' + $P.css( newCSS ); + + // make sure the zIndex of all other panes is normal + $.each(_c.allPanes.split(","), function(i, p) { + if (p != pane) resetOverflow(p); + }); + + }; + + function resetOverflow (el) { + if (!isInitialized()) return; + if (this && this.tagName) el = this; // BOUND to element + var $P; + if (isStr(el)) + $P = $Ps[el]; + else if ($(el).data("layoutRole")) + $P = $(el); + else + $(el).parents().each(function(){ + if ($(this).data("layoutRole")) { + $P = $(this); + return false; // BREAK + } + }); + if (!$P || !$P.length) return; // INVALID + + var + pane = $P.data("layoutEdge") + , s = state[pane] + , CSS = s.cssSaved || {} + ; + // reset the zIndex + if (!s.isSliding && !s.isResizing) + $P.css("zIndex", _c.zIndex.pane_normal); + + // reset Overflow - if necessary + $P.css( CSS ); + + // clear var + s.cssSaved = false; + }; + + + /** + * Helper function to validate params received by addButton utilities + * + * Two classes are added to the element, based on the buttonClass... + * The type of button is appended to create the 2nd className: + * - ui-layout-button-pin + * - ui-layout-pane-button-toggle + * - ui-layout-pane-button-open + * - ui-layout-pane-button-close + * + * @param {(string|!Object)} selector jQuery selector (or element) for button, eg: ".ui-layout-north .toggle-button" + * @param {string} pane Name of the pane the button is for: 'north', 'south', etc. + * @return {Array.} If both params valid, the element matching 'selector' in a jQuery wrapper - otherwise returns null + */ + function getBtn (selector, pane, action) { + var $E = $(selector) + , err = options.showErrorMessages; + if (!$E.length) { // element not found + if (err) alert(lang.errButton + lang.selector +": "+ selector); + } + else if (_c.borderPanes.indexOf(pane) == -1) // invalid 'pane' sepecified + if (err) alert(lang.errButton + lang.pane +": "+ pane); + else { // VALID + var btn = options[pane].buttonClass +"-"+ action; + $E + .addClass( btn +" "+ btn +"-"+ pane ) + .data("layoutName", options.name) // add layout identifier - even if blank! + ; + return $E; + } + return null; // INVALID + }; + + + /** + * NEW syntax for binding layout-buttons - will eventually replace addToggleBtn, addOpenBtn, etc. + * + * @param {(string|!Object)} selector jQuery selector (or element) for button, eg: ".ui-layout-north .toggle-button" + * @param {string} action + * @param {string} pane + */ + function bindButton (selector, action, pane) { + switch (action.toLowerCase()) { + case "toggle": addToggleBtn(selector, pane); break; + case "open": addOpenBtn(selector, pane); break; + case "close": addCloseBtn(selector, pane); break; + case "pin": addPinBtn(selector, pane); break; + case "toggle-slide": addToggleBtn(selector, pane, true); break; + case "open-slide": addOpenBtn(selector, pane, true); break; + } + }; + + /** + * Add a custom Toggler button for a pane + * + * @param {(string|!Object)} selector jQuery selector (or element) for button, eg: ".ui-layout-north .toggle-button" + * @param {string} pane Name of the pane the button is for: 'north', 'south', etc. + * @param {boolean=} slide true = slide-open, false = pin-open + */ + function addToggleBtn (selector, pane, slide) { + var $E = getBtn(selector, pane, "toggle"); + if ($E) + $E.click(function (evt) { + toggle(pane, !!slide); + evt.stopPropagation(); + }); + }; + + /** + * Add a custom Open button for a pane + * + * @param {(string|!Object)} selector jQuery selector (or element) for button, eg: ".ui-layout-north .toggle-button" + * @param {string} pane Name of the pane the button is for: 'north', 'south', etc. + * @param {boolean=} slide true = slide-open, false = pin-open + */ + function addOpenBtn (selector, pane, slide) { + var $E = getBtn(selector, pane, "open"); + if ($E) + $E + .attr("title", lang.Open) + .click(function (evt) { + open(pane, !!slide); + evt.stopPropagation(); + }) + ; + }; + + /** + * Add a custom Close button for a pane + * + * @param {(string|!Object)} selector jQuery selector (or element) for button, eg: ".ui-layout-north .toggle-button" + * @param {string} pane Name of the pane the button is for: 'north', 'south', etc. + */ + function addCloseBtn (selector, pane) { + var $E = getBtn(selector, pane, "close"); + if ($E) + $E + .attr("title", lang.Close) + .click(function (evt) { + close(pane); + evt.stopPropagation(); + }) + ; + }; + + /** + * addPinBtn + * + * Add a custom Pin button for a pane + * + * Four classes are added to the element, based on the paneClass for the associated pane... + * Assuming the default paneClass and the pin is 'up', these classes are added for a west-pane pin: + * - ui-layout-pane-pin + * - ui-layout-pane-west-pin + * - ui-layout-pane-pin-up + * - ui-layout-pane-west-pin-up + * + * @param {(string|!Object)} selector jQuery selector (or element) for button, eg: ".ui-layout-north .toggle-button" + * @param {string} pane Name of the pane the pin is for: 'north', 'south', etc. + */ + function addPinBtn (selector, pane) { + var $E = getBtn(selector, pane, "pin"); + if ($E) { + var s = state[pane]; + $E.click(function (evt) { + setPinState($(this), pane, (s.isSliding || s.isClosed)); + if (s.isSliding || s.isClosed) open( pane ); // change from sliding to open + else close( pane ); // slide-closed + evt.stopPropagation(); + }); + // add up/down pin attributes and classes + setPinState($E, pane, (!s.isClosed && !s.isSliding)); + // add this pin to the pane data so we can 'sync it' automatically + // PANE.pins key is an array so we can store multiple pins for each pane + _c[pane].pins.push( selector ); // just save the selector string + } + }; + + /** + * INTERNAL function to sync 'pin buttons' when pane is opened or closed + * Unpinned means the pane is 'sliding' - ie, over-top of the adjacent panes + * + * @see open(), close() + * @param {string} pane These are the params returned to callbacks by layout() + * @param {boolean} doPin True means set the pin 'down', False means 'up' + */ + function syncPinBtns (pane, doPin) { + $.each(_c[pane].pins, function (i, selector) { + setPinState($(selector), pane, doPin); + }); + }; + + /** + * Change the class of the pin button to make it look 'up' or 'down' + * + * @see addPinBtn(), syncPinBtns() + * @param {Array.} $Pin The pin-span element in a jQuery wrapper + * @param {string} pane These are the params returned to callbacks by layout() + * @param {boolean} doPin true = set the pin 'down', false = set it 'up' + */ + function setPinState ($Pin, pane, doPin) { + var updown = $Pin.attr("pin"); + if (updown && doPin == (updown=="down")) return; // already in correct state + var + pin = options[pane].buttonClass +"-pin" + , side = pin +"-"+ pane + , UP = pin +"-up "+ side +"-up" + , DN = pin +"-down "+side +"-down" + ; + $Pin + .attr("pin", doPin ? "down" : "up") // logic + .attr("title", doPin ? lang.Unpin : lang.Pin) + .removeClass( doPin ? UP : DN ) + .addClass( doPin ? DN : UP ) + ; + }; + + + /* + * LAYOUT STATE MANAGEMENT + * + * @example .layout({ cookie: { name: "myLayout", keys: "west.isClosed,east.isClosed" } }) + * @example .layout({ cookie__name: "myLayout", cookie__keys: "west.isClosed,east.isClosed" }) + * @example myLayout.getState( "west.isClosed,north.size,south.isHidden" ); + * @example myLayout.saveCookie( "west.isClosed,north.size,south.isHidden", {expires: 7} ); + * @example myLayout.deleteCookie(); + * @example myLayout.loadCookie(); + * @example var hSaved = myLayout.state.cookie; + */ + + function isCookiesEnabled () { + // TODO: is the cookieEnabled property common enough to be useful??? + return (navigator.cookieEnabled != 0); + }; + + /** + * Read & return data from the cookie - as JSON + * + * @param {Object=} opts + */ + function getCookie (opts) { + var + o = $.extend( {}, options.cookie, opts || {} ) + , name = o.name || options.name || "Layout" + , c = document.cookie + , cs = c ? c.split(';') : [] + , pair // loop var + ; + for (var i=0, n=cs.length; i < n; i++) { + pair = $.trim(cs[i]).split('='); // name=value pair + if (pair[0] == name) // found the layout cookie + // convert cookie string back to a hash + return decodeJSON( decodeURIComponent(pair[1]) ); + } + return ""; + }; + + /** + * Get the current layout state and save it to a cookie + * + * @param {(string|Array)=} keys + * @param {Object=} opts + */ + function saveCookie (keys, opts) { + var + o = $.extend( {}, options.cookie, opts || {} ) + , name = o.name || options.name || "Layout" + , params = '' + , date = '' + , clear = false + ; + if (o.expires.toUTCString) + date = o.expires; + else if (typeof o.expires == 'number') { + date = new Date(); + if (o.expires > 0) + date.setDate(date.getDate() + o.expires); + else { + date.setYear(1970); + clear = true; + } + } + if (date) params += ';expires='+ date.toUTCString(); + if (o.path) params += ';path='+ o.path; + if (o.domain) params += ';domain='+ o.domain; + if (o.secure) params += ';secure'; + + if (clear) { + state.cookie = {}; // clear data + document.cookie = name +'='+ params; // expire the cookie + } + else { + state.cookie = getState(keys || o.keys); // read current panes-state + document.cookie = name +'='+ encodeURIComponent( encodeJSON(state.cookie) ) + params; // write cookie + } + + return $.extend({}, state.cookie); // return COPY of state.cookie + }; + + /** + * Remove the state cookie + */ + function deleteCookie () { + saveCookie('', { expires: -1 }); + }; + + /** + * Get data from the cookie and USE IT to loadState + * + * @param {Object=} opts + */ + function loadCookie (opts) { + var o = getCookie(opts); // READ the cookie + if (o) { + state.cookie = $.extend({}, o); // SET state.cookie + loadState(o); // LOAD the retrieved state + } + return o; + }; + + /** + * Update layout options from the cookie, if one exists + * + * @param {Object=} opts + * @param {boolean=} animate + */ + function loadState (opts, animate) { + opts = _transformData(opts); + $.extend( true, options, opts ); // update layout options + // if layout has already been initialized, then UPDATE layout state + if (state.initialized) { + var pane, o, s, h, c, a = !animate; + $.each(_c.allPanes.split(","), function (idx, pane) { + o = opts[ pane ]; + if (typeof o != 'object') return; // no key, continue + s = o.size; + c = o.initClosed; + h = o.initHidden; + if (s > 0 || s=="auto") sizePane(pane, s); + if (h === true) hide(pane, a); + else if (c === false) open(pane, false, a ); + else if (c === true) close(pane, false, a); + else if (h === false) show(pane, false, a); + }); + } + }; + + /** + * Get the *current layout state* and return it as a hash + * + * @param {(string|Array)=} keys + */ + function getState (keys) { + var + data = {} + , alt = { isClosed: 'initClosed', isHidden: 'initHidden' } + , pair, pane, key, val + ; + if (!keys) keys = options.cookie.keys; // if called by user + if ($.isArray(keys)) keys = keys.join(","); + // convert keys to an array and change delimiters from '__' to '.' + keys = keys.replace(/__/g, ".").split(','); + // loop keys and create a data hash + for (var i=0,n=keys.length; i < n; i++) { + pair = keys[i].split("."); + pane = pair[0]; + key = pair[1]; + if (_c.allPanes.indexOf(pane) < 0) continue; // bad pane! + val = state[ pane ][ key ]; + if (val == undefined) continue; + if (key=="isClosed" && state[pane]["isSliding"]) + val = true; // if sliding, then *really* isClosed + ( data[pane] || (data[pane]={}) )[ alt[key] ? alt[key] : key ] = val; + } + return data; + }; + + /** + * Stringify a JSON hash so can save in a cookie or db-field + */ + function encodeJSON (JSON) { + return parse( JSON ); + function parse (h) { + var D=[], i=0, k, v, t; // k = key, v = value + for (k in h) { + v = h[k]; + t = typeof v; + if (t == 'string') // STRING - add quotes + v = '"'+ v +'"'; + else if (t == 'object') // SUB-KEY - recurse into it + v = parse(v); + D[i++] = '"'+ k +'":'+ v; + } + return "{"+ D.join(",") +"}"; + }; + }; + + /** + * Convert stringified JSON back to a hash object + */ + function decodeJSON (str) { + try { return window["eval"]("("+ str +")") || {}; } + catch (e) { return {}; } + }; + + +/* + * ##################### + * CREATE/RETURN LAYOUT + * ##################### + */ + + // validate that container exists + var $N = $(this).eq(0); // FIRST matching Container element + if (!$N.length) { + if (options.showErrorMessages) + alert( lang.errContainerMissing ); + return null; + }; + + // Users retreive Instance of a layout with: $N.layout() OR $N.data("layout") + // return the Instance-pointer if layout has already been initialized + if ($N.data("layoutContainer") && $N.data("layout")) + return $N.data("layout"); // cached pointer + + // init global vars + var + $Ps = {} // Panes x5 - set in initPanes() + , $Cs = {} // Content x5 - set in initPanes() + , $Rs = {} // Resizers x4 - set in initHandles() + , $Ts = {} // Togglers x4 - set in initHandles() + // aliases for code brevity + , sC = state.container // alias for easy access to 'container dimensions' + , sID = state.id // alias for unique layout ID/namespace - eg: "layout435" + ; + + // create Instance object to expose data & option Properties, and primary action Methods + var Instance = { + options: options // property - options hash + , state: state // property - dimensions hash + , container: $N // property - object pointers for layout container + , panes: $Ps // property - object pointers for ALL Panes: panes.north, panes.center + , contents: $Cs // property - object pointers for ALL Content: content.north, content.center + , resizers: $Rs // property - object pointers for ALL Resizers, eg: resizers.north + , togglers: $Ts // property - object pointers for ALL Togglers, eg: togglers.north + , toggle: toggle // method - pass a 'pane' ("north", "west", etc) + , hide: hide // method - ditto + , show: show // method - ditto + , open: open // method - ditto + , close: close // method - ditto + , slideOpen: slideOpen // method - ditto + , slideClose: slideClose // method - ditto + , slideToggle: slideToggle // method - ditto + , initContent: initContent // method - ditto + , sizeContent: sizeContent // method - ditto + , sizePane: manualSizePane // method - pass a 'pane' AND an 'outer-size' in pixels or percent, or 'auto' + , swapPanes: swapPanes // method - pass TWO 'panes' - will swap them + , resizeAll: resizeAll // method - no parameters + , initPanes: isInitialized // method - no parameters + , destroy: destroy // method - no parameters + , addPane: addPane // method - pass a 'pane' + , removePane: removePane // method - pass a 'pane' to remove from layout, add 'true' to delete the pane-elem + , setSizeLimits: setSizeLimits // method - pass a 'pane' - update state min/max data + , bindButton: bindButton // utility - pass element selector, 'action' and 'pane' (E, "toggle", "west") + , addToggleBtn: addToggleBtn // utility - pass element selector and 'pane' (E, "west") + , addOpenBtn: addOpenBtn // utility - ditto + , addCloseBtn: addCloseBtn // utility - ditto + , addPinBtn: addPinBtn // utility - ditto + , allowOverflow: allowOverflow // utility - pass calling element (this) + , resetOverflow: resetOverflow // utility - ditto + , encodeJSON: encodeJSON // method - pass a JSON object + , decodeJSON: decodeJSON // method - pass a string of encoded JSON + , getState: getState // method - returns hash of current layout-state + , getCookie: getCookie // method - update options from cookie - returns hash of cookie data + , saveCookie: saveCookie // method - optionally pass keys-list and cookie-options (hash) + , deleteCookie: deleteCookie // method + , loadCookie: loadCookie // method - update options from cookie - returns hash of cookie data + , loadState: loadState // method - pass a hash of state to use to update options + , cssWidth: cssW // utility - pass element and target outerWidth + , cssHeight: cssH // utility - ditto + , enableClosable: enableClosable + , disableClosable: disableClosable + , enableSlidable: enableSlidable + , disableSlidable: disableSlidable + , enableResizable: enableResizable + , disableResizable: disableResizable + }; + + // create the border layout NOW + if (_create() === 'cancel') // onload_start callback returned false to CANCEL layout creation + return null; + else // true OR false -- if layout-elements did NOT init (hidden or do not exist), can auto-init later + return Instance; // return the Instance object + +} +})( jQuery ); \ No newline at end of file diff --git a/htdocs/theme/auguria/style.css.php b/htdocs/theme/auguria/style.css.php index debc0f1cef0..817e67b5516 100644 --- a/htdocs/theme/auguria/style.css.php +++ b/htdocs/theme/auguria/style.css.php @@ -21,7 +21,7 @@ /** * \file htdocs/theme/auguria/style.css.php * \brief Fichier de style CSS du theme auguria - * \version $Id: style.css.php,v 1.86 2011/07/06 09:42:55 eldy Exp $ + * \version $Id: style.css.php,v 1.87 2011/07/06 11:40:21 eldy Exp $ */ //if (! defined('NOREQUIREUSER')) define('NOREQUIREUSER','1'); // Not disabled cause need to load personalized language @@ -672,6 +672,7 @@ td.photo { .toolbar { background-image: url(theme.'/img/tmenu2.jpg' ?>) !important; background-repeat: repeat-x !important; + border: 1px solid #BBB !important; } .toolbarbutton { @@ -688,17 +689,16 @@ td.photo { /* Panes for ECM or Filemanager */ /* ============================================================================== */ -/* #containerlayout .layout-with-no-border { border: 0 !important; border-width: 0 !important; } -*/ #containerlayout .layout-padding { padding: 2px !important; } + /* * PANES and CONTENT-DIVs */ diff --git a/htdocs/theme/bureau2crea/style.css.php b/htdocs/theme/bureau2crea/style.css.php index 488a3ab72fc..fe222869fba 100644 --- a/htdocs/theme/bureau2crea/style.css.php +++ b/htdocs/theme/bureau2crea/style.css.php @@ -23,7 +23,7 @@ /** * \file htdocs/theme/bureau2crea/style.css.php * \brief Fichier de style CSS du theme bureau2crea - * \version $Id: style.css.php,v 1.37 2011/07/06 09:42:55 eldy Exp $ + * \version $Id: style.css.php,v 1.38 2011/07/06 11:40:21 eldy Exp $ */ //if (! defined('NOREQUIREUSER')) define('NOREQUIREUSER','1'); // Not disabled cause need to load personalized language @@ -788,6 +788,7 @@ td.photo { .toolbar { background-image: url(theme.'/img/tmenu2.png' ?>) !important; background-repeat: repeat-x !important; + border: 1px solid #BBB !important; } .toolbarbutton { @@ -804,6 +805,15 @@ td.photo { /* Panes for ECM or Filemanager */ /* ============================================================================== */ +#containerlayout .layout-with-no-border { + border: 0 !important; + border-width: 0 !important; +} + +#containerlayout .layout-padding { + padding: 2px !important; +} + /* * PANES and CONTENT-DIVs */ diff --git a/htdocs/theme/cameleo/style.css.php b/htdocs/theme/cameleo/style.css.php index 5782c5a3d62..bc2b96b8dc6 100644 --- a/htdocs/theme/cameleo/style.css.php +++ b/htdocs/theme/cameleo/style.css.php @@ -23,7 +23,7 @@ /** * \file htdocs/theme/eldy/style.css.php * \brief Fichier de style CSS du theme Cameleo - * \version $Id: style.css.php,v 1.5 2011/07/06 09:42:55 eldy Exp $ + * \version $Id: style.css.php,v 1.6 2011/07/06 11:40:22 eldy Exp $ */ //if (! defined('NOREQUIREUSER')) define('NOREQUIREUSER','1'); // Not disabled cause need to load personalized language @@ -809,6 +809,7 @@ td.photo { .toolbar { background-image: url(theme.'/img/tmenu2.png' ?>) !important; background-repeat: repeat-x !important; + border: 1px solid #BBB !important; } .toolbarbutton { @@ -937,13 +938,13 @@ td.photo { padding-bottom: 0.35ex; /* to 'vertically center' text inside text-span */ } -.ui-in-layout-center { +.ecm-in-layout-center { border-left: 0px !important; border-right: 0px !important; border-top: 0px !important; } -.ui-in-layout-south { +.ecm-in-layout-south { border-left: 0px !important; border-right: 0px !important; border-bottom: 0px !important; diff --git a/htdocs/theme/eldy/style.css.php b/htdocs/theme/eldy/style.css.php index 3a3a049f95f..240d1ce8db9 100644 --- a/htdocs/theme/eldy/style.css.php +++ b/htdocs/theme/eldy/style.css.php @@ -21,7 +21,7 @@ /** * \file htdocs/theme/eldy/style.css.php * \brief Fichier de style CSS du theme Eldy - * \version $Id: style.css.php,v 1.83 2011/07/06 10:22:56 eldy Exp $ + * \version $Id: style.css.php,v 1.84 2011/07/06 11:40:22 eldy Exp $ */ //if (! defined('NOREQUIREUSER')) define('NOREQUIREUSER','1'); // Not disabled cause need to load personalized language @@ -655,6 +655,7 @@ td.photo { .toolbar { background-image: url(theme.'/img/tmenu2.jpg' ?>) !important; background-repeat: repeat-x !important; + border: 1px solid #BBB !important; } .toolbarbutton { @@ -783,13 +784,13 @@ td.photo { padding-bottom: 0.35ex; /* to 'vertically center' text inside text-span */ } -.ui-in-layout-center { +.ecm-in-layout-center { border-left: 0px !important; border-right: 0px !important; border-top: 0px !important; } -.ui-in-layout-south { +.ecm-in-layout-south { border-left: 0px !important; border-right: 0px !important; border-bottom: 0px !important; diff --git a/htdocs/theme/freelug/style.css.php b/htdocs/theme/freelug/style.css.php index 0e5cb135e87..df2908253a1 100644 --- a/htdocs/theme/freelug/style.css.php +++ b/htdocs/theme/freelug/style.css.php @@ -20,7 +20,7 @@ /** * \file htdocs/theme/freelug/style.css.php * \brief Fichier de style CSS du theme Freelug - * \version $Id: style.css.php,v 1.40 2011/07/06 09:42:55 eldy Exp $ + * \version $Id: style.css.php,v 1.41 2011/07/06 11:40:22 eldy Exp $ */ //if (! defined('NOREQUIREUSER')) define('NOREQUIREUSER','1'); // Not disabled cause need to load personalized language @@ -531,6 +531,7 @@ td.photo { .toolbar { background-image: url(theme.'/img/tmenu2.png' ?>) !important; background-repeat: repeat-x !important; + border: 1px solid #BBB !important; } .toolbarbutton { @@ -658,13 +659,13 @@ td.photo { padding-bottom: 0.35ex; /* to 'vertically center' text inside text-span */ } -.ui-in-layout-center { +.ecm-in-layout-center { border-left: 0px !important; border-right: 0px !important; border-top: 0px !important; } -.ui-in-layout-south { +.ecm-in-layout-south { border-left: 0px !important; border-right: 0px !important; border-bottom: 0px !important; diff --git a/htdocs/theme/yellow/style.css.php b/htdocs/theme/yellow/style.css.php index b72b9f67261..89b73c4431d 100644 --- a/htdocs/theme/yellow/style.css.php +++ b/htdocs/theme/yellow/style.css.php @@ -20,7 +20,7 @@ /** * \file htdocs/theme/yellow/style.css.php * \brief Fichier de style CSS du theme Yellow - * \version $Id: style.css.php,v 1.41 2011/07/06 09:42:55 eldy Exp $ + * \version $Id: style.css.php,v 1.42 2011/07/06 11:40:21 eldy Exp $ */ //if (! defined('NOREQUIREUSER')) define('NOREQUIREUSER','1'); // Not disabled cause need to load personalized language @@ -525,6 +525,7 @@ td.photo { .toolbar { background-image: url(theme.'/img/tmenu2.png' ?>) !important; background-repeat: repeat-x !important; + border: 1px solid #BBB !important; } .toolbarbutton { @@ -652,13 +653,13 @@ td.photo { padding-bottom: 0.35ex; /* to 'vertically center' text inside text-span */ } -.ui-in-layout-center { +.ecm-in-layout-center { border-left: 0px !important; border-right: 0px !important; border-top: 0px !important; } -.ui-in-layout-south { +.ecm-in-layout-south { border-left: 0px !important; border-right: 0px !important; border-bottom: 0px !important; From c175311aedb125baa33412235b361f1f7ca56c1e Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 6 Jul 2011 11:43:39 +0000 Subject: [PATCH 15/66] Update jquery layout lib --- COPYRIGHT | 2 +- .../plugins/layout/jquery.layout-latest.jgz | Bin 0 -> 42730 bytes .../layout/jquery.layout-latest.min.jgz | Bin 17060 -> 0 bytes .../layout/jquery.layout-latest.min.js | 1226 ----------------- htdocs/main.inc.php | 4 +- 5 files changed, 3 insertions(+), 1229 deletions(-) create mode 100644 htdocs/includes/jquery/plugins/layout/jquery.layout-latest.jgz delete mode 100644 htdocs/includes/jquery/plugins/layout/jquery.layout-latest.min.jgz delete mode 100644 htdocs/includes/jquery/plugins/layout/jquery.layout-latest.min.js diff --git a/COPYRIGHT b/COPYRIGHT index 3872a12729d..ac8175f83a0 100644 --- a/COPYRIGHT +++ b/COPYRIGHT @@ -25,7 +25,7 @@ GeoIP x.x Yes GeoIP Ma jQuery 1.6.1 GPL and MIT Licence Yes JS library jQuery UI 1.8.14 GPL and MIT Licence Yes JS library plugin UI jQuery JCrop 0.9.8 MIT Licence Yes JS library plugin Crop (to crop images) -jQuery Layout 1.3.0 GPL and MIT Licence Yes JS library plugin Layout (RC-29.12) +jQuery Layout 1.3.0 GPL and MIT Licence Yes JS library plugin Layout (RC-29.15) jQuery TableDnD 0.5 GPL and MIT Licence Yes JS library plugin TableDnD (to reorder table rows) jQuery jNotify 1.1.00 Apache Software License 2.0 No GPL3 only JS library plugin jNotify (to use ajax popups) jQuery Mobile 1.0b1 GPL and MIT Licence Yes JS library for smartphone (not used) diff --git a/htdocs/includes/jquery/plugins/layout/jquery.layout-latest.jgz b/htdocs/includes/jquery/plugins/layout/jquery.layout-latest.jgz new file mode 100644 index 0000000000000000000000000000000000000000..a3c068acb0ef63e5642a712fad2f586bd869cd55 GIT binary patch literal 42730 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⁢- zbJ?kEQxL+ZqpxQs+qP?Cync#eh-uzvca;Ro<-+A$6BstkJ;d<2t;B_E#qnk3-%KdJ ztmGq!jEld@b|d~7(SJhQ33^mS?$f0ea}ZiZy_^-))ysVL%6e~&4K@RFemO~H#Ah3X zQxPyxd~Ke!G&i>j@?BDP7;UQqbKZ?5q--3N2I$%RykR{{aZ|3PG5~FU&Sl z6nqoH|GJm_CVH44#^o9LP$mv(43e)%o_WDDsggYJEqIoQcHveEF}4g^!30@VNj@@{ zGqQ+pQc$f4Ez&!J4_^lk-@7_RfWq{1$b@P=uLjfQU~;CqJKhOWOB6~}=C4)TV_=%Z z=hz-_67JC=@3z4vV8ODfz$trugnoCH6a9<%W5@()Iup87@SBiTdpTv>gjgg5<=tfw zDk&hXGNx28t*6?YS|FHDXgjnI>Xf7o(u@(`nUf~QdlA$KLq^NReFi;hoS}o%dAU4a z)UBhM5S%@q!BoI8if=;p(x`}JCAIuBD{_+x!UcCJPE86lh&0p$=z%%X(G`zVz!~xp z=C$eXADmgr_RNy1V*_Og;K)6;CHPGxcrzAOslH&EHLa|8(kh!3d=rL<<{!-Y*m21q zad%9zRK|@*GtLM~(y>d{t7&(!+s(&EK2d0E%HfD;lBGAMrZ zMOjru^THV~_3*@(ZFjow>+LBae459~NAevVCugsB_Y+N}pf>1Ic&o>|^l&$RSoEvV z&@l@Z<1JK^h?fb53&Of8W``zDU{@u}%{Gz|54lR>_9)2C+@~>=j&(x#SpL*E>_|=@ zh@U&Zn5XF??@!}UiZ%?0Xgi;wfxn-fas^mVvL1!}QLIiGSP_3I!-Q|&Q7_t5$2tv6 z|2`MIME)g1YSRJyGy?J%Fr}1Fp#H#Baq8-lA6l9Gl-d$PR;JD=EVQpvoCzswPSY+l zU?a$#Uo?gUensLYUp(j=Pj2`j35MHv;sum*!@adgQbJ7O{o*)TzN`w5)=CvxJ zu_q)XSZH0b0Ou^Ne9bpn$!pX!?>jxQ8aCA}zgFOKa@{}loRzVkEQJJ!I;e4Uy_gRY zu8jbB2Ivb*Mzp{E*K#z!iEaNFFQQEcZxZQ4c?t3#@g>YFIAS4$aR^{s@b6jw0LFr} zJTMsOC#>kZJd0~u9~O+>u++r$6HJv=)1V<@Y%4oO$z>#?ECsc58;%F6uGnShcPp>t znpk=I{bP$5)X)IDu&fU`4|qO2)#pIEB|hh6PVjehD`4Q62l>>386zkF@21T$l+@F{ z5efJ+I*nB5lnR}OLZ`lvG`)M++c{-6U0p!1WleD9@e936coZ$&FS?3L?c{+duIu7z z(qYXc?Rl>t^9+i3bLH>u2v>q0IENGVqL+D9nF><}Cr^?;RR0g(*ODaJVXNP(UVs01 z@9aKbZA4t!ZU9hepvE9;2*=h&ebr=1mkFK<(Z>MeHlUPoa3Tsh^`0cgijDcr;C!(p zjbTIX`9|Obvvf(IFj-0}Q1m)&pk0abPA#V$R$wdwL%5pxV~<6)n{nnMQl7OLTBuEE zKy8Hss$lFKK26OSwo7)b&9iE*>;oclX!)xGa>~$9O9OFE^W2zc0r4UX_-uD#Ivk&p z941$@;@ZeTNC|%II~7f(=qz}+?^Zp9R{acOrB&E?-nAz}^)?8k0yR1%+Cs=e%;8Yc zoJEz7bYT=S$Ktgq=iDLvIY%b6Vk^!v24xI%+a#E45jK3YWnT*fQe>(1pa_xi0TFgW z=cj2BY9=y;k{QE5iWhf;C#72KZiLE(7Blp9M{Aiw(5_XjCaDIi5p7nkuTw4J?|@?O zxiXm*UhaaQSvn>Dmr8mtDp8yGGb-*;v`-@S&IMcIiugEZur@#q1uKuvPKOo@NuYs} zLoZa+17VmD6JeD_zSH2o`x7wy82S0{BVgL}-Sn-x1cF-|@uT4uiJ!I;O)>qzumAPv z;o5@f(Q%eyUu}gcAAe9@qQVnrUBM4SzgCkuc-*?h!P_=ssI2Fom zy_td##Du>hGenM*`WiefDU}7B%9^Q1*l_M>5`Y;^(I7&~j`I*afaNIHecw~f-F{$3 zi(&fmXL!EmW{I>kZm*+CQ?sb@3=j}(KxctTs4eD$X?9_bLti8gcrm(~(GPCT?tcPU zw_o~_nQ3{)Kls(xfif8S;l$qRVZ-a46(O8AUAuv=CC!;@ax3?kTiYt=jWL~Zgftsi z82-f{wj$PTJmp-ihz^CoEAXi415X9AR_kpgFJ8Qe=yynB60bvOJ!(%SWZiu_hMWVK zxH>z7;T-w~_S5Rc$SPGEA9l4lvh>fX&0yKd&0VhPki zGz_wI&(jI~7_brE1l)1R)l?5;BjQ~3v-z8b9?i8wva9V#x14?He3@G!wvWLS(Ex9(hZTn#)k%w{< z%KfML2gs2h`lMca-=~Nu&7naFG|DirOLBIoj{4(Z-cHE8Fm0tR>`TK zkI#BZf4^r&Rf6vZIxpv+D`GYxs3;Je83+9`oxl9Kz?G;~l-xH)7mE$_S;``{4z9r0 zbpAQg|0MCn(N26u(dZc^Je3CpkDK!DxhV(MTVUe(NIq9}_H99RW7;%xOG2W@9zmYy z#k^mx-dlIdU;lJ^{JMX@b8TIh@?i)$zMMM}^73&{1}xn-<|9_NGzzPaSs%vPC0QqV z#199|;=Cz>Uuvo#u8p^6IwL2pQOCN4oV{k#lUU9%N!gSG2VNdihw&QbicsVkOf)wq z`5P_HU_uLt;+Q9~vhNlB+#K@aFh(nAE{r~pU*WBK90vo49!Fwq9-uaiTsVdH`^)L| z(P8iG>_K-PA7p0V51 zjcV=!CUVgj0-`Etq8T%*fC|9GFI3A~$n>B7zB80D@pp4Dqbs z)Fp-yA<+MnMPmWi=6FHQ23YQl{ zSK|0w-scFnw%&?(rSgIb_qr-2Nss}vo9Q`lJvT{XHlAwc2D&uy&B=KB(_)$v1f1Ye zm3SkF0S)%yXv7&L6rGttRq$2(miaN1a1RWji@d};( zuQt>r|NOhRLTy&1HKXC*M``ADW{+Z(SyE7$-J$t9k7A6=?`^ z*%HhMUrSHKf=MWteoFi*?;!><;#QJgWgd; zIXympl>jqEiWZFb$8<{U%vpIy<%It6RB>7f-j6&zO%HCuvFY9IXhsialY^3 z+pW7c#qXq_qzPn8Ew<3#IqfIv8_+3J(^-!8Rhqt_#wgJf63j3OXE)s%mufW2XCPKoO6syGxy&jJ&s}Y*+L$*; zW8+ZQihB*&AXi6r+` zIy+70&e6fEoj&R1etZ2UT^dPr#pl$U?h$D>`FAnm^xnEnB9_m^O#u(>cq)gdsOHSM z-9^1-5PrHzNm0bAsp(Ks*M)X!xpwYWd-A76t!JEnu0ri8@(d@3r2GC(k%+2 zkt!mXq7N72Yewmcb1Kv*{;1#E{2rhL7nfi{s+&Ff^<% zHEeHls!%jEh=BoZ4JdCsz|at~tWizd#mA!8oYZb4kHgQr4+n{PD^UMw?a{^ni^TvAozL7v99(18l9!~J0QG%ob00Z zk^Ks746$kw2$Xp6@o9Idc$&%6HXM!^--grgxHz@nTKmW*FVg_7MkIbzu1L!x8^_l0 zwX&71Qbca81U-Hf>meyn`*%@BK0v8hP)OcWky)*bw7g*zs>s5g>EE$uzF&E1I?21$ zm!jwV&6StY_6ht|K|BTC=4v#}2xGVtVK zVBzUqaK_?$JA%Te$PTg_dsNShWV(da*-YG1W~78igXfqnx6)(1?9&QO zH-gR=01IfWd-&>urhHTZUtu}GK~RWeH=~Le*`B9df38l zu5#Fqoy~&o9VdYQ=_30;+=7eu#&WzsNb->sz13U4qv|M!&)tHv`2Ri!^noTSIk-h#?u+Ev)2C0Ng+Dzd0KiuVM+dK7zmfyd(JYF{ogt?+ z7um7=tVy(~VoIrrJIy!99Ojvs{dNTtFy2-fHLH5Joz!nKHI$n;x&Dj}R#7RZP0`JlHEzKYM*U)X+n4 z01k^nj@rhQCJ2Nz-vUl)XaaN1yEYi%GBiQILkl@y@34LLj^T)Rau|CXiYjCY zYACxWxit$8R5v+5^Y@Zc0z~AVNU7?~fPV6gP`)}%pQkhgOuwjrnNJGq$@a)Fk@s6B z1O0#S?B~AR;G6-2>0;agu4s_Sm=ADHDRqL-AW{+NqzkGV%ZEwo5=7xXuSk7Fnk>Bt zKhD#y8Rmw_#v`_bUtCL~DNOw&PhvM4PA)2fI%#Y;D@(oQpGj}vA+QwBY!co*1^EVZ zWr^x+N5M3k4X&k;bNYIekGZxjp$C0H(X)j7WSD2H0-0{47Z+%Il^nX%B>vH02A89K zh4=?*M*xJ(`pR9o3Wm^ALDq+Eh)-pk%-lc)+e!{e6WJ{#ev;n2*5fDwSmr&hMaTQX zC|;?>x`gs^jd|lr6{CB9ql-)!Su!FP66dUFI&qi`r{oePffDQtWybhb9x7i)tO*$; z>=k9<<5;b9M-u@M7lrhAL-@GBV3l+?iAC3(@;O&EF~+~BHvX6J9i|+(Ut7#u%NRwa zzQJin(z$`Jd3J<~ypuV5k<@YZ$zsV2ME$)^K(gNGHY5?BsY^`!RzT9h46pY&GVcki z&`}MEn&geem{I6+`gLnsh9vrY>_Y4UoKs5|zUu?u1|QXJR}?edhB;^iZn)vZT{*eJ z{m~UchYy{@)IbyCEpW{H4r==eiG6@KjU+mWzE`>;(gmiA9+i7?vrR6g>@10zW<5>G z(Gpt?a;G!NxkPfc>1@~@^QeY6QEYOZsP99>k~^nIN6*7XXagPTcHu3@(AQxz9CI9gA@+N0!<(rH@yfE zc7ODrkz^?=bq~)b2kE!r5&(7FQ7Jnk9f37P-zGYi%sz41Y08$8lU&9hTdaTNd9i;) zJeZiJAMmwimkbp=>;@^P^ML@}d6Otgg2%KJ7hFQQTTs?{CTVbT8`eRf`x~+~n0!bR zj3$2ij<>pzcuze97zNGKyo2DV&J7q`-L4qHQ|Kp>tq^NYh!ZtLKuyHC5j|wJCw~_M=0cGn*J3!fx2D_8+T+XpL1A( z2WXc9N1)E4K4<_x>z$q+pKc~H9*VoU8c#*D^}{v?1;|hI9dI2YYX}r(6_u8yUvfZ0 ztxaDKnjwSoLkr3HBpf~w>*^TD!$k2=li_m2>6d>OvF`YJg(r83 z?|7&ujJ#D?h+Od!FI=_IJ89Mpd@VO}au8n55wP3_&yhdKl=$I#U!D>;)w1FGp1Sdd zSx{F3-Oa0a*2S1FwU6l>XtS9-%eJ)JuB>_1LouF;g9mDlm4N$@28~kDUFO16sFB^B zBLxt3yDkigYsm_ME*k-;Ws#=9qJJW6Q6RgY6U_{0NwmdNR=+MMS2@+AH2OngV<;c1B2F}$>t)H4xTrq~s#OKn>1cTcQvx$b z(K>6}I7T`rguc#>w%A6AH{Nc%HdFNzt_9L<6& z3hi>JGphtheHrUdxyp%V3?Di*=J(47)8*C2(fpQg7y0A~2u9=iX!rz^%+c(G5L~J* z-~*Gk2EErR`2Oz`AVf%rX0RkgrEvG%*N2J1VwB{~3{fm^(D8In zau^da$cL%IfJLJbIrT;#K2kla-Vf4B6v60suJkI;mQ-;hr@?u3b@7s74#eJ34{5KorUZnryv%T?++sJFk8X>?UDD5;2Msv(2^Y&{l}<( zGyCpa~O(GGn!i>qQUM$1|h>fuKS3aKFSk8!+Qvfb|WJR2lX)B5t6|9}r{HkaV!MIpp z#bio_D~H})3WXh}=mG_zYA`kg_*okNwtieRfSZec4kSW&MbV(!VOKGLb%#VtD1Wlc zzqn?-$OmbZf;9W!Vw#dWLJ1gaV#`^*XpG9DGZs$}12MmlNQ(G@rp%Ia%Fc<;+6g?& zZ^u@`tzg0=zBot2Pxm_6?b-qaN6gd5B1@9g7AOTTHhDwR!**@3K%^LW=$}ziBPfS$ zZErUyJ#Yg(jdQ+In*|3g<{saZzuF*M^idfHoH+dR?kPRRHIt{`V6(yCINGDB#k<@( zijvt_Q%qq6-Ptm`Y{FM8OS+1vdit-X2jn33JNcfq zM&4sXvIzVMO?ns~KF$oooKk7Q-v5+Nrvr?!U6TdzYXyYiPpyyuoo3MQ9qB2&gZ(lb zeO!K`v~9HB40%I?lDuDcMZFO{?g~YgOX03)H#*<}-RrV6hf<|pDAt7pHH*X^x7@Ad z1J-<0N`Z*2Ro%z;A#jRA|9#O?D}$wO^AwQ;12oJO{E&6mcp65bSC8LVi>-^wn z7_W)p`wNiw2ILtyQr8r+2#6HuJ&+p?hoMnAr+FLjG!2&!6rPN~j3maE4O;9QjzI@F z5WJ&SGdv|Q536t+ssW-a);-o$Rs>Uq#rZ9;dGCSD>-ZXF#dLpGBlWJv=`BO)E#-QW zN^Dp1RKnxIQ{QTv*ouF7YCb;&wIT{Lq#f0%x?tZD_1zWBn-!tJZ zb*R5o+GMW^;j2RWDi^=X*BV;JUjDl*$c>#k~ zmeMP96r+GG`%KI^^vD=e_3GrIlN*Gi0!>5(j6|i?+WsBE$PhZRtuVmapgj3d=k1Y;T(!a2$T{v#y6zkV*_5!(2j3N*f9~6 zH<{aDkx90UK}mzb8?D&3R>7DB8|d#R*W^BFMLW@Ul!CB+<7qEYfi8B}44pu|p8A4s zTtOUCc|qKaJ*6L9Vc4JY{*Aq-dZPERL+Ep?#y@-`58uc`Sp}Lj8m}&vYdAWgR3j9^ zA%|I`_C!g+MLJlZEq`;(hR4n`n{=dq9sF3|-LCEsjkM6AH{78&afjac4!yw+y=lwe zD?9WCJM<>sAy!d*MS_4S(?m(aI)4Io6di>V&DoG1AaPCOG%O4fh?v`i#>wJUO1Gtwp{6-pnQ`Rsp0yq37!XB-KjD@6E!C-+k zeG~DY4L-1qRTl7C6e&uo9@fB2(sULtj)&(DmOvy7I7h@Y6OTY-oh(GSJWPw)348xJ zrIYwFcmnLNgsx}_i3V(lE6mDkq8wZ{=K>@{D!*Hc*GsGHIxW=t&$}@LjV99x%_W5w zeoR5af{w_1h6xUL3zWkfBXkrVxT}Y2hzjtdJFchvz=Ic!UAQ$S5l`>9%PPE{qPwQl znaJj{YH!>+p2;c-z62&7uppYn$66JW=(r>P&CZ0ydXk;PLx%_$Y%#U_>MM13JVZrl zI!BmQ$pA{A__%wJ7s22Q)doj?6eVfge5zSvsPed;21_yLDE19bD~yR9*(kl?5}K4A z%0PXqxZ9lGKhG3bqQw+7E#|@QE(^gM%Gx;ys3LKTTo?+<7NWqh@}BEiF==Ib`dEz+ zDXR;FLSj^>FmL|r4T@C7e(2CG^jKqGsd2mgD*7taFtTFb2kJsZMZ# z=@d@;`x|G#)b2a*rYFgfpxoljWtts}=nhf$2F8LmfaEmhcw!so2t}dUOwVv@n3)Fy znJ_2>QNhs!85{5iQ@Hw1cV6}Ez|pLs8b&i!V+7R&f@g5wxz3GM(h%KH0t~8=NTg4a zJCbJ_<;;&5R-Fqh-3qTUzI8kK2#bUhi@v5GE$npdBT&lgS1`Ae$hGhUx6hmfswP%| zE}*n?>2Y^4_=J`M5IA|@b&bQrgDu~(BRh?^-e&Q^-nHYf=T^RP3bQIUPLInPcWrZ- z^0oz7v$SpJ3HEyu)qB#^e+Gn9X;NHPM|~Ynhv`?Vm2^%vL?i^u4DW38Wiee&n6>R< zt}`BX)i6KcGwxDAAFia zcf~WOLC(0ie2dG0VlvZ?%P!~3>F0go>SFu{QXB7!mjV}iqJ4}UqU%(*O8|IpC(}0uD}02 z&E+&z&lz(c%mrBRDJ%qI@M9R0H~qp-ui}f`jff4Qzt$*l$M1#kG+z(s&co*?O1rEN6ur(iGxygi8DtzZ?w{v_CC!93XFe5vacXwe`n1l zww9!5FI4N6LZ%pL(11C#lOamZm+=tIv9vtl2H1ItN<^WDc>ax1@qky(p-%ch*h+}9 z;r2CIY9=T5SHzdV$mkZ2_O|V4W4jHeB*txDPJv4sPX**-noc{!UdK6-%5P$+v2Y*f zo(mL_)3(Gc{C#h0{G*gKMnti|6u?74%LxyxH?cgCX9wQoykhx#;sF@wlz>n;A~FgL zXxmmYzTnXvUac`Mx-d=J61(y_M#In6Ju(;;J=%8h*>3cVFub|{yLw>+#BQQ#5nyDo zC!Xelh)WQ7$uLn46&`W*%~sc0_VOVeyJWiGEq=aR{dt*pv_`Z_#HwKa!l{mRkr8;( zcuG+kN{}(=hYUTY?J8TtZCS)@g}w1nxk1oSZ@pDf!F8LAgeyR}E%wEh^26X!>?G(i zE24dJU(d<=85bSpQbz7ho8s^~CT3<%C~ncxJxYLyRrbU(bK(`{me=`2MH6bhc{;8v zA$3GWam;J52kD=p|43fAM=|%W2WJPEr%^4SDoH^fVS1(Yhw_^D;R(AXE#whxYKmPq zA9Z--gf?<b@G7)?y(aD zLbeI!GYO6a9LvX#6`FKzZwSN^#S(4Sg16dM8$!%Y^lQ?zqldRRsmnAtMb}lAMVOb; zx)@aqR7$>QRH-_btvi)izFa%0+m=yvy&JUPo=f-H<4(WTH7KuDn~nmAEb%U9J|T0< z&PVuc<0qQICm<(Imcvo@Bt$LE7@Mk}&NhCMx&63Y(6r+FE*obuJ*B8D-sfy}5h5JS zTxgE$f6(P_sJ!LMaA$I5xbc=hfxEdg$2(8d<7=em-j4#xR7a>ZP>8wGZ*-SuV?8_W z(FsJ}d|%R><-B^08$NdKb&q#RVZLABNwp&3l+B1hr5P<16>r!HXEC|;lETCFAO6sL z#P9I<-kK2UhY{%yeCnEygZ)xS9=TW-*RG8h%{&_&OktLKyk)Njb2#w)9r<0W~Rz-~glD=`XPSm>q%_YVnmHMF z7J`v7B6!=_*=T*CUg&c~7zb?Q6G`Q-?tDc>c;R>>1VkmW~{ zi}H?XGrR6sR%u2BTf^mh7?=e>lQ?N8Y&tjj1-(=lVJOJmjHo6y?xOFhY*E~BK49ok z>@KKMewkmO6Apd_w{E<(G*#rSALNV&{1jiR$}-=VX)dtL=34gA`t)_237mO`B`*a2|3PXvmAZdoxP+aNpa zE=@t$4kdaaky^HF+ys9s^zSSC8HNC#H-wfT-*A?(!TEW*6tG3%f|EZgzns6C#XEw$ zDhPLh*PLq_ju$cq@CD)T)(Th+C&1L>E5+-*Z58$c&6fF(RLzy%B!04lP5<86@`HTl z2Kn&ma`XPw&3pB15n~jiQTv>5Y6M_r9KU9~=dY-aV@~CW47k|U7b+=8wZ%2a-l^sJ zJuC+lDp?IZ6qWg^q6}B5WJPo2Af)l)m>VKKL>$N}?NK1f0rS$}*yqQbiKOKu&c2cn zc9!@$xXF_JgS}p)-)37~lt3<~I3)-aQ1b&R1>XibAaWOl0fq6^pFMs0?{)X=N83S; z5$4jl4JnisolBV-!Gv6|tzvMY@mxg@WF!0I6%`8<1AQKYw->`~0uP8TD?7Mz~ z?9Md}S42!LNkSzyswL%6aWy~Eoon5{wyB^dD<=`i%(ZIudYjNNWnnUotyc-UI&ho6J`&X3-VvJ9Jl#?$^9&BIo0mfVzIuz<=a7Ec z<_-8&yc{_f&z5`Y6%?3*04@=~xoL(wSk7}Gt&Qg`&CpEo%_G|u`5_euRPN@cU0_Ng zTaY`pL5|;00rvhIp^Wocy$@)`!ZtWLK0D|i93T0G%I%qdwdDoAA-B3bZ((^&{C?2- zDnFsT0B-jpKTw|XGS5zsZ(vZ%qcAKs0rD3Ser-dhjvPxQ2GC%~6eh3S$%x#@QhFlk z)d|l~qWmi566II|qY>EBNI}pR$nRajTBkFhZCRRXj9$mscoQBgL3t)v`Cu4UVd0pNjn@iJGgEIAXuFiQ@q zIfA8wIPPpQ8VpgW=7XmZ7@STqW9N7nPo~#vWnSX4&5=)o5DxH}WW@M&2lzB`3Z>Nt z_*B$KZ0B!Se&YU-*Qt~)kQKR$i;^nENxI-V39LPEXtiy2x7)*R%?`8k@h;TCC*KD8-wuZ0E`h7)pcQ1D>; z$AKtl+0P9%Bd%~e;p9Io+4%p87L7Ml;B-8n(#) zYN&vKcEX;$=W1Ai@O&$N`#gI4OnWh3P9+{k(ecoAsKBr5nP8MHG`V@MW|dEn%#J?{ z&(H}I=h9q*e6_|V(E^cExlMiLm%6%Nv58n(wFsp0u5~X2NFivr8tb!bS~GrERhCXhN`nt%NB& z3SQGHCTg!*N%&h5?KK0Yp1ynT$~`YDck1dpT~XhutM9bDzVN-fVQAc;r|w5IfifkF z`Cyt|gmH(JL17&)CJM#ty-CCy_w(q6=7zc<+oP@9Yb*EWd0RJSyaA@mS(1MI+vuF< zS$3OkA`ZpRNL@Oagii_gfi2Dr^-;0p9U5O2X4n3vo9@GTSH5wReBvhA{w^sl9pH45 zH|&%JcPvOP@X7gjlySDrA)*f>wcu;gJMli8PCkJCsh3pQOCIe52wh2K&wXV)HfItK zxnO|QB+aR+h=q(1sVe%yP798(^2Ad;bW#PH!UTI$)j3$}X04_M>l1P!Iw2f6;6tEX za5HV!cG<-U-|iOQ(#&tVK&@}Ls$-bGSKGWy#b#VeCrHb&_9#CmL~5F}u}dO%=y`Ob zhnXU*AS%#3(XoU_yV|!2g_h{y>Dr8A=9FCOlmP2miPB$!(0XprtcaLQ{(e|vN5;>* zRd%VWMHmbE)(^Eo@*FG9E_KQ#xEx0bnmg_FcMgsS&#q&MNMHV(oQ)O>V6ZT)^i`_f zv{Ku}H9;UHkr=R~ALQdb$$GoPpg zOoE66N%Vjbsmqp?*FqOBeLOxrJjef@j{fc?W^-+aCg}7vcUyY+T%QS*Lv|ABhYdJC z+7J0n#G74;z~o;m99`I4g^kDGPzgV4!a$RD8&{e|2t!xJoLS3phbM&)>^cN2vc3Id z{<+yf<|B5IO-@Ijde^gNO2={+TYwepz@d_33xd@r^>;q$S{LAJ_Y3yzY%(|>HRt3Y zo3DyBxM4iT<$M}VM|0c|cXK*5?{p?AVVoM$%E|W<9bc#@%CaH3fGJne1@IM=LvN1R zxHOXkswY2>Mzf^BVwxlru;@8rrO}rPy`h16$xM-Zm7OZOfQ3zvU4|aGa&#U9+*|^( z?6nOFkt|hja8|V(%sr=OBB|VkMVc4r#d0FC}cmz?+kXw+2O%n zuk+Kv(cZz)pU-S*-61T1;^d(wpkF(bIio6-L}4y+5uZDvDvIJg@7A3fHil%`lT42X zFNIPOUeL(WkmwZb_0IaI$8Q-rk!Wy`(gH6lj;NvwA-wF~(O!a5@m>#R@N@?ak!Lyg z)Y$^v$C3GFzZWq}(M`6WF5m^5`7xI_%l8+_B|o1bcIe3Er6VuXVS2vAR}hZz)ep&^{#1-H5}$RAm&P$} zCS+G_C;gqDC?9Vu_YxqZ`I-Oe|K3c*Wr~96X9-VBtpntLIo7UpUn>{&Kc2z|9B@ql zCOF3OPxg-A9LYz2`cL?X!m-INjmGtgj}Ch;`})!U++>(a{^*qK;^kkR9{hQ~AI@{T zwsY9W8w@9NFj@R%bR!8+Twtw)lFNLzp@dXy+H@p4QLwRw&m`}p<*QaO4 z=oj0>5_s-tWq#aF{`8;r>xb>+e>_z^0x&b}MEt=;U7(A~m61c2+|{@;@34;d@4Lk< zN+uvq70YVJ4sTH!_KZ(Ss3BiL)*GMW-?_&;uQFuqicTlj zPYrywU9!E6K4nLPqvmI->ND<}{|VQ{XOJsi!{sJ&MHd*XIqvZt3P9Ne=2)8f3x&J+ z7IYUV0+`8xq7Q#{6*fz5O7Y+)*Kx-Z!#$X^;Le;f&K7!;d_>$vLX`r- zfX)(aPH%hs1>H_C44qKBHGy?}4^p3wwb#wj1s5?|bYEI^j@|jqMPHm~9FzxtI5_(24ltK7Pk>vCG)qMxR<+(Gn9PAtwYV%Hm&o6fhA-YfaS#EleSbd$u9B!eHD#>M z2L&dc4Zf(+lRBd{Isu0}sjkcfx~7SW(I;ZLH><>tn;Zg|ICS#E`ILP?c3atg1?~fy3wj= z>t|~0=hD`GUe(-NY3}WVntQvVxwmD_$?RvEzfvsFC9~c!ZI@wG`2W8Q|4+IMEqj1F3(&B$65ACMHmLP77vi(# zF9DrzCY$_Wy`7ZO?>N&Hv}K#ElaHFUvy?kXY-5(mD8FiyjW$koe>9mPPMhv?*nm!m z0A3T_mCr}xFMLl9hC}9$ljQ=PAIHvhr2jQlUujbHL0P8qIBT-2+aZpJ(88%M((1B5 z2GoK+k)<^Ej?|%2fQ6XVWzb5o zx&$!_c~(97)?}xcU)GpuZ{5~1fi(2%)a1L5e6|oBu}+@VA}$d~nnK zfd};CZLM~2ftc5qx@A;s4-3x%FCLPHj<$hLQ-eh2;OiXM7(?KR=6}VDM&WZ%g;)?z zK8+SXEvCSPNp)>a3rDX5T2}HHvjRM}azY74^SR4PfS%C3`8=J!!c+E1(2tl5^^E>- z=!XmlmE44jZ+8n+*@9bZG+#8~e*M&i-*Q;i6MA6lyk3XpT(@-(Xjg%T=hQW0q!-5* zqKDqzPCC!TvzQf24Sdmn8rfJldO)veBS?>e!Z^gb2%)VBGVfxVcew1jV=TrQUIej- zNj8Qq5CkgIq)OK7*dYI<_MD{5B8Qu{9}I+cT=UUHEJ4fRA!jgCC>j!}H3Kr4v9P}!`U%^X0t-;ZPk-wH zi$whY7P1lW>uS7&6)#1<6rV_(5!qVqRB}C$6MzO@@~hY!Z)~vyU}8_}=*!@zFL&r$ zTN)f$bKjC#S-zC1F8R(!RZ8e28JguolHXRf=stvNhCajZPR93cPga8vHQ@Ud<5WXU z#bB+9>JjH^&~qT%uI}_5I9aMmPWn;SI0hn6{o=pSj{PmKxnfG>8snYRYHESQpTrWh z)vYptFJU`UmcO`$h$Hx3yAS|+_Bkc0UbKSuT(>(ZIxLH#4_Mxv#U#Sq^ihB?4!o+dmKL`-yqohOaQG| zfa4SFzvBzaqomw<|FbR_4(xKafo4fst>9Tnhr|cTM-$PrIVj6S6gyAGiw3!#T@5G$)#OI2A#@n1B140+B(xcz*1}Z*gNCzv1t)$y zo6sK2B!QYZJy2zabB6CFLAo84R)9e?1-!kq*);fTZb-Lvz9lQAXFlb*b>u0T5baob$_3b5pa@j6KQ$WQTzRP&1S(!dP}B;h6TR{S2Ra)CVH zR5E?YG;W=0mg8#n1Gl(2M;O!2gsjQ#eA`>y-&}hzBIFmGeFE4-LwAePz;O9#j?eJ| zy>C9#z;Lm7(7ELaLMEnH4x`P@YhG3N7>aQ?AKVk!%voJav~iVdUi-~g+!*MR#a2dI zOwr}*9aaYeT*lLQUBaEPoDmH}Gr@Q&{84-rNC}`(ycf~qho5b4*YTeK87@d&Y&gdl zP)!r4ZnkEc7*7k%{&~F__TtgMxo`!XgP$r!Y<8m*Xt;6XGz5BEe_$181MV;2P3;}SV52JNWBowvuY`^i}!(+IuVIokPi?^W-p9}djudT@m# zUQ-QxgM~^zk4Kxyw*>WxFv6=Fj`V`Duq$R=@~%q0c1a7nD?paF3>~tf?hhY|HS3#u zwUDb{tLXwi!$c@w0jDS<|7p$`roLNs4pA$s3~#Mn>eU0PYbW1E*R%0_1cUg`+v>`P zKw1`BePyXhIv7@zAy_3fVue0SwkG^!O9+7{1uz17m-WaMb7=6>27EQnBqakD(^50w ze@64g4dswer-~lVZVK$q{^AAU4vN;eIURf%1H+WgyKzlx+mbGmT%O$>aEc%xe^ggs z;%JD03!TAyfLZdt&W?`?9K8B`VT4FlpntF{c+`~8BMStNj#U~(g(NPHOS(?KGq!tb zye#ft$_0&oxY#)RU6+l`#VPK}{HwWIc|&H`b2!WAVx@0vHP9vUofu$egE3{cBA-*~ zGtnnx$C|hc#@kQZ$#nZ%v8}g~aq>d~^mn|zt`{`cf^@wL7u8ksJbO2O-_rKB8_cR< z*S87AEWzTB%r5|bV3-`xdNVL^)?-{7F*AfQMft~N<@kO~C>;)Y++7YbL5b4^AE5^+ zE^br)etmkdn_kV*Df{9>A25W|*@NWEdcB0B__JJ<>hU4HWGrq9DngcYyb%9C?R>dh zNPI#Ic9%rD-gn;x0+KZGQ{G7b4cm`-5cr_c;0I{Kc0b8FUfT{~kapjvU7=>^z}w$F zqY2?Vi|pXo2PqNzS)iR7xHx-@yQjfWhYj77QUf~C>><&1SrNi_h_A^zWk})UqXm_v zAD`q$D_IXM`Sd#9;xFIAFq_Z*=YLwlUUmy`QTr0Isl&$)TQ!-#WF3aEC9P~X))T7M z&5MiG#+a2SyC!cA^9u4LXq^nKI;}P;lwv+s| zk(Nx`YmNq#%R*yNb8|?JeW{_#+&y(AwvoYfd?nLfm{^h1@auLW1!b1#BG^toFhoN- zzuH4z0G{MW|K@Xxaymf^hNTaqh{t%0!U1A(q>$w=%JS)u2*ll2Qj_|dRadn#!3*vX z7Ckvj<|Xj`Fi05Fnv7J`iT6$xn9s1KF*n&e*7Ru9jlYwS32ksVUL&6$6G&29U86M^ zG|?26dTY=F93G1U^7zsovn=4Xmy}@%^_Czeqf)NUdBA;ar`L8~5)=*Dqp@Pd#S;&n zC_x4hc`C}dX0;+{-yo^@f-5h6;*6#L^FLU1qv+ZtO`JJN87fRMDwK@A)k7 zhxg>i^4z{0A-Y~-ZVQQ`^Zg!777PralHF#~2W*zAq4@w}CfTT?%cSFYJLE zJfye_bmML3_&U1%G_p&gZa&^n8eRSTG02rPSRRU1g#UE^mX4=%9@Gz0d2T9%e66Z= zNeRs=Hn?9f1R5~_h^#d}e4rSZMweJYU%!T!Z+!T$@u{8EyLFQo+6`-z#1i_9T4Ddg z0bf4KtVuH-BG{VY=k~W-V0_*IIbXbk?z{(4)BfD#F+qdSY(cWZI!bOy zW_HIHu(3!Qr*&4KUY4t-CRCx0cKES#F&<5Zg`*nyw4{mul`SC669jsbo8ZN_59t*U zCV#K&ZNGcpW?}dEH#G$t#5tFI?}fpjG6^;?OSC{xP|LCIvn z$3UTIHDWnFM7HO7*|54$g-r8eenUVk)E`bf|E@lQg{i-9)|=@0xj-k-76+rEm_j2U zeEpz;Md`d&7V(~(Zk0EZ z*BM>}$S9*1dvY)Z+B7{U6ZAmu=&$3&goKYY^Hyke_9+1RL2^6e2@ zZYe^FWgP9c@j$z|wxHB7E*TJ4uHXg>c zET`kYFHPV$*xNwiozk+QLo&Ke@T>pvr~g28m2FPpCBmj;3?mE!x}KqD6!91eG?1LI zih4XEsR>oqAWzJ1#K$&yHCkMzLtSC3blU_ghV#BYpigCjE zFRuBDwiMJmBX=l`GYH9=7#xIrbodOTAh#(3U9Jv=gKd+5Mf(c=tt^Cs9u~rXD+`fu zw^al>S|^bNX+Nxv8w7 zAre>|uhA=aV0I>xO$Q+vS2RsouZeNSna!lO9tC0E@!b&i>^!~Mtm$91yIMkT3!YN> zYt`|P0iiZjJ_F`?F~KMZ@J8!YPmvPao>g?rQeD@6!>gzWTKE-SD}%A zEP7q!Oq^ymp_*6?&sFi?aa{@z={4PEY%es#FKqV*v&5#iQN%4M`pV>n$Bt^9&IE)e z))&zih8x<%rdl$a!4R?;{R-7n!C;Hnthpb*;W$kxzv;{krOT={Fu4GNgusCKPt-i$ z%~|$_Q&iEPwN*5OJgo^CF$qb!w3dBMnvH+wtMNPAXRg^y;@>>wadw-{B=^A8qnSgr z9{ug>31xB*Ts@XfK`XKcz9v^H|0EJ;UGy`@hbs4cbo@pH%MVy8g8|w(BYHZ; z#br=;bI}Wad+cZjiz#4C%9vw)91NJ3vlV}ly!Sw8<{CPkAPRRBa1+KgB-m$?NSl(_ z7FA~{1=Et60sT!;rwa^5hst3V$hm7+r5bOZ^s=~7t4IHK3;%C5)upku_5T6VZjXiR GOacJQ->!H7 literal 0 HcmV?d00001 diff --git a/htdocs/includes/jquery/plugins/layout/jquery.layout-latest.min.jgz b/htdocs/includes/jquery/plugins/layout/jquery.layout-latest.min.jgz deleted file mode 100644 index 49090eb197df3a6edcea5be6aa5ab4baec95e8d5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17060 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 Tcm131MeqLts^fgcefIzWayeiB diff --git a/htdocs/includes/jquery/plugins/layout/jquery.layout-latest.min.js b/htdocs/includes/jquery/plugins/layout/jquery.layout-latest.min.js deleted file mode 100644 index b4e70826459..00000000000 --- a/htdocs/includes/jquery/plugins/layout/jquery.layout-latest.min.js +++ /dev/null @@ -1,1226 +0,0 @@ -;(function($){var $b=$.browser;$.layout={browser:{mozilla:!!$b.mozilla -,webkit:!!$b.webkit||!!$b.safari -,msie:!!$b.msie -,isIE6:!!$b.msie&&$b.version==6 -,boxModel:false} -,scrollbarWidth:function(){return window.scrollbarWidth||$.layout.getScrollbarSize('width');} -,scrollbarHeight:function(){return window.scrollbarHeight||$.layout.getScrollbarSize('height');} -,getScrollbarSize:function(dim){var $c=$('
').appendTo("body");var d={width:$c.width()-$c[0].clientWidth,height:$c.height()-$c[0].clientHeight};$c.remove();window.scrollbarWidth=d.width;window.scrollbarHeight=d.height;return dim.match(/^(width|height)$/i)?d[dim]:d;} -,showInvisibly:function($E,force){if(!$E)return{};if(!$E.jquery)$E=$($E);var CSS={display:$E.css('display') -,visibility:$E.css('visibility')};if(force||CSS.display=="none"){$E.css({display:"block",visibility:"hidden"});return CSS;} -else return{};} -,getElemDims:function($E){var -d={} -,x=d.css={} -,i={} -,b,p -,off=$E.offset() -;d.offsetLeft=off.left;d.offsetTop=off.top;$.each("Left,Right,Top,Bottom".split(","),function(idx,e){b=x["border"+e]=$.layout.borderWidth($E,e);p=x["padding"+e]=$.layout.cssNum($E,"padding"+e);i[e]=b+p;d["inset"+e]=p;});d.offsetWidth=$E.innerWidth();d.offsetHeight=$E.innerHeight();d.outerWidth=$E.outerWidth();d.outerHeight=$E.outerHeight();d.innerWidth=d.outerWidth-i.Left-i.Right;d.innerHeight=d.outerHeight-i.Top-i.Bottom;x.width=$E.width();x.height=$E.height();return d;} -,getElemCSS:function($E,list){var -CSS={} -,style=$E[0].style -,props=list.split(",") -,sides="Top,Bottom,Left,Right".split(",") -,attrs="Color,Style,Width".split(",") -,p,s,a,i,j,k -;for(i=0;i0?W:0;} -,cssHeight:function($E,outerHeight){var -b=$.layout.borderWidth -,n=$.layout.cssNum -;if(outerHeight<=0)return 0;if(!$.layout.browser.boxModel)return outerHeight;var H=outerHeight --b($E,"Top") --b($E,"Bottom") --n($E,"paddingTop") --n($E,"paddingBottom") -;return H>0?H:0;} -,cssNum:function($E,prop){if(!$E.jquery)$E=$($E);var CSS=$.layout.showInvisibly($E);var val=parseInt($.curCSS($E[0],prop,true),10)||0;$E.css(CSS);return val;} -,borderWidth:function(el,side){if(el.jquery)el=el[0];var b="border"+side.substr(0,1).toUpperCase()+side.substr(1);return $.curCSS(el,b+"Style",true)=="none"?0:(parseInt($.curCSS(el,b+"Width",true),10)||0);} -,isMouseOverElem:function(evt,el){var -$E=$(el||this) -,d=$E.offset() -,T=d.top -,L=d.left -,R=L+$E.outerWidth() -,B=T+$E.outerHeight() -,x=evt.pageX -,y=evt.pageY -;return($.layout.browser.msie&&x<0&&y<0)||((x>=L&&x<=R)&&(y>=T&&y<=B));}};$.fn.layout=function(opts){var lang={Pane:"Pane" -,Open:"Open" -,Close:"Close" -,Resize:"Resize" -,Slide:"Slide Open" -,Pin:"Pin" -,Unpin:"Un-Pin" -,selector:"selector" -,msgNoRoom:"Not enough room to show this pane." -,errContainerMissing:"UI Layout Initialization Error\n\nThe specified layout-container does not exist." -,errCenterPaneMissing:"UI Layout Initialization Error\n\nThe center-pane element does not exist.\n\nThe center-pane is a required element." -,errContainerHeight:"UI Layout Initialization Warning\n\nThe layout-container \"CONTAINER\" has no height.\n\nTherefore the layout is 0-height and hence 'invisible'!" -,errButton:"Error Adding Button \n\nInvalid "};var options={name:"" -,scrollToBookmarkOnLoad:true -,resizeWithWindow:true -,resizeWithWindowDelay:200 -,resizeWithWindowMaxDelay:0 -,onresizeall_start:null -,onresizeall_end:null -,onload:null -,onunload:null -,autoBindCustomButtons:false -,zIndex:null -,defaults:{applyDemoStyles:false -,closable:true -,resizable:true -,slidable:true -,initClosed:false -,initHidden:false -,contentSelector:".ui-layout-content" -,contentIgnoreSelector:".ui-layout-ignore" -,findNestedContent:false -,paneClass:"ui-layout-pane" -,resizerClass:"ui-layout-resizer" -,togglerClass:"ui-layout-toggler" -,buttonClass:"ui-layout-button" -,minSize:0 -,maxSize:0 -,spacing_open:6 -,spacing_closed:6 -,togglerLength_open:50 -,togglerLength_closed:50 -,togglerAlign_open:"center" -,togglerAlign_closed:"center" -,togglerTip_open:lang.Close -,togglerTip_closed:lang.Open -,togglerContent_open:"" -,togglerContent_closed:"" -,resizerDblClickToggle:true -,autoResize:true -,autoReopen:true -,resizerDragOpacity:1 -,maskIframesOnResize:true -,resizeNestedLayout:true -,resizeWhileDragging:false -,resizeContentWhileDragging:false -,noRoomToOpenTip:lang.msgNoRoom -,resizerTip:lang.Resize -,sliderTip:lang.Slide -,sliderCursor:"pointer" -,slideTrigger_open:"click" -,slideTrigger_close:"mouseleave" -,hideTogglerOnSlide:false -,preventQuickSlideClose:!!($.browser.webkit||$.browser.safari) -,preventPrematureSlideClose:false -,showOverflowOnHover:false -,enableCursorHotkey:true -,customHotkeyModifier:"SHIFT" -,fxName:"slide" -,fxSpeed:null -,fxSettings:{} -,fxOpacityFix:true -,triggerEventsOnLoad:false -,triggerEventsWhileDragging:true -,onshow_start:null -,onshow_end:null -,onhide_start:null -,onhide_end:null -,onopen_start:null -,onopen_end:null -,onclose_start:null -,onclose_end:null -,onresize_start:null -,onresize_end:null -,onsizecontent_start:null -,onsizecontent_end:null -,onswap_start:null -,onswap_end:null -,ondrag_start:null -,ondrag_end:null} -,north:{paneSelector:".ui-layout-north" -,size:"auto" -,resizerCursor:"n-resize" -,customHotkey:""} -,south:{paneSelector:".ui-layout-south" -,size:"auto" -,resizerCursor:"s-resize" -,customHotkey:""} -,east:{paneSelector:".ui-layout-east" -,size:200 -,resizerCursor:"e-resize" -,customHotkey:""} -,west:{paneSelector:".ui-layout-west" -,size:200 -,resizerCursor:"w-resize" -,customHotkey:""} -,center:{paneSelector:".ui-layout-center" -,minWidth:0 -,minHeight:0} -,useStateCookie:false -,cookie:{name:"" -,autoSave:true -,autoLoad:true -,domain:"" -,path:"" -,expires:"" -,secure:false -,keys:"north.size,south.size,east.size,west.size,"+ -"north.isClosed,south.isClosed,east.isClosed,west.isClosed,"+ -"north.isHidden,south.isHidden,east.isHidden,west.isHidden"}};var effects={slide:{all:{duration:"fast"} -,north:{direction:"up"} -,south:{direction:"down"} -,east:{direction:"right"} -,west:{direction:"left"}} -,drop:{all:{duration:"slow"} -,north:{direction:"up"} -,south:{direction:"down"} -,east:{direction:"right"} -,west:{direction:"left"}} -,scale:{all:{duration:"fast"}}};var state={id:"layout"+new Date().getTime() -,initialized:false -,container:{} -,north:{} -,south:{} -,east:{} -,west:{} -,center:{} -,cookie:{}};var _c={allPanes:"north,south,west,east,center" -,borderPanes:"north,south,west,east" -,altSide:{north:"south" -,south:"north" -,east:"west" -,west:"east"} -,hidden:{visibility:"hidden"} -,visible:{visibility:"visible"} -,zIndex:{pane_normal:1 -,resizer_normal:2 -,iframe_mask:2 -,pane_sliding:100 -,pane_animate:1000 -,resizer_drag:10000} -,resizers:{cssReq:{position:"absolute" -,padding:0 -,margin:0 -,fontSize:"1px" -,textAlign:"left" -,overflow:"hidden"} -,cssDemo:{background:"#DDD" -,border:"none"}} -,togglers:{cssReq:{position:"absolute" -,display:"block" -,padding:0 -,margin:0 -,overflow:"hidden" -,textAlign:"center" -,fontSize:"1px" -,cursor:"pointer" -,zIndex:1} -,cssDemo:{background:"#AAA"}} -,content:{cssReq:{position:"relative"} -,cssDemo:{overflow:"auto" -,padding:"10px"} -,cssDemoPane:{overflow:"hidden" -,padding:0}} -,panes:{cssReq:{position:"absolute" -,margin:0} -,cssDemo:{padding:"10px" -,background:"#FFF" -,border:"1px solid #BBB" -,overflow:"auto"}} -,north:{side:"Top" -,sizeType:"Height" -,dir:"horz" -,cssReq:{top:0 -,bottom:"auto" -,left:0 -,right:0 -,width:"auto"} -,pins:[]} -,south:{side:"Bottom" -,sizeType:"Height" -,dir:"horz" -,cssReq:{top:"auto" -,bottom:0 -,left:0 -,right:0 -,width:"auto"} -,pins:[]} -,east:{side:"Right" -,sizeType:"Width" -,dir:"vert" -,cssReq:{left:"auto" -,right:0 -,top:"auto" -,bottom:"auto" -,height:"auto"} -,pins:[]} -,west:{side:"Left" -,sizeType:"Width" -,dir:"vert" -,cssReq:{left:0 -,right:"auto" -,top:"auto" -,bottom:"auto" -,height:"auto"} -,pins:[]} -,center:{dir:"center" -,cssReq:{left:"auto" -,right:"auto" -,top:"auto" -,bottom:"auto" -,height:"auto" -,width:"auto"}}};var timer={data:{} -,set:function(s,fn,ms){timer.clear(s);timer.data[s]=setTimeout(fn,ms);} -,clear:function(s){var t=timer.data;if(t[s]){clearTimeout(t[s]);delete t[s];}}};var isStr=function(o){try{return typeof o=="string" -||(typeof o=="object"&&o.constructor.toString().match(/string/i)!==null);} -catch(e){return false;}};var str=function(o){return isStr(o)?$.trim(o):o==undefined||o==null?"":o;};var min=function(x,y){return Math.min(x,y);};var max=function(x,y){return Math.max(x,y);};var _transformData=function(d){var a,json={cookie:{},defaults:{fxSettings:{}},north:{fxSettings:{}},south:{fxSettings:{}},east:{fxSettings:{}},west:{fxSettings:{}},center:{fxSettings:{}}};d=d||{};if(d.effects||d.cookie||d.defaults||d.north||d.south||d.west||d.east||d.center) -json=$.extend(true,json,d);else -$.each(d,function(key,val){a=key.split("__");if(!a[1]||json[a[0]]) -json[a[1]?a[0]:"defaults"][a[1]?a[1]:a[0]]=val;});return json;};var _queue=function(action,pane,param){var tried=[];$.each(_c.borderPanes.split(","),function(i,p){if(_c[p].isMoving){bindCallback(p);return false;}});function bindCallback(p){var c=_c[p];if(!c.doCallback){c.doCallback=true;c.callback=action+","+pane+","+(param?1:0);} -else{tried.push(p);var cbPane=c.callback.split(",")[1];if(cbPane!=pane&&!$.inArray(cbPane,tried)>=0) -bindCallback(cbPane);}}};var _dequeue=function(pane){var c=_c[pane];_c.isLayoutBusy=false;delete c.isMoving;if(!c.doCallback||!c.callback)return;c.doCallback=false;var -cb=c.callback.split(",") -,param=(cb[2]>0?true:false) -;if(cb[0]=="open") -open(cb[1],param);else if(cb[0]=="close") -close(cb[1],param);if(!c.doCallback)c.callback=null;};var _execCallback=function(pane,v_fn){if(!v_fn)return;var fn;try{if(typeof v_fn=="function") -fn=v_fn;else if(!isStr(v_fn)) -return;else if(v_fn.match(/,/)){var args=v_fn.split(",");fn=eval(args[0]);if(typeof fn=="function"&&args.length>1) -return fn(args[1]);} -else -fn=eval(v_fn);if(typeof fn=="function"){if(pane&&$Ps[pane]) -return fn(pane,$Ps[pane],$.extend({},state[pane]),options[pane],options.name);else -return fn(Instance,$.extend({},state),options,options.name);}} -catch(ex){}};var _showInvisibly=function($E,force){if(!$E)return{};if(!$E.jquery)$E=$($E);var CSS={display:$E.css('display') -,visibility:$E.css('visibility')};if(force||CSS.display=="none"){$E.css({display:"block",visibility:"hidden"});return CSS;} -else return{};};var _fixIframe=function(pane){if(state.browser.mozilla)return;var $P=$Ps[pane];if(state[pane].tagName=="IFRAME") -$P.css(_c.hidden).css(_c.visible);else -$P.find('IFRAME').css(_c.hidden).css(_c.visible);};var _cssNum=function($E,prop){if(!$E.jquery)$E=$($E);var CSS=_showInvisibly($E);var val=parseInt($.curCSS($E[0],prop,true),10)||0;$E.css(CSS);return val;};var _borderWidth=function(E,side){if(E.jquery)E=E[0];var b="border"+side.substr(0,1).toUpperCase()+side.substr(1);return $.curCSS(E,b+"Style",true)=="none"?0:(parseInt($.curCSS(E,b+"Width",true),10)||0);};var cssW=function(el,outerWidth){var -str=isStr(el) -,$E=str?$Ps[el]:$(el) -;if(isNaN(outerWidth)) -outerWidth=str?getPaneSize(el):$E.outerWidth();if(outerWidth<=0)return 0;if(!state.browser.boxModel)return outerWidth;var W=outerWidth --_borderWidth($E,"Left") --_borderWidth($E,"Right") --_cssNum($E,"paddingLeft") --_cssNum($E,"paddingRight") -;return W>0?W:0;};var cssH=function(el,outerHeight){var -str=isStr(el) -,$E=str?$Ps[el]:$(el) -;if(isNaN(outerHeight)) -outerHeight=str?getPaneSize(el):$E.outerHeight();if(outerHeight<=0)return 0;if(!state.browser.boxModel)return outerHeight;var H=outerHeight --_borderWidth($E,"Top") --_borderWidth($E,"Bottom") --_cssNum($E,"paddingTop") --_cssNum($E,"paddingBottom") -;return H>0?H:0;};var cssSize=function(pane,outerSize){if(_c[pane].dir=="horz") -return cssH(pane,outerSize);else -return cssW(pane,outerSize);};var cssMinDims=function(pane){var -dir=_c[pane].dir -,d={minWidth:1001-cssW(pane,1000) -,minHeight:1001-cssH(pane,1000)} -;if(dir=="horz")d.minSize=d.minHeight;if(dir=="vert")d.minSize=d.minWidth;return d;};var setOuterWidth=function(el,outerWidth,autoHide){var $E=el,w;if(isStr(el))$E=$Ps[el];else if(!el.jquery)$E=$(el);w=cssW($E,outerWidth);$E.css({width:w});if(w>0){if(autoHide&&$E.data('autoHidden')&&$E.innerHeight()>0){$E.show().data('autoHidden',false);if(!state.browser.mozilla) -$E.css(_c.hidden).css(_c.visible);}} -else if(autoHide&&!$E.data('autoHidden')) -$E.hide().data('autoHidden',true);};var setOuterHeight=function(el,outerHeight,autoHide){var $E=el,h;if(isStr(el))$E=$Ps[el];else if(!el.jquery)$E=$(el);h=cssH($E,outerHeight);$E.css({height:h,visibility:"visible"});if(h>0&&$E.innerWidth()>0){if(autoHide&&$E.data('autoHidden')){$E.show().data('autoHidden',false);if(!state.browser.mozilla) -$E.css(_c.hidden).css(_c.visible);}} -else if(autoHide&&!$E.data('autoHidden')) -$E.hide().data('autoHidden',true);};var setOuterSize=function(el,outerSize,autoHide){if(_c[pane].dir=="horz") -setOuterHeight(el,outerSize,autoHide);else -setOuterWidth(el,outerSize,autoHide);};var _parseSize=function(pane,size,dir){if(!dir)dir=_c[pane].dir;if(isStr(size)&&size.match(/%/)) -size=parseInt(size,10)/100;if(size===0) -return 0;else if(size>=1) -return parseInt(size,10);else if(size>0){var o=options,avail;if(dir=="horz") -avail=sC.innerHeight-($Ps.north?o.north.spacing_open:0)-($Ps.south?o.south.spacing_open:0);else if(dir=="vert") -avail=sC.innerWidth-($Ps.west?o.west.spacing_open:0)-($Ps.east?o.east.spacing_open:0);return Math.floor(avail*size);} -else if(pane=="center") -return 0;else{var -$P=$Ps[pane] -,dim=(dir=="horz"?"height":"width") -,vis=_showInvisibly($P) -,s=$P.css(dim);;$P.css(dim,"auto");size=(dim=="height")?$P.outerHeight():$P.outerWidth();$P.css(dim,s).css(vis);return size;}};var getPaneSize=function(pane,inclSpace){var -$P=$Ps[pane] -,o=options[pane] -,s=state[pane] -,oSp=(inclSpace?o.spacing_open:0) -,cSp=(inclSpace?o.spacing_closed:0) -;if(!$P||s.isHidden) -return 0;else if(s.isClosed||(s.isSliding&&inclSpace)) -return cSp;else if(_c[pane].dir=="horz") -return $P.outerHeight()+oSp;else -return $P.outerWidth()+oSp;};var setSizeLimits=function(pane,slide){var -o=options[pane] -,s=state[pane] -,c=_c[pane] -,dir=c.dir -,side=c.side.toLowerCase() -,type=c.sizeType.toLowerCase() -,isSliding=(slide!=undefined?slide:s.isSliding) -,$P=$Ps[pane] -,paneSpacing=o.spacing_open -,altPane=_c.altSide[pane] -,altS=state[altPane] -,$altP=$Ps[altPane] -,altPaneSize=(!$altP||altS.isVisible===false||altS.isSliding?0:(dir=="horz"?$altP.outerHeight():$altP.outerWidth())) -,altPaneSpacing=((!$altP||altS.isHidden?0:options[altPane][altS.isClosed!==false?"spacing_closed":"spacing_open"])||0) -,containerSize=(dir=="horz"?sC.innerHeight:sC.innerWidth) -,minCenterDims=cssMinDims("center") -,minCenterSize=dir=="horz"?max(options.center.minHeight,minCenterDims.minHeight):max(options.center.minWidth,minCenterDims.minWidth) -,limitSize=(containerSize-paneSpacing-(isSliding?0:(_parseSize("center",minCenterSize,dir)+altPaneSize+altPaneSpacing))) -,minSize=s.minSize=max(_parseSize(pane,o.minSize),cssMinDims(pane).minSize) -,maxSize=s.maxSize=min((o.maxSize?_parseSize(pane,o.maxSize):100000),limitSize) -,r=s.resizerPosition={} -,top=sC.insetTop -,left=sC.insetLeft -,W=sC.innerWidth -,H=sC.innerHeight -,rW=o.spacing_open -;switch(pane){case"north":r.min=top+minSize;r.max=top+maxSize;break;case"west":r.min=left+minSize;r.max=left+maxSize;break;case"south":r.min=top+H-maxSize-rW;r.max=top+H-minSize-rW;break;case"east":r.min=left+W-maxSize-rW;r.max=left+W-minSize-rW;break;};};var calcNewCenterPaneDims=function(){var d={top:getPaneSize("north",true) -,bottom:getPaneSize("south",true) -,left:getPaneSize("west",true) -,right:getPaneSize("east",true) -,width:0 -,height:0};d.width=sC.innerWidth-d.left-d.right;d.height=sC.innerHeight-d.bottom-d.top;d.top+=sC.insetTop;d.bottom+=sC.insetBottom;d.left+=sC.insetLeft;d.right+=sC.insetRight;return d;};var getElemDims=function($E){var -d={} -,x=d.css={} -,i={} -,b,p -,off=$E.offset() -;d.offsetLeft=off.left;d.offsetTop=off.top;$.each("Left,Right,Top,Bottom".split(","),function(idx,e){b=x["border"+e]=_borderWidth($E,e);p=x["padding"+e]=_cssNum($E,"padding"+e);i[e]=b+p;d["inset"+e]=p;});d.offsetWidth=$E.innerWidth();d.offsetHeight=$E.innerHeight();d.outerWidth=$E.outerWidth();d.outerHeight=$E.outerHeight();d.innerWidth=d.outerWidth-i.Left-i.Right;d.innerHeight=d.outerHeight-i.Top-i.Bottom;x.width=$E.width();x.height=$E.height();return d;};var getElemCSS=function($E,list){var -CSS={} -,style=$E[0].style -,props=list.split(",") -,sides="Top,Bottom,Left,Right".split(",") -,attrs="Color,Style,Width".split(",") -,p,s,a,i,j,k -;for(i=0;i0){timer.clear("winResize");timer.set("winResize",function(){timer.clear("winResize");timer.clear("winResizeRepeater");resizeAll();},delay);if(!timer.data["winResizeRepeater"])setWindowResizeRepeater();}};var setWindowResizeRepeater=function(){var delay=Number(options.resizeWithWindowMaxDelay);if(delay>0) -timer.set("winResizeRepeater",function(){setWindowResizeRepeater();resizeAll();},delay);};var unload=function(){var o=options;state.cookie=getState();if(o.useStateCookie&&o.cookie.autoSave)saveCookie();_execCallback(null,o.onunload);};var initContainer=function(){var -$C=$Container -,tag=sC.tagName=$C.attr("tagName") -,fullPage=(tag=="BODY") -,props="position,margin,padding,border" -,CSS={} -;sC.selector=$C.selector.split(".slice")[0];sC.ref=tag+"/"+sC.selector;$C.data("layout",Instance) -.data("layoutContainer",sID) -;if(!$C.data("layoutCSS")){if(fullPage){CSS=$.extend(getElemCSS($C,props),{height:$C.css("height") -,overflow:$C.css("overflow") -,overflowX:$C.css("overflowX") -,overflowY:$C.css("overflowY")});var $H=$("html");$H.data("layoutCSS",{height:"auto" -,overflow:$H.css("overflow") -,overflowX:$H.css("overflowX") -,overflowY:$H.css("overflowY")});} -else -CSS=getElemCSS($C,props+",top,bottom,left,right,width,height,overflow,overflowX,overflowY");$C.data("layoutCSS",CSS);} -try{if(fullPage){$("html").css({height:"100%" -,overflow:"hidden" -,overflowX:"hidden" -,overflowY:"hidden"});$("body").css({position:"relative" -,height:"100%" -,overflow:"hidden" -,overflowX:"hidden" -,overflowY:"hidden" -,margin:0 -,padding:0 -,border:"none"});} -else{CSS={overflow:"hidden"} -var -p=$C.css("position") -,h=$C.css("height") -;if(!$C.data("layoutRole")){if(!p||!p.match(/fixed|absolute|relative/)) -CSS.position="relative";} -$C.css(CSS);if($C.is(":visible")&&$C.innerHeight()<2) -alert(lang.errContainerHeight.replace(/CONTAINER/,sC.ref));}}catch(ex){} -$.extend(state.container,getElemDims($C));};var initHotkeys=function(){$.each(_c.borderPanes.split(","),function(i,pane){var o=options[pane];if(o.enableCursorHotkey||o.customHotkey){$(document).bind("keydown."+sID,keyDown);return false;}});};var initOptions=function(){opts=_transformData(opts);var newOpts={applyDefaultStyles:"applyDemoStyles"};renameOpts(opts.defaults);$.each(_c.allPanes.split(","),function(i,pane){renameOpts(opts[pane]);});if(opts.effects){$.extend(effects,opts.effects);delete opts.effects;} -$.extend(options.cookie,opts.cookie);var globals="name,zIndex,scrollToBookmarkOnLoad,resizeWithWindow,resizeWithWindowDelay,resizeWithWindowMaxDelay,"+ -"onresizeall,onresizeall_start,onresizeall_end,onload,onunload,autoBindCustomButtons,useStateCookie";$.each(globals.split(","),function(i,key){if(opts[key]!==undefined) -options[key]=opts[key];else if(opts.defaults[key]!==undefined){options[key]=opts.defaults[key];delete opts.defaults[key];}});$.each("paneSelector,resizerCursor,customHotkey".split(","),function(i,key){delete opts.defaults[key];});$.extend(true,options.defaults,opts.defaults);_c.center=$.extend(true,{},_c.panes,_c.center);var z=options.zIndex;if(z===0||z>0){_c.zIndex.pane_normal=z;_c.zIndex.resizer_normal=z+1;_c.zIndex.iframe_mask=z+1;} -$.extend(options.center,opts.center);var o_Center=$.extend(true,{},options.defaults,opts.defaults,options.center);var optionsCenter=("paneClass,contentSelector,applyDemoStyles,triggerEventsOnLoad,showOverflowOnHover," -+"onresize,onresize_start,onresize_end,resizeNestedLayout,resizeContentWhileDragging," -+"onsizecontent,onsizecontent_start,onsizecontent_end").split(",");$.each(optionsCenter,function(i,key){options.center[key]=o_Center[key];});var o,defs=options.defaults;$.each(_c.borderPanes.split(","),function(i,pane){_c[pane]=$.extend(true,{},_c.panes,_c[pane]);o=options[pane]=$.extend(true,{},options.defaults,options[pane],opts.defaults,opts[pane]);if(!o.paneClass)o.paneClass="ui-layout-pane";if(!o.resizerClass)o.resizerClass="ui-layout-resizer";if(!o.togglerClass)o.togglerClass="ui-layout-toggler";$.each(["_open","_close",""],function(i,n){var -sName="fxName"+n -,sSpeed="fxSpeed"+n -,sSettings="fxSettings"+n -;o[sName]=opts[pane][sName] -||opts[pane].fxName -||opts.defaults[sName] -||opts.defaults.fxName -||o[sName] -||o.fxName -||defs[sName] -||defs.fxName -||"none" -;var fxName=o[sName];if(fxName=="none"||!$.effects||!$.effects[fxName]||(!effects[fxName]&&!o[sSettings]&&!o.fxSettings)) -fxName=o[sName]="none";var -fx=effects[fxName]||{} -,fx_all=fx.all||{} -,fx_pane=fx[pane]||{} -;o[sSettings]=$.extend({} -,fx_all -,fx_pane -,defs.fxSettings||{} -,defs[sSettings]||{} -,o.fxSettings -,o[sSettings] -,opts.defaults.fxSettings -,opts.defaults[sSettings]||{} -,opts[pane].fxSettings -,opts[pane][sSettings]||{});o[sSpeed]=opts[pane][sSpeed] -||opts[pane].fxSpeed -||opts.defaults[sSpeed] -||opts.defaults.fxSpeed -||o[sSpeed] -||o[sSettings].duration -||o.fxSpeed -||o.fxSettings.duration -||defs.fxSpeed -||defs.fxSettings.duration -||fx_pane.duration -||fx_all.duration -||"normal" -;});});function renameOpts(O){for(var key in newOpts){if(O[key]!=undefined){O[newOpts[key]]=O[key];delete O[key];}}}};var getPane=function(pane){var sel=options[pane].paneSelector -if(sel.substr(0,1)==="#") -return $Container.find(sel).eq(0);else{var $P=$Container.children(sel).eq(0);return $P.length?$P:$Container.children("form:first").children(sel).eq(0);}};var initPanes=function(){$.each(_c.allPanes.split(","),function(idx,pane){var -o=options[pane] -,s=state[pane] -,c=_c[pane] -,fx=s.fx -,dir=c.dir -,spacing=o.spacing_open||0 -,isCenter=(pane=="center") -,CSS={} -,$P,$C -,size,minSize,maxSize -;$Cs[pane]=false;$P=$Ps[pane]=getPane(pane);if(!$P.length){$Ps[pane]=false;return true;} -if(!$P.data("layoutCSS")){var props="position,top,left,bottom,right,width,height,overflow,zIndex,display,backgroundColor,padding,margin,border";$P.data("layoutCSS",getElemCSS($P,props));} -$P -.data("parentLayout",Instance) -.data("layoutRole","pane") -.data("layoutEdge",pane) -.css(c.cssReq).css("zIndex",_c.zIndex.pane_normal) -.css(o.applyDemoStyles?c.cssDemo:{}) -.addClass(o.paneClass+" "+o.paneClass+"-"+pane) -.bind("mouseenter."+sID,addHover) -.bind("mouseleave."+sID,removeHover) -;initContent(pane,false);if(!isCenter){size=s.size=_parseSize(pane,o.size);minSize=_parseSize(pane,o.minSize)||1;maxSize=_parseSize(pane,o.maxSize)||100000;if(size>0)size=max(min(size,maxSize),minSize);s.isClosed=false;s.isSliding=false;s.isResizing=false;s.isHidden=false;} -s.tagName=$P.attr("tagName");s.edge=pane -s.noRoom=false;s.isVisible=true;switch(pane){case"north":CSS.top=sC.insetTop;CSS.left=sC.insetLeft;CSS.right=sC.insetRight;break;case"south":CSS.bottom=sC.insetBottom;CSS.left=sC.insetLeft;CSS.right=sC.insetRight;break;case"west":CSS.left=sC.insetLeft;break;case"east":CSS.right=sC.insetRight;break;case"center":} -if(dir=="horz") -CSS.height=max(1,cssH(pane,size));else if(dir=="vert") -CSS.width=max(1,cssW(pane,size));$P.css(CSS);if(dir!="horz")sizeMidPanes(pane,true);$P.css({visibility:"visible",display:"block"});if(o.initClosed&&o.closable) -close(pane,true,true);else if(o.initHidden||o.initClosed) -hide(pane);if(o.showOverflowOnHover) -$P.hover(allowOverflow,resetOverflow);});initHandles();$.each(_c.borderPanes.split(","),function(i,pane){if($Ps[pane]&&state[pane].isVisible){setSizeLimits(pane);makePaneFit(pane);}});sizeMidPanes("center");$.each(_c.allPanes.split(","),function(i,pane){var o=options[pane];if($Ps[pane]&&o.triggerEventsOnLoad&&state[pane].isVisible) -_execCallback(pane,o.onresize_end||o.onresize);});if($Container.innerHeight()<2) -alert(lang.errContainerHeight.replace(/CONTAINER/,sC.ref));};var initHandles=function(panes){if(!panes||panes=="all")panes=_c.borderPanes;$.each(panes.split(","),function(i,pane){var $P=$Ps[pane];$Rs[pane]=false;$Ts[pane]=false;if(!$P)return;var -o=options[pane] -,s=state[pane] -,c=_c[pane] -,rClass=o.resizerClass -,tClass=o.togglerClass -,side=c.side.toLowerCase() -,spacing=(s.isVisible?o.spacing_open:o.spacing_closed) -,_pane="-"+pane -,_state=(s.isVisible?"-open":"-closed") -,$R=$Rs[pane]=$("
") -,$T=(o.closable?$Ts[pane]=$("
"):false) -;if(!s.isVisible&&o.slidable) -$R.attr("title",o.sliderTip).css("cursor",o.sliderCursor);$R -.attr("id",(o.paneSelector.substr(0,1)=="#"?o.paneSelector.substr(1)+"-resizer":"")) -.data("parentLayout",Instance) -.data("layoutRole","resizer") -.data("layoutEdge",pane) -.css(_c.resizers.cssReq).css("zIndex",_c.zIndex.resizer_normal) -.css(o.applyDemoStyles?_c.resizers.cssDemo:{}) -.addClass(rClass+" "+rClass+_pane) -.appendTo($Container) -;if($T){$T -.attr("id",(o.paneSelector.substr(0,1)=="#"?o.paneSelector.substr(1)+"-toggler":"")) -.data("parentLayout",Instance) -.data("layoutRole","toggler") -.data("layoutEdge",pane) -.css(_c.togglers.cssReq) -.css(o.applyDemoStyles?_c.togglers.cssDemo:{}) -.addClass(tClass+" "+tClass+_pane) -.appendTo($R) -;if(o.togglerContent_open) -$(""+o.togglerContent_open+"") -.data("layoutRole","togglerContent") -.data("layoutEdge",pane) -.addClass("content content-open") -.css("display","none") -.appendTo($T) -.hover(addHover,removeHover) -;if(o.togglerContent_closed) -$(""+o.togglerContent_closed+"") -.data("layoutRole","togglerContent") -.data("layoutEdge",pane) -.addClass("content content-closed") -.css("display","none") -.appendTo($T) -.hover(addHover,removeHover) -;enableClosable(pane);} -initResizable(pane);if(s.isVisible) -setAsOpen(pane);else{setAsClosed(pane);bindStartSlidingEvent(pane,true);}});sizeHandles("all");};var initContent=function(pane,resize){var -o=options[pane] -,sel=o.contentSelector -,$P=$Ps[pane] -,$C -;if(sel)$C=$Cs[pane]=(o.findNestedContent) -?$P.find(sel).eq(0) -:$P.children(sel).eq(0) -;if($C&&$C.length){$C.css(_c.content.cssReq);if(o.applyDemoStyles){$C.css(_c.content.cssDemo);$P.css(_c.content.cssDemoPane);} -state[pane].content={};if(resize!==false)sizeContent(pane);} -else -$Cs[pane]=false;};var initButtons=function(){var pre="ui-layout-button-",name;$.each("toggle,open,close,pin,toggle-slide,open-slide".split(","),function(i,action){$.each(_c.borderPanes.split(","),function(ii,pane){$("."+pre+action+"-"+pane).each(function(){name=$(this).data("layoutName")||$(this).attr("layoutName");if(name==undefined||name==options.name) -bindButton(this,action,pane);});});});};var initResizable=function(panes){var -draggingAvailable=(typeof $.fn.draggable=="function") -,$Frames,side -;if(!panes||panes=="all")panes=_c.borderPanes;$.each(panes.split(","),function(idx,pane){var -o=options[pane] -,s=state[pane] -,c=_c[pane] -,side=(c.dir=="horz"?"top":"left") -,r,live -;if(!draggingAvailable||!$Ps[pane]||!o.resizable){o.resizable=false;return true;} -var -$P=$Ps[pane] -,$R=$Rs[pane] -,base=o.resizerClass -,resizerClass=base+"-drag" -,resizerPaneClass=base+"-"+pane+"-drag" -,helperClass=base+"-dragging" -,helperPaneClass=base+"-"+pane+"-dragging" -,helperLimitClass=base+"-dragging-limit" -,helperClassesSet=false -;if(!s.isClosed) -$R -.attr("title",o.resizerTip) -.css("cursor",o.resizerCursor) -;$R.bind("mouseenter."+sID,onResizerEnter) -.bind("mouseleave."+sID,onResizerLeave);$R.draggable({containment:$Container[0] -,axis:(c.dir=="horz"?"y":"x") -,delay:0 -,distance:1 -,helper:"clone" -,opacity:o.resizerDragOpacity -,addClasses:false -,zIndex:_c.zIndex.resizer_drag -,start:function(e,ui){o=options[pane];s=state[pane];live=o.resizeWhileDragging;if(false===_execCallback(pane,o.ondrag_start))return false;_c.isLayoutBusy=true;s.isResizing=true;timer.clear(pane+"_closeSlider");setSizeLimits(pane);r=s.resizerPosition;$R.addClass(resizerClass+" "+resizerPaneClass);helperClassesSet=false;$Frames=$(o.maskIframesOnResize===true?"iframe":o.maskIframesOnResize).filter(":visible");var id,i=0;$Frames.each(function(){id="ui-layout-mask-"+(++i);$(this).data("layoutMaskID",id);$('
') -.css({background:"#fff" -,opacity:"0.001" -,zIndex:_c.zIndex.iframe_mask -,position:"absolute" -,width:this.offsetWidth+"px" -,height:this.offsetHeight+"px"}) -.css($(this).position()) -.appendTo(this.parentNode) -;});$('body').disableSelection();} -,drag:function(e,ui){if(!helperClassesSet){ui.helper -.addClass(helperClass+" "+helperPaneClass) -.children().css("visibility","hidden") -;helperClassesSet=true;if(s.isSliding)$Ps[pane].css("zIndex",_c.zIndex.pane_sliding);} -var limit=0;if(ui.position[side]r.max){ui.position[side]=r.max;limit=1;} -if(limit){ui.helper.addClass(helperLimitClass);window.defaultStatus="Panel has reached its "+ -((limit>0&&pane.match(/north|west/))||(limit<0&&pane.match(/south|east/))?"maximum":"minimum")+" size";} -else{ui.helper.removeClass(helperLimitClass);window.defaultStatus="";} -if(live)resizePanes(e,ui,pane);} -,stop:function(e,ui){$('body').enableSelection();window.defaultStatus="";$R.removeClass(resizerClass+" "+resizerPaneClass+" "+helperLimitClass);s.isResizing=false;_c.isLayoutBusy=false;resizePanes(e,ui,pane,true);}});var resizePanes=function(evt,ui,pane,resizingDone){var -dragPos=ui.position -,c=_c[pane] -,resizerPos,newSize -,i=0 -;switch(pane){case"north":resizerPos=dragPos.top;break;case"west":resizerPos=dragPos.left;break;case"south":resizerPos=sC.offsetHeight-dragPos.top-o.spacing_open;break;case"east":resizerPos=sC.offsetWidth-dragPos.left-o.spacing_open;break;};if(resizingDone){$("div.ui-layout-mask").each(function(){this.parentNode.removeChild(this);});if(false===_execCallback(pane,o.ondrag_end||o.ondrag))return false;} -else -$Frames.each(function(){$("#"+$(this).data("layoutMaskID")) -.css($(this).position()) -.css({width:this.offsetWidth+"px" -,height:this.offsetHeight+"px"}) -;});newSize=resizerPos-sC["inset"+c.side];manualSizePane(pane,newSize);}});};var destroy=function(){$(window).unbind("."+sID);$(document).unbind("."+sID);var -fullPage=(sC.tagName=="BODY") -,_open="-open" -,_sliding="-sliding" -,_closed="-closed" -,$P,root,pRoot,pClasses -;$.each(_c.allPanes.split(","),function(i,pane){$P=$Ps[pane];if(!$P)return true;if(pane!="center"){if($Ts[pane])$Ts[pane].remove();$Rs[pane].remove();} -root=options[pane].paneClass;pRoot=root+"-"+pane;pClasses=[root,root+_open,root+_closed,root+_sliding,pRoot,pRoot+_open,pRoot+_closed,pRoot+_sliding];$.merge(pClasses,getHoverClasses($P,true));$P -.removeClass(pClasses.join(" ")) -.removeData("layoutRole") -.removeData("layoutEdge") -.unbind("."+sID) -.unbind("mouseenter") -.unbind("mouseleave") -;if(!$P.data("layoutContainer")) -$P.css($P.data("layoutCSS"));});$Container.removeData("layoutContainer");if(!$Container.data("layoutEdge")) -$Container.css($Container.data("layoutCSS"));if(fullPage) -$("html").css($("html").data("layoutCSS"));unload();};var hide=function(pane,noAnimation){var -o=options[pane] -,s=state[pane] -,$P=$Ps[pane] -,$R=$Rs[pane] -;if(!$P||s.isHidden)return;if(state.initialized&&false===_execCallback(pane,o.onhide_start))return;s.isSliding=false;if($R)$R.hide();if(!state.initialized||s.isClosed){s.isClosed=true;s.isHidden=true;s.isVisible=false;$P.hide();sizeMidPanes(_c[pane].dir=="horz"?"all":"center");if(state.initialized||o.triggerEventsOnLoad) -_execCallback(pane,o.onhide_end||o.onhide);} -else{s.isHiding=true;close(pane,false,noAnimation);}};var show=function(pane,openPane,noAnimation,noAlert){var -o=options[pane] -,s=state[pane] -,$P=$Ps[pane] -,$R=$Rs[pane] -;if(!$P||!s.isHidden)return;if(false===_execCallback(pane,o.onshow_start))return;s.isSliding=false;s.isShowing=true;if(openPane===false) -close(pane,true);else -open(pane,false,noAnimation,noAlert);};var toggle=function(pane,slide){if(!isStr(pane)){pane.stopImmediatePropagation();pane=$(this).data("layoutEdge");} -var s=state[str(pane)];if(s.isHidden) -show(pane);else if(s.isClosed) -open(pane,!!slide);else -close(pane);};var _closePane=function(pane,setHandles){var -$P=$Ps[pane] -,s=state[pane] -;$P.hide();s.isClosed=true;s.isVisible=false;};var close=function(pane,force,noAnimation,skipCallback){if(!state.initialized){_closePane(pane) -return;} -var -$P=$Ps[pane] -,$R=$Rs[pane] -,$T=$Ts[pane] -,o=options[pane] -,s=state[pane] -,doFX=!noAnimation&&!s.isClosed&&(o.fxName_close!="none") -,isShowing=s.isShowing -,isHiding=s.isHiding -,wasSliding=s.isSliding -;delete s.isShowing;delete s.isHiding;if(!$P||(!o.closable&&!isShowing&&!isHiding))return;else if(!force&&s.isClosed&&!isShowing)return;if(_c.isLayoutBusy){_queue("close",pane,force);return;} -if(!isShowing&&false===_execCallback(pane,o.onclose_start))return;_c[pane].isMoving=true;_c.isLayoutBusy=true;s.isClosed=true;s.isVisible=false;if(isHiding)s.isHidden=true;else if(isShowing)s.isHidden=false;if(s.isSliding) -bindStopSlidingEvents(pane,false);else -sizeMidPanes(_c[pane].dir=="horz"?"all":"center",false);setAsClosed(pane);if(doFX){lockPaneForFX(pane,true);$P.hide(o.fxName_close,o.fxSettings_close,o.fxSpeed_close,function(){lockPaneForFX(pane,false);close_2();});} -else{$P.hide();close_2();};function close_2(){if(s.isClosed){bindStartSlidingEvent(pane,true);var altPane=_c.altSide[pane];if(state[altPane].noRoom){setSizeLimits(altPane);makePaneFit(altPane);} -if(!skipCallback&&(state.initialized||o.triggerEventsOnLoad)){if(!isShowing)_execCallback(pane,o.onclose_end||o.onclose);if(isShowing)_execCallback(pane,o.onshow_end||o.onshow);if(isHiding)_execCallback(pane,o.onhide_end||o.onhide);}} -_dequeue(pane);}};var setAsClosed=function(pane){var -$P=$Ps[pane] -,$R=$Rs[pane] -,$T=$Ts[pane] -,o=options[pane] -,s=state[pane] -,side=_c[pane].side.toLowerCase() -,inset="inset"+_c[pane].side -,rClass=o.resizerClass -,tClass=o.togglerClass -,_pane="-"+pane -,_open="-open" -,_sliding="-sliding" -,_closed="-closed" -;$R -.css(side,sC[inset]) -.removeClass(rClass+_open+" "+rClass+_pane+_open) -.removeClass(rClass+_sliding+" "+rClass+_pane+_sliding) -.addClass(rClass+_closed+" "+rClass+_pane+_closed) -.unbind("dblclick."+sID) -;if(o.resizable&&typeof $.fn.draggable=="function") -$R -.draggable("disable") -.removeClass("ui-state-disabled") -.css("cursor","default") -.attr("title","") -;if($T){$T -.removeClass(tClass+_open+" "+tClass+_pane+_open) -.addClass(tClass+_closed+" "+tClass+_pane+_closed) -.attr("title",o.togglerTip_closed) -;$T.children(".content-open").hide();$T.children(".content-closed").css("display","block");} -syncPinBtns(pane,false);if(state.initialized){sizeHandles("all");}};var open=function(pane,slide,noAnimation,noAlert){var -$P=$Ps[pane] -,$R=$Rs[pane] -,$T=$Ts[pane] -,o=options[pane] -,s=state[pane] -,doFX=!noAnimation&&s.isClosed&&(o.fxName_open!="none") -,isShowing=s.isShowing -;delete s.isShowing;if(!$P||(!o.resizable&&!o.closable&&!isShowing))return;else if(s.isVisible&&!s.isSliding)return;if(s.isHidden&&!isShowing){show(pane,true);return;} -if(_c.isLayoutBusy){_queue("open",pane,slide);return;} -if(false===_execCallback(pane,o.onopen_start))return;setSizeLimits(pane,slide);if(s.minSize>s.maxSize){syncPinBtns(pane,false);if(!noAlert&&o.noRoomToOpenTip)alert(o.noRoomToOpenTip);return;} -_c[pane].isMoving=true;_c.isLayoutBusy=true;if(slide) -bindStopSlidingEvents(pane,true);else if(s.isSliding) -bindStopSlidingEvents(pane,false);else if(o.slidable) -bindStartSlidingEvent(pane,false);s.noRoom=false;makePaneFit(pane);s.isVisible=true;s.isClosed=false;if(isShowing)s.isHidden=false;if(doFX){lockPaneForFX(pane,true);$P.show(o.fxName_open,o.fxSettings_open,o.fxSpeed_open,function(){lockPaneForFX(pane,false);open_2();});} -else{$P.show();open_2();};function open_2(){if(s.isVisible){_fixIframe(pane);if(!s.isSliding) -sizeMidPanes(_c[pane].dir=="vert"?"center":"all",false);setAsOpen(pane);} -_dequeue(pane);};};var setAsOpen=function(pane,skipCallback){var -$P=$Ps[pane] -,$R=$Rs[pane] -,$T=$Ts[pane] -,o=options[pane] -,s=state[pane] -,side=_c[pane].side.toLowerCase() -,inset="inset"+_c[pane].side -,rClass=o.resizerClass -,tClass=o.togglerClass -,_pane="-"+pane -,_open="-open" -,_closed="-closed" -,_sliding="-sliding" -;$R -.css(side,sC[inset]+getPaneSize(pane)) -.removeClass(rClass+_closed+" "+rClass+_pane+_closed) -.addClass(rClass+_open+" "+rClass+_pane+_open) -;if(s.isSliding) -$R.addClass(rClass+_sliding+" "+rClass+_pane+_sliding) -else -$R.removeClass(rClass+_sliding+" "+rClass+_pane+_sliding) -if(o.resizerDblClickToggle) -$R.bind("dblclick",toggle);removeHover(0,$R);if(o.resizable&&typeof $.fn.draggable=="function") -$R -.draggable("enable") -.css("cursor",o.resizerCursor) -.attr("title",o.resizerTip) -;else if(!s.isSliding) -$R.css("cursor","default");if($T){$T -.removeClass(tClass+_closed+" "+tClass+_pane+_closed) -.addClass(tClass+_open+" "+tClass+_pane+_open) -.attr("title",o.togglerTip_open) -;removeHover(0,$T);$T.children(".content-closed").hide();$T.children(".content-open").css("display","block");} -syncPinBtns(pane,!s.isSliding);$.extend(s,getElemDims($P));if(state.initialized){sizeHandles("all");sizeContent(pane,true);} -if(!skipCallback&&(state.initialized||o.triggerEventsOnLoad)&&$P.is(":visible")){_execCallback(pane,o.onopen_end||o.onopen);if(s.isShowing)_execCallback(pane,o.onshow_end||o.onshow);if(state.initialized){_execCallback(pane,o.onresize_end||o.onresize);resizeNestedLayout(pane);}}};var slideOpen=function(evt_or_pane){var -type=typeof evt_or_pane -,pane=(type=="string"?evt_or_pane:$(this).data("layoutEdge")) -;if(type=="object"){evt_or_pane.stopImmediatePropagation();} -if(state[pane].isClosed) -open(pane,true);else -bindStopSlidingEvents(pane,true);};var slideClose=function(evt_or_pane){var -evt=isStr(evt_or_pane)?null:evt_or_pane -$E=(evt?$(this):$Ps[evt_or_pane]) -,pane=$E.data("layoutEdge") -,o=options[pane] -,s=state[pane] -,$P=$Ps[pane] -;if(s.isClosed||s.isResizing) -return;else if(o.slideTrigger_close=="click") -close_NOW();else if(o.preventQuickSlideClose&&_c.isLayoutBusy) -return;else if(o.preventPrematureSlideClose&&evt&&$.layout.isMouseOverElem(evt,$P)) -return;else if(evt) -timer.set(pane+"_closeSlider",close_NOW,_c[pane].isMoving?1000:300);else -close_NOW();function close_NOW(evt){if(s.isClosed) -bindStopSlidingEvents(pane,false);else if(!_c[pane].isMoving) -close(pane);};};var slideToggle=function(pane){toggle(pane,true);};var lockPaneForFX=function(pane,doLock){var $P=$Ps[pane];if(doLock){$P.css({zIndex:_c.zIndex.pane_animate});if(pane=="south") -$P.css({top:sC.insetTop+sC.innerHeight-$P.outerHeight()});else if(pane=="east") -$P.css({left:sC.insetLeft+sC.innerWidth-$P.outerWidth()});} -else{$P.css({zIndex:(state[pane].isSliding?_c.zIndex.pane_sliding:_c.zIndex.pane_normal)});if(pane=="south") -$P.css({top:"auto"});else if(pane=="east") -$P.css({left:"auto"});var o=options[pane];if(state.browser.msie&&o.fxOpacityFix&&o.fxName_open!="slide"&&$P.css("filter")&&$P.css("opacity")==1) -$P[0].style.removeAttribute('filter');}};var bindStartSlidingEvent=function(pane,enable){var -o=options[pane] -,$P=$Ps[pane] -,$R=$Rs[pane] -,trigger=o.slideTrigger_open -;if(!$R||(enable&&!o.slidable))return;if(trigger.match(/mouseover/)) -trigger=o.slideTrigger_open="mouseenter";else if(!trigger.match(/click|dblclick|mouseenter/)) -trigger=o.slideTrigger_open="click";$R -[enable?"bind":"unbind"](trigger+'.'+sID,slideOpen) -.css("cursor",enable?o.sliderCursor:"default") -.attr("title",enable?o.sliderTip:"") -;};var bindStopSlidingEvents=function(pane,enable){var -o=options[pane] -,s=state[pane] -,z=_c.zIndex -,trigger=o.slideTrigger_close -,action=(enable?"bind":"unbind") -,$P=$Ps[pane] -,$R=$Rs[pane] -;s.isSliding=enable;timer.clear(pane+"_closeSlider");if(enable)bindStartSlidingEvent(pane,false);$P.css("zIndex",enable?z.pane_sliding:z.pane_normal);$R.css("zIndex",enable?z.pane_sliding:z.resizer_normal);if(!trigger.match(/click|mouseleave/)) -trigger=o.slideTrigger_close="mouseleave";$R[action](trigger,slideClose);if(trigger=="mouseleave"){$P[action]("mouseleave."+sID,slideClose);$R[action]("mouseenter."+sID,cancelMouseOut);$P[action]("mouseenter."+sID,cancelMouseOut);} -if(!enable) -timer.clear(pane+"_closeSlider");else if(trigger=="click"&&!o.resizable){$R.css("cursor",enable?o.sliderCursor:"default");$R.attr("title",enable?o.togglerTip_open:"");} -function cancelMouseOut(evt){timer.clear(pane+"_closeSlider");evt.stopPropagation();}};var makePaneFit=function(pane,isOpening,skipCallback,force){var -o=options[pane] -,s=state[pane] -,c=_c[pane] -,$P=$Ps[pane] -,$R=$Rs[pane] -,isSidePane=c.dir=="vert" -,hasRoom=false -;if(pane=="center"||(isSidePane&&s.noVerticalRoom)){hasRoom=s.minHeight<=s.maxHeight&&(isSidePane||s.minWidth<=s.maxWidth);if(hasRoom&&s.noRoom){$P.show();if($R)$R.show();s.isVisible=true;s.noRoom=false;if(isSidePane)s.noVerticalRoom=false;_fixIframe(pane);} -else if(!hasRoom&&!s.noRoom){$P.hide();if($R)$R.hide();s.isVisible=false;s.noRoom=true;}} -if(pane=="center"){} -else if(s.minSize<=s.maxSize){hasRoom=true;if(s.size>s.maxSize) -sizePane(pane,s.maxSize,skipCallback,force);else if(s.size0&&CSS.height>0;if(!hasRoom&&!state.initialized&&o.minWidth>0){var -reqPx=o.minWidth-s.outerWidth -,minE=options.east.minSize||0 -,minW=options.west.minSize||0 -,sizeE=state.east.size -,sizeW=state.west.size -,newE=sizeE -,newW=sizeW -;if(reqPx>0&&state.east.isVisible&&sizeE>minE){newE=max(sizeE-minE,sizeE-reqPx);reqPx-=sizeE-newE;} -if(reqPx>0&&state.west.isVisible&&sizeW>minW){newW=max(sizeW-minW,sizeW-reqPx);reqPx-=sizeW-newW;} -if(reqPx==0){if(sizeE!=minE) -sizePane('east',newE,true);if(sizeW!=minW) -sizePane('west',newW,true);sizeMidPanes('center',skipCallback,force);return;}}} -else{if(s.isVisible&&!s.noVerticalRoom) -$.extend(s,getElemDims($P),cssMinDims(pane)) -if(!force&&!s.noVerticalRoom&&d.height==s.outerHeight) -return true;CSS.top=d.top;CSS.bottom=d.bottom;CSS.height=cssH(pane,d.height);s.maxHeight=max(0,CSS.height);hasRoom=(s.maxHeight>0);if(!hasRoom)s.noVerticalRoom=true;} -if(hasRoom){if(!skipCallback&&state.initialized) -_execCallback(pane,o.onresize_start);$P.css(CSS);if(s.isVisible){$.extend(s,getElemDims($P));if(s.noRoom)makePaneFit(pane);if(state.initialized)sizeContent(pane);}} -else if(!s.noRoom&&s.isVisible) -makePaneFit(pane);if(pane=="center"){var b=state.browser;var fix=b.isIE6||(b.msie&&!b.boxModel);if($Ps.north&&(fix||state.north.tagName=="IFRAME")) -$Ps.north.css("width",cssW($Ps.north,sC.innerWidth));if($Ps.south&&(fix||state.south.tagName=="IFRAME")) -$Ps.south.css("width",cssW($Ps.south,sC.innerWidth));} -if(!skipCallback&&state.initialized&&s.isVisible){_execCallback(pane,o.onresize_end||o.onresize);resizeNestedLayout(pane);}});};var resizeAll=function(){var -oldW=sC.innerWidth -,oldH=sC.innerHeight -;$.extend(state.container,getElemDims($Container));if(!sC.outerHeight)return;if(false===_execCallback(null,options.onresizeall_start))return false;var -shrunkH=(sC.innerHeight0&&$P.css("overflow")=="hidden"){$P.css("overflow","visible");_measure();$P.css("overflow","hidden");}} -var newH=s.innerHeight-(m.spaceAbove-s.css.paddingTop)-(m.spaceBelow-s.css.paddingBottom);if(!$C.is(":visible")||m.height!=newH){setOuterHeight($C,newH,true);m.height=newH;};if(state.initialized){_execCallback(pane,o.onsizecontent_end||o.onsizecontent);resizeNestedLayout(pane);} -function _below($E){return max(s.css.paddingBottom,(parseInt($E.css("marginBottom"),10)||0));};function _measure(){var -ignore=options[pane].contentIgnoreSelector -,$Fs=$C.nextAll().not(ignore||':lt(0)') -,$Fs_vis=$Fs.filter(':visible') -,$F=$Fs_vis.filter(':last') -;m={top:$C[0].offsetTop -,height:$C.outerHeight() -,numFooters:$Fs.length -,hiddenFooters:$Fs.length-$Fs_vis.length -,spaceBelow:0} -m.spaceAbove=m.top;m.bottom=m.top+m.height;if($F.length) -m.spaceBelow=($F[0].offsetTop+$F.outerHeight())-m.bottom+_below($F);else -m.spaceBelow=_below($C);};});};var sizeHandles=function(panes){if(!panes||panes=="all")panes=_c.borderPanes;$.each(panes.split(","),function(i,pane){var -o=options[pane] -,s=state[pane] -,$P=$Ps[pane] -,$R=$Rs[pane] -,$T=$Ts[pane] -,$TC -;if(!$P||!$R)return;var -dir=_c[pane].dir -,_state=(s.isClosed?"_closed":"_open") -,spacing=o["spacing"+_state] -,togAlign=o["togglerAlign"+_state] -,togLen=o["togglerLength"+_state] -,paneLen -,offset -,CSS={} -;if(spacing==0){$R.hide();return;} -else if(!s.noRoom&&!s.isHidden) -$R.show();if(dir=="horz"){paneLen=$P.outerWidth();s.resizerLength=paneLen;$R.css({width:max(1,cssW($R,paneLen)) -,height:max(0,cssH($R,spacing)) -,left:_cssNum($P,"left")});} -else{paneLen=$P.outerHeight();s.resizerLength=paneLen;$R.css({height:max(1,cssH($R,paneLen)) -,width:max(0,cssW($R,spacing)) -,top:sC.insetTop+getPaneSize("north",true)});} -removeHover(o,$R);if($T){if(togLen==0||(s.isSliding&&o.hideTogglerOnSlide)){$T.hide();return;} -else -$T.show();if(!(togLen>0)||togLen=="100%"||togLen>paneLen){togLen=paneLen;offset=0;} -else{if(isStr(togAlign)){switch(togAlign){case"top":case"left":offset=0;break;case"bottom":case"right":offset=paneLen-togLen;break;case"middle":case"center":default:offset=Math.floor((paneLen-togLen)/2);}} -else{var x=parseInt(togAlign,10);if(togAlign>=0)offset=x;else offset=paneLen-togLen+x;}} -if(dir=="horz"){var width=cssW($T,togLen);$T.css({width:max(0,width) -,height:max(1,cssH($T,spacing)) -,left:offset -,top:0});$T.children(".content").each(function(){$TC=$(this);$TC.css("marginLeft",Math.floor((width-$TC.outerWidth())/2));});} -else{var height=cssH($T,togLen);$T.css({height:max(0,height) -,width:max(1,cssW($T,spacing)) -,top:offset -,left:0});$T.children(".content").each(function(){$TC=$(this);$TC.css("marginTop",Math.floor((height-$TC.outerHeight())/2));});} -removeHover(0,$T);} -if(!state.initialized&&o.initHidden){$R.hide();if($T)$T.hide();}});};var enableClosable=function(pane){var $T=$Ts[pane],o=options[pane];if(!$T)return;o.closable=true;$T.bind("click."+sID,function(evt){toggle(pane);evt.stopPropagation();}) -.bind("mouseenter."+sID,addHover) -.bind("mouseleave."+sID,removeHover) -.css("visibility","visible") -.css("cursor","pointer") -.attr("title",state[pane].isClosed?o.togglerTip_closed:o.togglerTip_open) -.show() -;};var disableClosable=function(pane,hide){var $T=$Ts[pane];if(!$T)return;options[pane].closable=false;if(state[pane].isClosed)open(pane,false,true);$T.unbind("."+sID) -.css("visibility",hide?"hidden":"visible") -.css("cursor","default") -.attr("title","") -;};var enableSlidable=function(pane){var $R=$Rs[pane],o=options[pane];if(!$R||!$R.data('draggable'))return;options[pane].slidable=true;if(s.isClosed) -bindStartSlidingEvent(pane,true);};var disableSlidable=function(pane){var $R=$Rs[pane];if(!$R)return;options[pane].slidable=false;if(state[pane].isSliding) -close(pane,false,true);else{bindStartSlidingEvent(pane,false);$R.css("cursor","default") -.attr("title","") -;removeHover(null,$R[0]);}};var enableResizable=function(pane){var $R=$Rs[pane],o=options[pane];if(!$R||!$R.data('draggable'))return;o.resizable=true;$R.draggable("enable") -.bind("mouseenter."+sID,onResizerEnter) -.bind("mouseleave."+sID,onResizerLeave) -;if(!state[pane].isClosed) -$R.css("cursor",o.resizerCursor) -.attr("title",o.resizerTip) -;};var disableResizable=function(pane){var $R=$Rs[pane];if(!$R||!$R.data('draggable'))return;options[pane].resizable=false;$R.draggable("disable") -.unbind("."+sID) -.css("cursor","default") -.attr("title","") -;removeHover(null,$R[0]);};var swapPanes=function(pane1,pane2){state[pane1].edge=pane2;state[pane2].edge=pane1;var cancelled=false;if(false===_execCallback(pane1,options[pane1].onswap_start))cancelled=true;if(!cancelled&&false===_execCallback(pane2,options[pane2].onswap_start))cancelled=true;if(cancelled){state[pane1].edge=pane1;state[pane2].edge=pane2;return;} -var -oPane1=copy(pane1) -,oPane2=copy(pane2) -,sizes={} -;sizes[pane1]=oPane1?oPane1.state.size:0;sizes[pane2]=oPane2?oPane2.state.size:0;$Ps[pane1]=false;$Ps[pane2]=false;state[pane1]={};state[pane2]={};if($Ts[pane1])$Ts[pane1].remove();if($Ts[pane2])$Ts[pane2].remove();if($Rs[pane1])$Rs[pane1].remove();if($Rs[pane2])$Rs[pane2].remove();$Rs[pane1]=$Rs[pane2]=$Ts[pane1]=$Ts[pane2]=false;move(oPane1,pane2);move(oPane2,pane1);oPane1=oPane2=sizes=null;if($Ps[pane1])$Ps[pane1].css(_c.visible);if($Ps[pane2])$Ps[pane2].css(_c.visible);resizeAll();_execCallback(pane1,options[pane1].onswap_end||options[pane1].onswap);_execCallback(pane2,options[pane2].onswap_end||options[pane2].onswap);return;function copy(n){var -$P=$Ps[n] -,$C=$Cs[n] -;return!$P?false:{pane:n -,P:$P?$P[0]:false -,C:$C?$C[0]:false -,state:$.extend({},state[n]) -,options:$.extend({},options[n])}};function move(oPane,pane){if(!oPane)return;var -P=oPane.P -,C=oPane.C -,oldPane=oPane.pane -,c=_c[pane] -,side=c.side.toLowerCase() -,inset="inset"+c.side -,s=$.extend({},state[pane]) -,o=options[pane] -,fx={resizerCursor:o.resizerCursor} -,re,size,pos -;$.each("fxName,fxSpeed,fxSettings".split(","),function(i,k){fx[k]=o[k];fx[k+"_open"]=o[k+"_open"];fx[k+"_close"]=o[k+"_close"];});$Ps[pane]=$(P) -.data("layoutEdge",pane) -.css(_c.hidden) -.css(c.cssReq) -;$Cs[pane]=C?$(C):false;options[pane]=$.extend({},oPane.options,fx);state[pane]=$.extend({},oPane.state);re=new RegExp(o.paneClass+"-"+oldPane,"g");P.className=P.className.replace(re,o.paneClass+"-"+pane);initHandles(pane);if(c.dir!=_c[oldPane].dir){size=sizes[pane]||0;setSizeLimits(pane);size=max(size,state[pane].minSize);manualSizePane(pane,size,true);} -else -$Rs[pane].css(side,sC[inset]+(state[pane].isVisible?getPaneSize(pane):0));if(oPane.state.isVisible&&!s.isVisible) -setAsOpen(pane,true);else{setAsClosed(pane);bindStartSlidingEvent(pane,true);} -oPane=null;};};function keyDown(evt){if(!evt)return true;var code=evt.keyCode;if(code<33)return true;var -PANE={38:"north" -,40:"south" -,37:"west" -,39:"east"} -,ALT=evt.altKey -,SHIFT=evt.shiftKey -,CTRL=evt.ctrlKey -,CURSOR=(CTRL&&code>=37&&code<=40) -,o,k,m,pane -;if(CURSOR&&options[PANE[code]].enableCursorHotkey) -pane=PANE[code];else if(CTRL||SHIFT) -$.each(_c.borderPanes.split(","),function(i,p){o=options[p];k=o.customHotkey;m=o.customHotkeyModifier;if((SHIFT&&m=="SHIFT")||(CTRL&&m=="CTRL")||(CTRL&&SHIFT)){if(k&&code==(isNaN(k)||k<=9?k.toUpperCase().charCodeAt(0):k)){pane=p;return false;}}});if(!pane||!$Ps[pane]||!options[pane].closable||state[pane].isHidden) -return true;toggle(pane);evt.stopPropagation();evt.returnValue=false;return false;};function allowOverflow(el){if(this&&this.tagName)el=this;var $P;if(isStr(el)) -$P=$Ps[el];else if($(el).data("layoutRole")) -$P=$(el);else -$(el).parents().each(function(){if($(this).data("layoutRole")){$P=$(this);return false;}});if(!$P||!$P.length)return;var -pane=$P.data("layoutEdge") -,s=state[pane] -;if(s.cssSaved) -resetOverflow(pane);if(s.isSliding||s.isResizing||s.isClosed){s.cssSaved=false;return;} -var -newCSS={zIndex:(_c.zIndex.pane_normal+2)} -,curCSS={} -,of=$P.css("overflow") -,ofX=$P.css("overflowX") -,ofY=$P.css("overflowY") -;if(of!="visible"){curCSS.overflow=of;newCSS.overflow="visible";} -if(ofX&&!ofX.match(/visible|auto/)){curCSS.overflowX=ofX;newCSS.overflowX="visible";} -if(ofY&&!ofY.match(/visible|auto/)){curCSS.overflowY=ofX;newCSS.overflowY="visible";} -s.cssSaved=curCSS;$P.css(newCSS);$.each(_c.allPanes.split(","),function(i,p){if(p!=pane)resetOverflow(p);});};function resetOverflow(el){if(this&&this.tagName)el=this;var $P;if(isStr(el)) -$P=$Ps[el];else if($(el).data("layoutRole")) -$P=$(el);else -$(el).parents().each(function(){if($(this).data("layoutRole")){$P=$(this);return false;}});if(!$P||!$P.length)return;var -pane=$P.data("layoutEdge") -,s=state[pane] -,CSS=s.cssSaved||{} -;if(!s.isSliding&&!s.isResizing) -$P.css("zIndex",_c.zIndex.pane_normal);$P.css(CSS);s.cssSaved=false;};function getBtn(selector,pane,action){var $E=$(selector);if(!$E.length) -alert(lang.errButton+lang.selector+": "+selector);else if(_c.borderPanes.indexOf(pane)==-1) -alert(lang.errButton+lang.Pane.toLowerCase()+": "+pane);else{var btn=options[pane].buttonClass+"-"+action;$E -.addClass(btn+" "+btn+"-"+pane) -.data("layoutName",options.name) -;return $E;} -return null;};function bindButton(selector,action,pane){switch(action.toLowerCase()){case"toggle":addToggleBtn(selector,pane);break;case"open":addOpenBtn(selector,pane);break;case"close":addCloseBtn(selector,pane);break;case"pin":addPinBtn(selector,pane);break;case"toggle-slide":addToggleBtn(selector,pane,true);break;case"open-slide":addOpenBtn(selector,pane,true);break;}};function addToggleBtn(selector,pane,slide){var $E=getBtn(selector,pane,"toggle");if($E) -$E.click(function(evt){toggle(pane,!!slide);evt.stopPropagation();});};function addOpenBtn(selector,pane,slide){var $E=getBtn(selector,pane,"open");if($E) -$E -.attr("title",lang.Open) -.click(function(evt){open(pane,!!slide);evt.stopPropagation();}) -;};function addCloseBtn(selector,pane){var $E=getBtn(selector,pane,"close");if($E) -$E -.attr("title",lang.Close) -.click(function(evt){close(pane);evt.stopPropagation();}) -;};function addPinBtn(selector,pane){var $E=getBtn(selector,pane,"pin");if($E){var s=state[pane];$E.click(function(evt){setPinState($(this),pane,(s.isSliding||s.isClosed));if(s.isSliding||s.isClosed)open(pane);else close(pane);evt.stopPropagation();});setPinState($E,pane,(!s.isClosed&&!s.isSliding));_c[pane].pins.push(selector);}};function syncPinBtns(pane,doPin){$.each(_c[pane].pins,function(i,selector){setPinState($(selector),pane,doPin);});};function setPinState($Pin,pane,doPin){var updown=$Pin.attr("pin");if(updown&&doPin==(updown=="down"))return;var -pin=options[pane].buttonClass+"-pin" -,side=pin+"-"+pane -,UP=pin+"-up "+side+"-up" -,DN=pin+"-down "+side+"-down" -;$Pin -.attr("pin",doPin?"down":"up") -.attr("title",doPin?lang.Unpin:lang.Pin) -.removeClass(doPin?UP:DN) -.addClass(doPin?DN:UP) -;};function isCookiesEnabled(){return(navigator.cookieEnabled!=0);};function getCookie(opts){var -o=$.extend({},options.cookie,opts||{}) -,name=o.name||options.name||"Layout" -,c=document.cookie -,cs=c?c.split(';'):[] -,pair -;for(var i=0,n=cs.length;i0) -date.setDate(date.getDate()+o.expires);else{date.setYear(1970);clear=true;}} -if(date)params+=';expires='+date.toUTCString();if(o.path)params+=';path='+o.path;if(o.domain)params+=';domain='+o.domain;if(o.secure)params+=';secure';if(clear){state.cookie={};document.cookie=name+'='+params;} -else{state.cookie=getState(keys||o.keys);document.cookie=name+'='+encodeURIComponent(encodeJSON(state.cookie))+params;} -return $.extend({},state.cookie);};function deleteCookie(){saveCookie('',{expires:-1});};function loadCookie(opts){var o=getCookie(opts);if(o){state.cookie=$.extend({},o);loadState(o);} -return o;};function loadState(opts,animate){$.extend(true,options,opts);if(state.initialized){var pane,o,v,a=!animate;$.each(_c.allPanes.split(","),function(idx,pane){o=opts[pane];if(typeof o!='object')return;v=o.initHidden;if(v===true)hide(pane,a);if(v===false)show(pane,0,a);v=o.size;if(v>0)sizePane(pane,v);v=o.initClosed;if(v===true)close(pane,0,a);if(v===false)open(pane,0,a);});}};function getState(keys){var -data={} -,alt={isClosed:'initClosed',isHidden:'initHidden'} -,pair,pane,key,val -;if(!keys)keys=options.cookie.keys;if($.isArray(keys))keys=keys.join(",");keys=keys.replace(/__/g,".").split(',');for(var i=0,n=keys.length;iglobal->MAIN_MENU_USE_JQUERY_LAYOUT) || defined('REQUIRE_JQUERY_LAYOUT')) { - print ''."\n"; + print ''."\n"; } // jQuery jnotify if (empty($conf->global->MAIN_DISABLE_JQUERY_JNOTIFY)) print ''."\n"; From be97cce9dec47f1972eef089a865fd048f24669b Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 6 Jul 2011 12:09:19 +0000 Subject: [PATCH 16/66] Minor speed enhancement --- htdocs/main.inc.php | 29 +++++++++++------------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/htdocs/main.inc.php b/htdocs/main.inc.php index 167427a6b2b..d4e61d77b6d 100644 --- a/htdocs/main.inc.php +++ b/htdocs/main.inc.php @@ -26,7 +26,7 @@ * \file htdocs/main.inc.php * \ingroup core * \brief File that defines environment for Dolibarr pages only (variables not required by scripts) - * \version $Id: main.inc.php,v 1.750 2011/07/06 11:43:39 eldy Exp $ + * \version $Id: main.inc.php,v 1.751 2011/07/06 12:09:19 eldy Exp $ */ @ini_set('memory_limit', '64M'); // This may be useless if memory is hard limited by your PHP @@ -904,12 +904,11 @@ function top_htmlhead($head, $title='', $disablejs=0, $disablehead=0, $arrayofjs print ''."\n"; $jquerytheme = 'smoothness'; if (!empty($conf->global->MAIN_USE_JQUERY_THEME)) $jquerytheme = $conf->global->MAIN_USE_JQUERY_THEME; - print ''."\n"; // JQuery - print ''."\n"; // Tooltip - print ''."\n"; // JNotify - //print ''."\n"; // Lightbox - // jQuery fileupload - if (! empty($conf->global->MAIN_USE_JQUERY_FILEUPLOAD)) + print ''."\n"; // JQuery + print ''."\n"; // Tooltip + print ''."\n"; // JNotify + //print ''."\n"; // Lightbox + if (! empty($conf->global->MAIN_USE_JQUERY_FILEUPLOAD)) // jQuery fileupload { print ''."\n"; } @@ -1014,6 +1013,7 @@ function top_htmlhead($head, $title='', $disablejs=0, $disablehead=0, $arrayofjs } // Define tradMonths javascript array (we define this in datapicker AND in parent page to avoid errors with IE8) + print ''."\n"; + print 'var tradMonths = '.json_encode($tradMonths).';'."\n"; // Define tradMonthsMin javascript array (we define this in datapicker AND in parent page to avoid errors with IE8) $tradMonthsMin=array($langs->trans("JanuaryMin"), @@ -1045,9 +1043,7 @@ function top_htmlhead($head, $title='', $disablejs=0, $disablehead=0, $arrayofjs $langs->trans("NovemberMin"), $langs->trans("DecemberMin") ); - print ''."\n"; + print 'var tradMonthsMin = '.json_encode($tradMonthsMin).';'."\n"; // Define tradDays javascript array (we define this in datapicker AND in parent page to avoid errors with IE8) $tradDays=array($langs->trans("Monday"), @@ -1058,9 +1054,7 @@ function top_htmlhead($head, $title='', $disablejs=0, $disablehead=0, $arrayofjs $langs->trans("Saturday"), $langs->trans("Sunday") ); - print ''."\n"; + print 'var tradDays = '.json_encode($tradDays).';'."\n"; // Define tradDaysMin javascript array (we define this in datapicker AND in parent page to avoid errors with IE8) $tradDaysMin=array($langs->trans("MondayMin"), @@ -1071,8 +1065,7 @@ function top_htmlhead($head, $title='', $disablejs=0, $disablehead=0, $arrayofjs $langs->trans("SaturdayMin"), $langs->trans("SundayMin") ); - print ''."\n"; if (! empty($head)) print $head."\n"; From f54d056f0f556c97cee453d9e1b91c98f0d7a4ca Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 6 Jul 2011 12:44:51 +0000 Subject: [PATCH 17/66] Perf: ckeditor was seriously slowing everything. So now we use the ckeditor basic to load it only when required. --- htdocs/includes/ckeditor/.htaccess | 24 + htdocs/includes/ckeditor/CHANGES.html | 128 + htdocs/includes/ckeditor/LICENSE.html | 9 +- .../_samples/adobeair/application.xml | 32 + .../ckeditor/_samples/adobeair/run.bat | 9 + .../ckeditor/_samples/adobeair/run.sh | 8 + .../ckeditor/_samples/adobeair/sample.html | 45 + htdocs/includes/ckeditor/_samples/ajax.html | 98 + htdocs/includes/ckeditor/_samples/api.html | 192 + .../ckeditor/_samples/api_dialog.html | 198 + .../ckeditor/_samples/api_dialog/my_dialog.js | 28 + .../ckeditor/_samples/asp/advanced.asp | 105 + .../includes/ckeditor/_samples/asp/events.asp | 136 + .../includes/ckeditor/_samples/asp/index.html | 103 + .../ckeditor/_samples/asp/replace.asp | 72 + .../ckeditor/_samples/asp/replaceall.asp | 77 + .../_samples/asp/sample_posteddata.asp | 46 + .../ckeditor/_samples/asp/standalone.asp | 72 + .../ckeditor/_samples/assets/_posteddata.php | 59 + .../_samples/assets/output_for_flash.fla | Bin 0 -> 85504 bytes .../_samples/assets/output_for_flash.swf | Bin 0 -> 15571 bytes .../ckeditor/_samples/assets/output_xhtml.css | 204 + .../ckeditor/_samples/assets/parsesample.css | 70 + .../ckeditor/_samples/assets/swfobject.js | 18 + .../includes/ckeditor/_samples/autogrow.html | 107 + htdocs/includes/ckeditor/_samples/bbcode.html | 125 + .../includes/ckeditor/_samples/devtools.html | 94 + .../ckeditor/_samples/divreplace.html | 154 + .../includes/ckeditor/_samples/enterkey.html | 115 + .../includes/ckeditor/_samples/fullpage.html | 82 + htdocs/includes/ckeditor/_samples/index.html | 116 + .../ckeditor/_samples/jqueryadapter.html | 99 + .../ckeditor/_samples/output_for_flash.html | 275 ++ .../ckeditor/_samples/output_html.html | 285 ++ .../ckeditor/_samples/output_xhtml.html | 177 + .../ckeditor/_samples/php/advanced.php | 120 + .../includes/ckeditor/_samples/php/events.php | 153 + .../includes/ckeditor/_samples/php/index.html | 47 + .../ckeditor/_samples/php/replace.php | 87 + .../ckeditor/_samples/php/replaceall.php | 88 + .../ckeditor/_samples/php/standalone.php | 83 + .../ckeditor/_samples/placeholder.html | 81 + .../includes/ckeditor/_samples/readonly.html | 91 + .../ckeditor/_samples/replacebyclass.html | 64 + .../ckeditor/_samples/replacebycode.html | 97 + htdocs/includes/ckeditor/_samples/sample.css | 163 + htdocs/includes/ckeditor/_samples/sample.js | 65 + .../ckeditor/_samples/sample_posteddata.php | 21 + .../ckeditor/_samples/sharedspaces.html | 153 + htdocs/includes/ckeditor/_samples/skins.html | 110 + .../ckeditor/_samples/stylesheetparser.html | 93 + .../ckeditor/_samples/tableresize.html | 115 + .../includes/ckeditor/_samples/ui_color.html | 129 + .../ckeditor/_samples/ui_languages.html | 134 + .../ckeditor/_source/adapters/jquery.js | 306 ++ .../ckeditor/_source/core/_bootstrap.js | 87 + .../ckeditor/_source/core/ckeditor.js | 141 + .../ckeditor/_source/core/ckeditor_base.js | 227 ++ .../ckeditor/_source/core/ckeditor_basic.js | 238 ++ .../includes/ckeditor/_source/core/command.js | 209 ++ .../_source/core/commanddefinition.js | 129 + .../includes/ckeditor/_source/core/config.js | 431 +++ .../ckeditor/_source/core/dataprocessor.js | 65 + htdocs/includes/ckeditor/_source/core/dom.js | 20 + .../ckeditor/_source/core/dom/comment.js | 32 + .../ckeditor/_source/core/dom/document.js | 251 ++ .../_source/core/dom/documentfragment.js | 49 + .../ckeditor/_source/core/dom/domobject.js | 258 ++ .../ckeditor/_source/core/dom/element.js | 1681 +++++++++ .../ckeditor/_source/core/dom/elementpath.js | 119 + .../ckeditor/_source/core/dom/event.js | 145 + .../ckeditor/_source/core/dom/node.js | 695 ++++ .../ckeditor/_source/core/dom/nodelist.js | 26 + .../ckeditor/_source/core/dom/range.js | 2032 ++++++++++ .../ckeditor/_source/core/dom/rangelist.js | 213 ++ .../ckeditor/_source/core/dom/text.js | 128 + .../ckeditor/_source/core/dom/walker.js | 462 +++ .../ckeditor/_source/core/dom/window.js | 96 + htdocs/includes/ckeditor/_source/core/dtd.js | 266 ++ .../includes/ckeditor/_source/core/editor.js | 1059 ++++++ .../ckeditor/_source/core/editor_basic.js | 186 + htdocs/includes/ckeditor/_source/core/env.js | 291 ++ .../includes/ckeditor/_source/core/event.js | 342 ++ .../ckeditor/_source/core/eventInfo.js | 120 + .../ckeditor/_source/core/focusmanager.js | 152 + .../ckeditor/_source/core/htmlparser.js | 224 ++ .../_source/core/htmlparser/basicwriter.js | 145 + .../ckeditor/_source/core/htmlparser/cdata.js | 43 + .../_source/core/htmlparser/comment.js | 60 + .../_source/core/htmlparser/element.js | 308 ++ .../_source/core/htmlparser/filter.js | 288 ++ .../_source/core/htmlparser/fragment.js | 497 +++ .../ckeditor/_source/core/htmlparser/text.js | 55 + htdocs/includes/ckeditor/_source/core/lang.js | 157 + .../includes/ckeditor/_source/core/loader.js | 240 ++ .../ckeditor/_source/core/plugindefinition.js | 83 + .../includes/ckeditor/_source/core/plugins.js | 103 + .../ckeditor/_source/core/resourcemanager.js | 238 ++ .../ckeditor/_source/core/scriptloader.js | 180 + .../includes/ckeditor/_source/core/skins.js | 184 + .../includes/ckeditor/_source/core/themes.js | 19 + .../includes/ckeditor/_source/core/tools.js | 763 ++++ htdocs/includes/ckeditor/_source/core/ui.js | 128 + .../ckeditor/_source/lang/_languages.js | 84 + .../_source/lang/_translationstatus.txt | 61 + htdocs/includes/ckeditor/_source/lang/af.js | 812 ++++ htdocs/includes/ckeditor/_source/lang/ar.js | 812 ++++ htdocs/includes/ckeditor/_source/lang/bg.js | 812 ++++ htdocs/includes/ckeditor/_source/lang/bn.js | 812 ++++ htdocs/includes/ckeditor/_source/lang/bs.js | 812 ++++ htdocs/includes/ckeditor/_source/lang/ca.js | 812 ++++ htdocs/includes/ckeditor/_source/lang/cs.js | 812 ++++ htdocs/includes/ckeditor/_source/lang/cy.js | 812 ++++ htdocs/includes/ckeditor/_source/lang/da.js | 812 ++++ htdocs/includes/ckeditor/_source/lang/de.js | 812 ++++ htdocs/includes/ckeditor/_source/lang/el.js | 812 ++++ .../includes/ckeditor/_source/lang/en-au.js | 812 ++++ .../includes/ckeditor/_source/lang/en-ca.js | 812 ++++ .../includes/ckeditor/_source/lang/en-gb.js | 812 ++++ htdocs/includes/ckeditor/_source/lang/en.js | 812 ++++ htdocs/includes/ckeditor/_source/lang/eo.js | 812 ++++ htdocs/includes/ckeditor/_source/lang/es.js | 812 ++++ htdocs/includes/ckeditor/_source/lang/et.js | 812 ++++ htdocs/includes/ckeditor/_source/lang/eu.js | 812 ++++ htdocs/includes/ckeditor/_source/lang/fa.js | 812 ++++ htdocs/includes/ckeditor/_source/lang/fi.js | 812 ++++ htdocs/includes/ckeditor/_source/lang/fo.js | 812 ++++ .../includes/ckeditor/_source/lang/fr-ca.js | 812 ++++ htdocs/includes/ckeditor/_source/lang/fr.js | 812 ++++ htdocs/includes/ckeditor/_source/lang/gl.js | 812 ++++ htdocs/includes/ckeditor/_source/lang/gu.js | 812 ++++ htdocs/includes/ckeditor/_source/lang/he.js | 812 ++++ htdocs/includes/ckeditor/_source/lang/hi.js | 812 ++++ htdocs/includes/ckeditor/_source/lang/hr.js | 812 ++++ htdocs/includes/ckeditor/_source/lang/hu.js | 812 ++++ htdocs/includes/ckeditor/_source/lang/is.js | 812 ++++ htdocs/includes/ckeditor/_source/lang/it.js | 812 ++++ htdocs/includes/ckeditor/_source/lang/ja.js | 812 ++++ htdocs/includes/ckeditor/_source/lang/ka.js | 812 ++++ htdocs/includes/ckeditor/_source/lang/km.js | 812 ++++ htdocs/includes/ckeditor/_source/lang/ko.js | 812 ++++ htdocs/includes/ckeditor/_source/lang/lt.js | 812 ++++ htdocs/includes/ckeditor/_source/lang/lv.js | 812 ++++ htdocs/includes/ckeditor/_source/lang/mn.js | 812 ++++ htdocs/includes/ckeditor/_source/lang/ms.js | 812 ++++ htdocs/includes/ckeditor/_source/lang/nb.js | 812 ++++ htdocs/includes/ckeditor/_source/lang/nl.js | 812 ++++ htdocs/includes/ckeditor/_source/lang/no.js | 812 ++++ htdocs/includes/ckeditor/_source/lang/pl.js | 812 ++++ .../includes/ckeditor/_source/lang/pt-br.js | 811 ++++ htdocs/includes/ckeditor/_source/lang/pt.js | 812 ++++ htdocs/includes/ckeditor/_source/lang/ro.js | 812 ++++ htdocs/includes/ckeditor/_source/lang/ru.js | 812 ++++ htdocs/includes/ckeditor/_source/lang/sk.js | 812 ++++ htdocs/includes/ckeditor/_source/lang/sl.js | 812 ++++ .../includes/ckeditor/_source/lang/sr-latn.js | 812 ++++ htdocs/includes/ckeditor/_source/lang/sr.js | 812 ++++ htdocs/includes/ckeditor/_source/lang/sv.js | 811 ++++ htdocs/includes/ckeditor/_source/lang/th.js | 812 ++++ htdocs/includes/ckeditor/_source/lang/tr.js | 811 ++++ htdocs/includes/ckeditor/_source/lang/uk.js | 812 ++++ htdocs/includes/ckeditor/_source/lang/vi.js | 812 ++++ .../includes/ckeditor/_source/lang/zh-cn.js | 812 ++++ htdocs/includes/ckeditor/_source/lang/zh.js | 812 ++++ .../plugins/a11yhelp/dialogs/a11yhelp.js | 222 ++ .../_source/plugins/a11yhelp/lang/en.js | 108 + .../_source/plugins/a11yhelp/lang/he.js | 216 ++ .../_source/plugins/a11yhelp/plugin.js | 47 + .../_source/plugins/about/dialogs/about.js | 76 + .../plugins/about/dialogs/logo_ckeditor.png | Bin 0 -> 2759 bytes .../ckeditor/_source/plugins/about/plugin.js | 24 + .../_source/plugins/adobeair/plugin.js | 228 ++ .../ckeditor/_source/plugins/ajax/plugin.js | 152 + .../_source/plugins/autogrow/plugin.js | 88 + .../_source/plugins/basicstyles/plugin.js | 101 + .../ckeditor/_source/plugins/bbcode/plugin.js | 931 +++++ .../ckeditor/_source/plugins/bidi/plugin.js | 334 ++ .../_source/plugins/blockquote/plugin.js | 305 ++ .../ckeditor/_source/plugins/button/plugin.js | 290 ++ .../plugins/clipboard/dialogs/paste.js | 223 ++ .../_source/plugins/clipboard/plugin.js | 453 +++ .../_source/plugins/colorbutton/plugin.js | 294 ++ .../colordialog/dialogs/colordialog.js | 340 ++ .../_source/plugins/colordialog/plugin.js | 15 + .../_source/plugins/contextmenu/plugin.js | 177 + .../_source/plugins/devtools/lang/en.js | 16 + .../_source/plugins/devtools/plugin.js | 173 + .../plugins/dialog/dialogDefinition.js | 1166 ++++++ .../ckeditor/_source/plugins/dialog/plugin.js | 3289 +++++++++++++++++ .../_source/plugins/dialogadvtab/plugin.js | 207 ++ .../_source/plugins/dialogui/plugin.js | 1541 ++++++++ .../_source/plugins/div/dialogs/div.js | 535 +++ .../ckeditor/_source/plugins/div/plugin.js | 121 + .../plugins/docprops/dialogs/docprops.js | 686 ++++ .../_source/plugins/docprops/plugin.js | 22 + .../_source/plugins/domiterator/plugin.js | 361 ++ .../_source/plugins/editingblock/plugin.js | 275 ++ .../_source/plugins/elementspath/plugin.js | 218 ++ .../_source/plugins/enterkey/plugin.js | 413 +++ .../_source/plugins/entities/plugin.js | 250 ++ .../_source/plugins/fakeobjects/plugin.js | 175 + .../_source/plugins/filebrowser/plugin.js | 524 +++ .../_source/plugins/find/dialogs/find.js | 903 +++++ .../ckeditor/_source/plugins/find/plugin.js | 47 + .../_source/plugins/flash/dialogs/flash.js | 673 ++++ .../plugins/flash/images/placeholder.png | Bin 0 -> 256 bytes .../ckeditor/_source/plugins/flash/plugin.js | 154 + .../_source/plugins/floatpanel/plugin.js | 428 +++ .../ckeditor/_source/plugins/font/plugin.js | 234 ++ .../ckeditor/_source/plugins/format/plugin.js | 197 + .../_source/plugins/forms/dialogs/button.js | 118 + .../_source/plugins/forms/dialogs/checkbox.js | 153 + .../_source/plugins/forms/dialogs/form.js | 177 + .../plugins/forms/dialogs/hiddenfield.js | 100 + .../_source/plugins/forms/dialogs/radio.js | 135 + .../_source/plugins/forms/dialogs/select.js | 558 +++ .../_source/plugins/forms/dialogs/textarea.js | 114 + .../plugins/forms/dialogs/textfield.js | 199 + .../plugins/forms/images/hiddenfield.gif | Bin 0 -> 105 bytes .../ckeditor/_source/plugins/forms/plugin.js | 288 ++ .../_source/plugins/horizontalrule/plugin.js | 48 + .../plugins/htmldataprocessor/plugin.js | 595 +++ .../_source/plugins/htmlwriter/plugin.js | 319 ++ .../_source/plugins/iframe/dialogs/iframe.js | 229 ++ .../plugins/iframe/images/placeholder.png | Bin 0 -> 449 bytes .../ckeditor/_source/plugins/iframe/plugin.js | 87 + .../_source/plugins/iframedialog/plugin.js | 188 + .../_source/plugins/image/dialogs/image.js | 1406 +++++++ .../ckeditor/_source/plugins/image/plugin.js | 81 + .../ckeditor/_source/plugins/indent/plugin.js | 461 +++ .../_source/plugins/justify/plugin.js | 253 ++ .../_source/plugins/keystrokes/plugin.js | 225 ++ .../_source/plugins/link/dialogs/anchor.js | 144 + .../_source/plugins/link/dialogs/link.js | 1424 +++++++ .../_source/plugins/link/images/anchor.gif | Bin 0 -> 184 bytes .../ckeditor/_source/plugins/link/plugin.js | 374 ++ .../ckeditor/_source/plugins/list/plugin.js | 734 ++++ .../_source/plugins/listblock/plugin.js | 268 ++ .../plugins/liststyle/dialogs/liststyle.js | 225 ++ .../_source/plugins/liststyle/plugin.js | 66 + .../_source/plugins/maximize/plugin.js | 352 ++ .../ckeditor/_source/plugins/menu/plugin.js | 541 +++ .../_source/plugins/menubutton/plugin.js | 98 + .../_source/plugins/newpage/plugin.js | 54 + .../plugins/pagebreak/images/pagebreak.gif | Bin 0 -> 54 bytes .../_source/plugins/pagebreak/plugin.js | 164 + .../ckeditor/_source/plugins/panel/plugin.js | 403 ++ .../_source/plugins/panelbutton/plugin.js | 144 + .../plugins/pastefromword/filter/default.js | 1367 +++++++ .../_source/plugins/pastefromword/plugin.js | 141 + .../plugins/pastetext/dialogs/pastetext.js | 67 + .../_source/plugins/pastetext/plugin.js | 98 + .../placeholder/dialogs/placeholder.js | 71 + .../_source/plugins/placeholder/lang/en.js | 16 + .../_source/plugins/placeholder/lang/he.js | 16 + .../plugins/placeholder/placeholder.gif | Bin 0 -> 96 bytes .../_source/plugins/placeholder/plugin.js | 171 + .../ckeditor/_source/plugins/popup/plugin.js | 64 + .../_source/plugins/preview/plugin.js | 109 + .../ckeditor/_source/plugins/print/plugin.js | 42 + .../_source/plugins/removeformat/plugin.js | 185 + .../ckeditor/_source/plugins/resize/plugin.js | 168 + .../_source/plugins/richcombo/plugin.js | 381 ++ .../ckeditor/_source/plugins/save/plugin.js | 56 + .../_source/plugins/scayt/dialogs/options.js | 537 +++ .../_source/plugins/scayt/dialogs/toolbar.css | 71 + .../ckeditor/_source/plugins/scayt/plugin.js | 964 +++++ .../_source/plugins/selection/plugin.js | 1601 ++++++++ .../showblocks/images/block_address.png | Bin 0 -> 171 bytes .../showblocks/images/block_blockquote.png | Bin 0 -> 181 bytes .../plugins/showblocks/images/block_div.png | Bin 0 -> 136 bytes .../plugins/showblocks/images/block_h1.png | Bin 0 -> 127 bytes .../plugins/showblocks/images/block_h2.png | Bin 0 -> 134 bytes .../plugins/showblocks/images/block_h3.png | Bin 0 -> 131 bytes .../plugins/showblocks/images/block_h4.png | Bin 0 -> 133 bytes .../plugins/showblocks/images/block_h5.png | Bin 0 -> 133 bytes .../plugins/showblocks/images/block_h6.png | Bin 0 -> 129 bytes .../plugins/showblocks/images/block_p.png | Bin 0 -> 119 bytes .../plugins/showblocks/images/block_pre.png | Bin 0 -> 136 bytes .../_source/plugins/showblocks/plugin.js | 160 + .../_source/plugins/showborders/plugin.js | 207 ++ .../_source/plugins/smiley/dialogs/smiley.js | 224 ++ .../plugins/smiley/images/angel_smile.gif | Bin 0 -> 465 bytes .../plugins/smiley/images/angry_smile.gif | Bin 0 -> 443 bytes .../plugins/smiley/images/broken_heart.gif | Bin 0 -> 192 bytes .../plugins/smiley/images/confused_smile.gif | Bin 0 -> 464 bytes .../plugins/smiley/images/cry_smile.gif | Bin 0 -> 468 bytes .../plugins/smiley/images/devil_smile.gif | Bin 0 -> 436 bytes .../smiley/images/embaressed_smile.gif | Bin 0 -> 442 bytes .../plugins/smiley/images/envelope.gif | Bin 0 -> 426 bytes .../_source/plugins/smiley/images/heart.gif | Bin 0 -> 183 bytes .../_source/plugins/smiley/images/kiss.gif | Bin 0 -> 241 bytes .../plugins/smiley/images/lightbulb.gif | Bin 0 -> 368 bytes .../plugins/smiley/images/omg_smile.gif | Bin 0 -> 451 bytes .../plugins/smiley/images/regular_smile.gif | Bin 0 -> 450 bytes .../plugins/smiley/images/sad_smile.gif | Bin 0 -> 460 bytes .../plugins/smiley/images/shades_smile.gif | Bin 0 -> 449 bytes .../plugins/smiley/images/teeth_smile.gif | Bin 0 -> 442 bytes .../plugins/smiley/images/thumbs_down.gif | Bin 0 -> 408 bytes .../plugins/smiley/images/thumbs_up.gif | Bin 0 -> 396 bytes .../plugins/smiley/images/tounge_smile.gif | Bin 0 -> 446 bytes .../images/whatchutalkingabout_smile.gif | Bin 0 -> 452 bytes .../plugins/smiley/images/wink_smile.gif | Bin 0 -> 458 bytes .../ckeditor/_source/plugins/smiley/plugin.js | 94 + .../_source/plugins/sourcearea/plugin.js | 209 ++ .../specialchar/dialogs/specialchar.js | 350 ++ .../_source/plugins/specialchar/lang/en.js | 89 + .../_source/plugins/specialchar/plugin.js | 70 + .../ckeditor/_source/plugins/styles/plugin.js | 1696 +++++++++ .../_source/plugins/styles/styles/default.js | 88 + .../_source/plugins/stylescombo/plugin.js | 218 ++ .../plugins/stylesheetparser/plugin.js | 148 + .../ckeditor/_source/plugins/tab/plugin.js | 367 ++ .../_source/plugins/table/dialogs/table.js | 602 +++ .../ckeditor/_source/plugins/table/plugin.js | 78 + .../_source/plugins/tableresize/plugin.js | 443 +++ .../plugins/tabletools/dialogs/tableCell.js | 525 +++ .../_source/plugins/tabletools/plugin.js | 1194 ++++++ .../plugins/templates/dialogs/templates.js | 234 ++ .../_source/plugins/templates/plugin.js | 99 + .../plugins/templates/templates/default.js | 94 + .../templates/templates/images/template1.gif | Bin 0 -> 375 bytes .../templates/templates/images/template2.gif | Bin 0 -> 333 bytes .../templates/templates/images/template3.gif | Bin 0 -> 422 bytes .../_source/plugins/toolbar/plugin.js | 545 +++ .../plugins/uicolor/dialogs/uicolor.js | 205 + .../_source/plugins/uicolor/lang/en.js | 15 + .../_source/plugins/uicolor/lang/he.js | 15 + .../_source/plugins/uicolor/plugin.js | 37 + .../_source/plugins/uicolor/uicolor.gif | Bin 0 -> 1108 bytes .../plugins/uicolor/yui/assets/hue_bg.png | Bin 0 -> 1120 bytes .../plugins/uicolor/yui/assets/hue_thumb.png | Bin 0 -> 195 bytes .../uicolor/yui/assets/picker_mask.png | Bin 0 -> 12174 bytes .../uicolor/yui/assets/picker_thumb.png | Bin 0 -> 192 bytes .../plugins/uicolor/yui/assets/yui.css | 15 + .../_source/plugins/uicolor/yui/yui.js | 71 + .../ckeditor/_source/plugins/undo/plugin.js | 580 +++ .../_source/plugins/wsc/dialogs/ciframe.html | 49 + .../plugins/wsc/dialogs/tmpFrameset.html | 52 + .../_source/plugins/wsc/dialogs/wsc.css | 82 + .../_source/plugins/wsc/dialogs/wsc.js | 192 + .../ckeditor/_source/plugins/wsc/plugin.js | 33 + .../_source/plugins/wysiwygarea/plugin.js | 1351 +++++++ .../ckeditor/_source/plugins/xml/plugin.js | 170 + .../ckeditor/_source/skins/kama/dialog.css | 943 +++++ .../ckeditor/_source/skins/kama/editor.css | 25 + .../_source/skins/kama/elementspath.css | 73 + .../ckeditor/_source/skins/kama/icons.css | 366 ++ .../ckeditor/_source/skins/kama/icons.png | Bin 0 -> 5598 bytes .../ckeditor/_source/skins/kama/icons_rtl.png | Bin 0 -> 5600 bytes .../skins/kama/images/dialog_sides.gif | Bin 0 -> 48 bytes .../skins/kama/images/dialog_sides.png | Bin 0 -> 178 bytes .../skins/kama/images/dialog_sides_rtl.png | Bin 0 -> 181 bytes .../_source/skins/kama/images/mini.gif | Bin 0 -> 183 bytes .../_source/skins/kama/images/noimage.png | Bin 0 -> 2115 bytes .../_source/skins/kama/images/sprites.png | Bin 0 -> 7086 bytes .../_source/skins/kama/images/sprites_ie6.png | Bin 0 -> 2724 bytes .../skins/kama/images/toolbar_start.gif | Bin 0 -> 105 bytes .../ckeditor/_source/skins/kama/mainui.css | 203 + .../ckeditor/_source/skins/kama/menu.css | 232 ++ .../ckeditor/_source/skins/kama/panel.css | 217 ++ .../ckeditor/_source/skins/kama/presets.css | 49 + .../ckeditor/_source/skins/kama/reset.css | 84 + .../ckeditor/_source/skins/kama/richcombo.css | 287 ++ .../ckeditor/_source/skins/kama/skin.js | 236 ++ .../ckeditor/_source/skins/kama/templates.css | 88 + .../ckeditor/_source/skins/kama/toolbar.css | 407 ++ .../_source/skins/office2003/dialog.css | 844 +++++ .../_source/skins/office2003/editor.css | 25 + .../_source/skins/office2003/elementspath.css | 74 + .../_source/skins/office2003/icons.css | 363 ++ .../_source/skins/office2003/icons.png | Bin 0 -> 5598 bytes .../_source/skins/office2003/icons_rtl.png | Bin 0 -> 5600 bytes .../skins/office2003/images/dialog_sides.gif | Bin 0 -> 48 bytes .../skins/office2003/images/dialog_sides.png | Bin 0 -> 178 bytes .../office2003/images/dialog_sides_rtl.png | Bin 0 -> 181 bytes .../_source/skins/office2003/images/mini.gif | Bin 0 -> 183 bytes .../skins/office2003/images/noimage.png | Bin 0 -> 2115 bytes .../skins/office2003/images/sprites.png | Bin 0 -> 6119 bytes .../skins/office2003/images/sprites_ie6.png | Bin 0 -> 2715 bytes .../_source/skins/office2003/mainui.css | 153 + .../_source/skins/office2003/menu.css | 229 ++ .../_source/skins/office2003/panel.css | 212 ++ .../_source/skins/office2003/presets.css | 49 + .../_source/skins/office2003/reset.css | 84 + .../_source/skins/office2003/richcombo.css | 309 ++ .../ckeditor/_source/skins/office2003/skin.js | 74 + .../_source/skins/office2003/templates.css | 87 + .../_source/skins/office2003/toolbar.css | 522 +++ .../ckeditor/_source/skins/v2/dialog.css | 861 +++++ .../ckeditor/_source/skins/v2/editor.css | 25 + .../_source/skins/v2/elementspath.css | 74 + .../ckeditor/_source/skins/v2/icons.css | 363 ++ .../ckeditor/_source/skins/v2/icons.png | Bin 0 -> 5598 bytes .../ckeditor/_source/skins/v2/icons_rtl.png | Bin 0 -> 5600 bytes .../_source/skins/v2/images/dialog_sides.gif | Bin 0 -> 48 bytes .../_source/skins/v2/images/dialog_sides.png | Bin 0 -> 178 bytes .../skins/v2/images/dialog_sides_rtl.png | Bin 0 -> 181 bytes .../ckeditor/_source/skins/v2/images/mini.gif | Bin 0 -> 183 bytes .../_source/skins/v2/images/noimage.png | Bin 0 -> 2115 bytes .../_source/skins/v2/images/sprites.png | Bin 0 -> 5389 bytes .../_source/skins/v2/images/sprites_ie6.png | Bin 0 -> 492 bytes .../_source/skins/v2/images/toolbar_start.gif | Bin 0 -> 105 bytes .../ckeditor/_source/skins/v2/mainui.css | 162 + .../ckeditor/_source/skins/v2/menu.css | 232 ++ .../ckeditor/_source/skins/v2/panel.css | 212 ++ .../ckeditor/_source/skins/v2/presets.css | 50 + .../ckeditor/_source/skins/v2/reset.css | 84 + .../ckeditor/_source/skins/v2/richcombo.css | 302 ++ .../ckeditor/_source/skins/v2/skin.js | 70 + .../ckeditor/_source/skins/v2/templates.css | 87 + .../ckeditor/_source/skins/v2/toolbar.css | 465 +++ .../ckeditor/_source/themes/default/theme.js | 395 ++ htdocs/includes/ckeditor/ckeditor.asp | 6 +- htdocs/includes/ckeditor/ckeditor.jgz | Bin 111394 -> 0 bytes htdocs/includes/ckeditor/ckeditor.js | 274 +- htdocs/includes/ckeditor/ckeditor.pack | 16 +- htdocs/includes/ckeditor/ckeditor_basic.js | 6 +- .../ckeditor/ckeditor_basic_source.js | 2 +- htdocs/includes/ckeditor/ckeditor_php4.php | 6 +- htdocs/includes/ckeditor/ckeditor_php5.php | 6 +- htdocs/includes/ckeditor/ckeditor_source.js | 2 +- htdocs/includes/ckeditor/contents.css | 26 +- htdocs/includes/ckeditor/lang/_languages.js | 2 +- .../ckeditor/lang/_translationstatus.txt | 115 +- htdocs/includes/ckeditor/lang/af.js | 2 +- htdocs/includes/ckeditor/lang/ar.js | 2 +- htdocs/includes/ckeditor/lang/bg.js | 2 +- htdocs/includes/ckeditor/lang/bn.js | 2 +- htdocs/includes/ckeditor/lang/bs.js | 2 +- htdocs/includes/ckeditor/lang/ca.js | 2 +- htdocs/includes/ckeditor/lang/cs.js | 2 +- htdocs/includes/ckeditor/lang/cy.js | 2 +- htdocs/includes/ckeditor/lang/da.js | 2 +- htdocs/includes/ckeditor/lang/de.js | 2 +- htdocs/includes/ckeditor/lang/el.js | 2 +- htdocs/includes/ckeditor/lang/en-au.js | 2 +- htdocs/includes/ckeditor/lang/en-ca.js | 2 +- htdocs/includes/ckeditor/lang/en-gb.js | 2 +- htdocs/includes/ckeditor/lang/en.js | 2 +- htdocs/includes/ckeditor/lang/eo.js | 2 +- htdocs/includes/ckeditor/lang/es.js | 2 +- htdocs/includes/ckeditor/lang/et.js | 2 +- htdocs/includes/ckeditor/lang/eu.js | 2 +- htdocs/includes/ckeditor/lang/fa.js | 2 +- htdocs/includes/ckeditor/lang/fi.js | 2 +- htdocs/includes/ckeditor/lang/fo.js | 2 +- htdocs/includes/ckeditor/lang/fr-ca.js | 2 +- htdocs/includes/ckeditor/lang/fr.js | 2 +- htdocs/includes/ckeditor/lang/gl.js | 2 +- htdocs/includes/ckeditor/lang/gu.js | 2 +- htdocs/includes/ckeditor/lang/he.js | 2 +- htdocs/includes/ckeditor/lang/hi.js | 2 +- htdocs/includes/ckeditor/lang/hr.js | 2 +- htdocs/includes/ckeditor/lang/hu.js | 2 +- htdocs/includes/ckeditor/lang/is.js | 2 +- htdocs/includes/ckeditor/lang/it.js | 2 +- htdocs/includes/ckeditor/lang/ja.js | 2 +- htdocs/includes/ckeditor/lang/ka.js | 6 + htdocs/includes/ckeditor/lang/km.js | 2 +- htdocs/includes/ckeditor/lang/ko.js | 2 +- htdocs/includes/ckeditor/lang/lt.js | 2 +- htdocs/includes/ckeditor/lang/lv.js | 2 +- htdocs/includes/ckeditor/lang/mn.js | 2 +- htdocs/includes/ckeditor/lang/ms.js | 2 +- htdocs/includes/ckeditor/lang/nb.js | 2 +- htdocs/includes/ckeditor/lang/nl.js | 2 +- htdocs/includes/ckeditor/lang/no.js | 2 +- htdocs/includes/ckeditor/lang/pl.js | 2 +- htdocs/includes/ckeditor/lang/pt-br.js | 2 +- htdocs/includes/ckeditor/lang/pt.js | 2 +- htdocs/includes/ckeditor/lang/ro.js | 2 +- htdocs/includes/ckeditor/lang/ru.js | 2 +- htdocs/includes/ckeditor/lang/sk.js | 2 +- htdocs/includes/ckeditor/lang/sl.js | 2 +- htdocs/includes/ckeditor/lang/sr-latn.js | 2 +- htdocs/includes/ckeditor/lang/sr.js | 2 +- htdocs/includes/ckeditor/lang/sv.js | 2 +- htdocs/includes/ckeditor/lang/th.js | 2 +- htdocs/includes/ckeditor/lang/tr.js | 2 +- htdocs/includes/ckeditor/lang/uk.js | 2 +- htdocs/includes/ckeditor/lang/vi.js | 2 +- htdocs/includes/ckeditor/lang/zh-cn.js | 2 +- htdocs/includes/ckeditor/lang/zh.js | 2 +- .../ckeditor/plugins/a11yhelp/lang/en.js | 2 +- .../ckeditor/plugins/about/dialogs/about.js | 2 +- .../ckeditor/plugins/bbcode/plugin.js | 9 + .../plugins/clipboard/dialogs/paste.js | 4 +- .../ckeditor/plugins/devtools/lang/en.js | 6 + .../ckeditor/plugins/devtools/plugin.js | 6 + .../plugins/docprops/dialogs/docprops.js | 10 + .../ckeditor/plugins/docprops/plugin.js | 6 + .../ckeditor/plugins/find/dialogs/find.js | 5 +- .../ckeditor/plugins/flash/dialogs/flash.js | 8 +- .../ckeditor/plugins/forms/dialogs/select.js | 6 +- .../ckeditor/plugins/iframe/dialogs/iframe.js | 4 +- .../ckeditor/plugins/iframedialog/plugin.js | 2 +- .../ckeditor/plugins/image/dialogs/image.js | 12 +- .../ckeditor/plugins/link/dialogs/anchor.js | 2 +- .../ckeditor/plugins/link/dialogs/link.js | 13 +- .../plugins/liststyle/dialogs/liststyle.js | 3 +- .../plugins/pastefromword/filter/default.js | 10 +- .../plugins/pastetext/dialogs/pastetext.js | 2 +- .../ckeditor/plugins/smiley/dialogs/smiley.js | 2 +- .../plugins/stylesheetparser/plugin.js | 6 + .../ckeditor/plugins/table/dialogs/table.js | 8 +- .../ckeditor/plugins/tableresize/plugin.js | 4 +- .../plugins/templates/dialogs/templates.js | 2 +- .../includes/ckeditor/skins/kama/dialog.css | 7 +- .../includes/ckeditor/skins/kama/editor.css | 14 +- .../ckeditor/skins/kama/images/sprites.png | Bin 6248 -> 7086 bytes htdocs/includes/ckeditor/skins/kama/skin.js | 2 +- .../ckeditor/skins/office2003/dialog.css | 6 +- .../ckeditor/skins/office2003/editor.css | 8 +- .../ckeditor/skins/office2003/skin.js | 2 +- htdocs/includes/ckeditor/skins/v2/dialog.css | 6 +- htdocs/includes/ckeditor/skins/v2/editor.css | 8 +- htdocs/includes/ckeditor/skins/v2/skin.js | 2 +- .../includes/ckeditor/themes/default/theme.js | 6 +- htdocs/lib/doleditor.class.php | 11 +- 520 files changed, 122182 insertions(+), 384 deletions(-) create mode 100755 htdocs/includes/ckeditor/.htaccess create mode 100755 htdocs/includes/ckeditor/_samples/adobeair/application.xml create mode 100755 htdocs/includes/ckeditor/_samples/adobeair/run.bat create mode 100755 htdocs/includes/ckeditor/_samples/adobeair/run.sh create mode 100755 htdocs/includes/ckeditor/_samples/adobeair/sample.html create mode 100755 htdocs/includes/ckeditor/_samples/ajax.html create mode 100755 htdocs/includes/ckeditor/_samples/api.html create mode 100755 htdocs/includes/ckeditor/_samples/api_dialog.html create mode 100755 htdocs/includes/ckeditor/_samples/api_dialog/my_dialog.js create mode 100755 htdocs/includes/ckeditor/_samples/asp/advanced.asp create mode 100755 htdocs/includes/ckeditor/_samples/asp/events.asp create mode 100755 htdocs/includes/ckeditor/_samples/asp/index.html create mode 100755 htdocs/includes/ckeditor/_samples/asp/replace.asp create mode 100755 htdocs/includes/ckeditor/_samples/asp/replaceall.asp create mode 100755 htdocs/includes/ckeditor/_samples/asp/sample_posteddata.asp create mode 100755 htdocs/includes/ckeditor/_samples/asp/standalone.asp create mode 100755 htdocs/includes/ckeditor/_samples/assets/_posteddata.php create mode 100755 htdocs/includes/ckeditor/_samples/assets/output_for_flash.fla create mode 100755 htdocs/includes/ckeditor/_samples/assets/output_for_flash.swf create mode 100755 htdocs/includes/ckeditor/_samples/assets/output_xhtml.css create mode 100755 htdocs/includes/ckeditor/_samples/assets/parsesample.css create mode 100755 htdocs/includes/ckeditor/_samples/assets/swfobject.js create mode 100755 htdocs/includes/ckeditor/_samples/autogrow.html create mode 100755 htdocs/includes/ckeditor/_samples/bbcode.html create mode 100755 htdocs/includes/ckeditor/_samples/devtools.html create mode 100755 htdocs/includes/ckeditor/_samples/divreplace.html create mode 100755 htdocs/includes/ckeditor/_samples/enterkey.html create mode 100755 htdocs/includes/ckeditor/_samples/fullpage.html create mode 100755 htdocs/includes/ckeditor/_samples/index.html create mode 100755 htdocs/includes/ckeditor/_samples/jqueryadapter.html create mode 100755 htdocs/includes/ckeditor/_samples/output_for_flash.html create mode 100755 htdocs/includes/ckeditor/_samples/output_html.html create mode 100755 htdocs/includes/ckeditor/_samples/output_xhtml.html create mode 100755 htdocs/includes/ckeditor/_samples/php/advanced.php create mode 100755 htdocs/includes/ckeditor/_samples/php/events.php create mode 100755 htdocs/includes/ckeditor/_samples/php/index.html create mode 100755 htdocs/includes/ckeditor/_samples/php/replace.php create mode 100755 htdocs/includes/ckeditor/_samples/php/replaceall.php create mode 100755 htdocs/includes/ckeditor/_samples/php/standalone.php create mode 100755 htdocs/includes/ckeditor/_samples/placeholder.html create mode 100755 htdocs/includes/ckeditor/_samples/readonly.html create mode 100755 htdocs/includes/ckeditor/_samples/replacebyclass.html create mode 100755 htdocs/includes/ckeditor/_samples/replacebycode.html create mode 100755 htdocs/includes/ckeditor/_samples/sample.css create mode 100755 htdocs/includes/ckeditor/_samples/sample.js create mode 100755 htdocs/includes/ckeditor/_samples/sample_posteddata.php create mode 100755 htdocs/includes/ckeditor/_samples/sharedspaces.html create mode 100755 htdocs/includes/ckeditor/_samples/skins.html create mode 100755 htdocs/includes/ckeditor/_samples/stylesheetparser.html create mode 100755 htdocs/includes/ckeditor/_samples/tableresize.html create mode 100755 htdocs/includes/ckeditor/_samples/ui_color.html create mode 100755 htdocs/includes/ckeditor/_samples/ui_languages.html create mode 100755 htdocs/includes/ckeditor/_source/adapters/jquery.js create mode 100755 htdocs/includes/ckeditor/_source/core/_bootstrap.js create mode 100755 htdocs/includes/ckeditor/_source/core/ckeditor.js create mode 100755 htdocs/includes/ckeditor/_source/core/ckeditor_base.js create mode 100755 htdocs/includes/ckeditor/_source/core/ckeditor_basic.js create mode 100755 htdocs/includes/ckeditor/_source/core/command.js create mode 100755 htdocs/includes/ckeditor/_source/core/commanddefinition.js create mode 100755 htdocs/includes/ckeditor/_source/core/config.js create mode 100755 htdocs/includes/ckeditor/_source/core/dataprocessor.js create mode 100755 htdocs/includes/ckeditor/_source/core/dom.js create mode 100755 htdocs/includes/ckeditor/_source/core/dom/comment.js create mode 100755 htdocs/includes/ckeditor/_source/core/dom/document.js create mode 100755 htdocs/includes/ckeditor/_source/core/dom/documentfragment.js create mode 100755 htdocs/includes/ckeditor/_source/core/dom/domobject.js create mode 100755 htdocs/includes/ckeditor/_source/core/dom/element.js create mode 100755 htdocs/includes/ckeditor/_source/core/dom/elementpath.js create mode 100755 htdocs/includes/ckeditor/_source/core/dom/event.js create mode 100755 htdocs/includes/ckeditor/_source/core/dom/node.js create mode 100755 htdocs/includes/ckeditor/_source/core/dom/nodelist.js create mode 100755 htdocs/includes/ckeditor/_source/core/dom/range.js create mode 100755 htdocs/includes/ckeditor/_source/core/dom/rangelist.js create mode 100755 htdocs/includes/ckeditor/_source/core/dom/text.js create mode 100755 htdocs/includes/ckeditor/_source/core/dom/walker.js create mode 100755 htdocs/includes/ckeditor/_source/core/dom/window.js create mode 100755 htdocs/includes/ckeditor/_source/core/dtd.js create mode 100755 htdocs/includes/ckeditor/_source/core/editor.js create mode 100755 htdocs/includes/ckeditor/_source/core/editor_basic.js create mode 100755 htdocs/includes/ckeditor/_source/core/env.js create mode 100755 htdocs/includes/ckeditor/_source/core/event.js create mode 100755 htdocs/includes/ckeditor/_source/core/eventInfo.js create mode 100755 htdocs/includes/ckeditor/_source/core/focusmanager.js create mode 100755 htdocs/includes/ckeditor/_source/core/htmlparser.js create mode 100755 htdocs/includes/ckeditor/_source/core/htmlparser/basicwriter.js create mode 100755 htdocs/includes/ckeditor/_source/core/htmlparser/cdata.js create mode 100755 htdocs/includes/ckeditor/_source/core/htmlparser/comment.js create mode 100755 htdocs/includes/ckeditor/_source/core/htmlparser/element.js create mode 100755 htdocs/includes/ckeditor/_source/core/htmlparser/filter.js create mode 100755 htdocs/includes/ckeditor/_source/core/htmlparser/fragment.js create mode 100755 htdocs/includes/ckeditor/_source/core/htmlparser/text.js create mode 100755 htdocs/includes/ckeditor/_source/core/lang.js create mode 100755 htdocs/includes/ckeditor/_source/core/loader.js create mode 100755 htdocs/includes/ckeditor/_source/core/plugindefinition.js create mode 100755 htdocs/includes/ckeditor/_source/core/plugins.js create mode 100755 htdocs/includes/ckeditor/_source/core/resourcemanager.js create mode 100755 htdocs/includes/ckeditor/_source/core/scriptloader.js create mode 100755 htdocs/includes/ckeditor/_source/core/skins.js create mode 100755 htdocs/includes/ckeditor/_source/core/themes.js create mode 100755 htdocs/includes/ckeditor/_source/core/tools.js create mode 100755 htdocs/includes/ckeditor/_source/core/ui.js create mode 100755 htdocs/includes/ckeditor/_source/lang/_languages.js create mode 100755 htdocs/includes/ckeditor/_source/lang/_translationstatus.txt create mode 100755 htdocs/includes/ckeditor/_source/lang/af.js create mode 100755 htdocs/includes/ckeditor/_source/lang/ar.js create mode 100755 htdocs/includes/ckeditor/_source/lang/bg.js create mode 100755 htdocs/includes/ckeditor/_source/lang/bn.js create mode 100755 htdocs/includes/ckeditor/_source/lang/bs.js create mode 100755 htdocs/includes/ckeditor/_source/lang/ca.js create mode 100755 htdocs/includes/ckeditor/_source/lang/cs.js create mode 100755 htdocs/includes/ckeditor/_source/lang/cy.js create mode 100755 htdocs/includes/ckeditor/_source/lang/da.js create mode 100755 htdocs/includes/ckeditor/_source/lang/de.js create mode 100755 htdocs/includes/ckeditor/_source/lang/el.js create mode 100755 htdocs/includes/ckeditor/_source/lang/en-au.js create mode 100755 htdocs/includes/ckeditor/_source/lang/en-ca.js create mode 100755 htdocs/includes/ckeditor/_source/lang/en-gb.js create mode 100755 htdocs/includes/ckeditor/_source/lang/en.js create mode 100755 htdocs/includes/ckeditor/_source/lang/eo.js create mode 100755 htdocs/includes/ckeditor/_source/lang/es.js create mode 100755 htdocs/includes/ckeditor/_source/lang/et.js create mode 100755 htdocs/includes/ckeditor/_source/lang/eu.js create mode 100755 htdocs/includes/ckeditor/_source/lang/fa.js create mode 100755 htdocs/includes/ckeditor/_source/lang/fi.js create mode 100755 htdocs/includes/ckeditor/_source/lang/fo.js create mode 100755 htdocs/includes/ckeditor/_source/lang/fr-ca.js create mode 100755 htdocs/includes/ckeditor/_source/lang/fr.js create mode 100755 htdocs/includes/ckeditor/_source/lang/gl.js create mode 100755 htdocs/includes/ckeditor/_source/lang/gu.js create mode 100755 htdocs/includes/ckeditor/_source/lang/he.js create mode 100755 htdocs/includes/ckeditor/_source/lang/hi.js create mode 100755 htdocs/includes/ckeditor/_source/lang/hr.js create mode 100755 htdocs/includes/ckeditor/_source/lang/hu.js create mode 100755 htdocs/includes/ckeditor/_source/lang/is.js create mode 100755 htdocs/includes/ckeditor/_source/lang/it.js create mode 100755 htdocs/includes/ckeditor/_source/lang/ja.js create mode 100755 htdocs/includes/ckeditor/_source/lang/ka.js create mode 100755 htdocs/includes/ckeditor/_source/lang/km.js create mode 100755 htdocs/includes/ckeditor/_source/lang/ko.js create mode 100755 htdocs/includes/ckeditor/_source/lang/lt.js create mode 100755 htdocs/includes/ckeditor/_source/lang/lv.js create mode 100755 htdocs/includes/ckeditor/_source/lang/mn.js create mode 100755 htdocs/includes/ckeditor/_source/lang/ms.js create mode 100755 htdocs/includes/ckeditor/_source/lang/nb.js create mode 100755 htdocs/includes/ckeditor/_source/lang/nl.js create mode 100755 htdocs/includes/ckeditor/_source/lang/no.js create mode 100755 htdocs/includes/ckeditor/_source/lang/pl.js create mode 100755 htdocs/includes/ckeditor/_source/lang/pt-br.js create mode 100755 htdocs/includes/ckeditor/_source/lang/pt.js create mode 100755 htdocs/includes/ckeditor/_source/lang/ro.js create mode 100755 htdocs/includes/ckeditor/_source/lang/ru.js create mode 100755 htdocs/includes/ckeditor/_source/lang/sk.js create mode 100755 htdocs/includes/ckeditor/_source/lang/sl.js create mode 100755 htdocs/includes/ckeditor/_source/lang/sr-latn.js create mode 100755 htdocs/includes/ckeditor/_source/lang/sr.js create mode 100755 htdocs/includes/ckeditor/_source/lang/sv.js create mode 100755 htdocs/includes/ckeditor/_source/lang/th.js create mode 100755 htdocs/includes/ckeditor/_source/lang/tr.js create mode 100755 htdocs/includes/ckeditor/_source/lang/uk.js create mode 100755 htdocs/includes/ckeditor/_source/lang/vi.js create mode 100755 htdocs/includes/ckeditor/_source/lang/zh-cn.js create mode 100755 htdocs/includes/ckeditor/_source/lang/zh.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/a11yhelp/dialogs/a11yhelp.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/a11yhelp/lang/en.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/a11yhelp/lang/he.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/a11yhelp/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/about/dialogs/about.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/about/dialogs/logo_ckeditor.png create mode 100755 htdocs/includes/ckeditor/_source/plugins/about/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/adobeair/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/ajax/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/autogrow/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/basicstyles/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/bbcode/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/bidi/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/blockquote/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/button/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/clipboard/dialogs/paste.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/clipboard/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/colorbutton/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/colordialog/dialogs/colordialog.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/colordialog/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/contextmenu/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/devtools/lang/en.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/devtools/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/dialog/dialogDefinition.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/dialog/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/dialogadvtab/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/dialogui/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/div/dialogs/div.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/div/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/docprops/dialogs/docprops.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/docprops/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/domiterator/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/editingblock/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/elementspath/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/enterkey/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/entities/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/fakeobjects/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/filebrowser/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/find/dialogs/find.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/find/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/flash/dialogs/flash.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/flash/images/placeholder.png create mode 100755 htdocs/includes/ckeditor/_source/plugins/flash/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/floatpanel/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/font/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/format/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/forms/dialogs/button.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/forms/dialogs/checkbox.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/forms/dialogs/form.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/forms/dialogs/hiddenfield.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/forms/dialogs/radio.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/forms/dialogs/select.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/forms/dialogs/textarea.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/forms/dialogs/textfield.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/forms/images/hiddenfield.gif create mode 100755 htdocs/includes/ckeditor/_source/plugins/forms/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/horizontalrule/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/htmldataprocessor/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/htmlwriter/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/iframe/dialogs/iframe.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/iframe/images/placeholder.png create mode 100755 htdocs/includes/ckeditor/_source/plugins/iframe/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/iframedialog/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/image/dialogs/image.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/image/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/indent/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/justify/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/keystrokes/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/link/dialogs/anchor.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/link/dialogs/link.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/link/images/anchor.gif create mode 100755 htdocs/includes/ckeditor/_source/plugins/link/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/list/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/listblock/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/liststyle/dialogs/liststyle.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/liststyle/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/maximize/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/menu/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/menubutton/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/newpage/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/pagebreak/images/pagebreak.gif create mode 100755 htdocs/includes/ckeditor/_source/plugins/pagebreak/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/panel/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/panelbutton/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/pastefromword/filter/default.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/pastefromword/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/pastetext/dialogs/pastetext.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/pastetext/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/placeholder/dialogs/placeholder.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/placeholder/lang/en.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/placeholder/lang/he.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/placeholder/placeholder.gif create mode 100755 htdocs/includes/ckeditor/_source/plugins/placeholder/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/popup/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/preview/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/print/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/removeformat/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/resize/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/richcombo/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/save/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/scayt/dialogs/options.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/scayt/dialogs/toolbar.css create mode 100755 htdocs/includes/ckeditor/_source/plugins/scayt/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/selection/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/showblocks/images/block_address.png create mode 100755 htdocs/includes/ckeditor/_source/plugins/showblocks/images/block_blockquote.png create mode 100755 htdocs/includes/ckeditor/_source/plugins/showblocks/images/block_div.png create mode 100755 htdocs/includes/ckeditor/_source/plugins/showblocks/images/block_h1.png create mode 100755 htdocs/includes/ckeditor/_source/plugins/showblocks/images/block_h2.png create mode 100755 htdocs/includes/ckeditor/_source/plugins/showblocks/images/block_h3.png create mode 100755 htdocs/includes/ckeditor/_source/plugins/showblocks/images/block_h4.png create mode 100755 htdocs/includes/ckeditor/_source/plugins/showblocks/images/block_h5.png create mode 100755 htdocs/includes/ckeditor/_source/plugins/showblocks/images/block_h6.png create mode 100755 htdocs/includes/ckeditor/_source/plugins/showblocks/images/block_p.png create mode 100755 htdocs/includes/ckeditor/_source/plugins/showblocks/images/block_pre.png create mode 100755 htdocs/includes/ckeditor/_source/plugins/showblocks/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/showborders/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/smiley/dialogs/smiley.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/smiley/images/angel_smile.gif create mode 100755 htdocs/includes/ckeditor/_source/plugins/smiley/images/angry_smile.gif create mode 100755 htdocs/includes/ckeditor/_source/plugins/smiley/images/broken_heart.gif create mode 100755 htdocs/includes/ckeditor/_source/plugins/smiley/images/confused_smile.gif create mode 100755 htdocs/includes/ckeditor/_source/plugins/smiley/images/cry_smile.gif create mode 100755 htdocs/includes/ckeditor/_source/plugins/smiley/images/devil_smile.gif create mode 100755 htdocs/includes/ckeditor/_source/plugins/smiley/images/embaressed_smile.gif create mode 100755 htdocs/includes/ckeditor/_source/plugins/smiley/images/envelope.gif create mode 100755 htdocs/includes/ckeditor/_source/plugins/smiley/images/heart.gif create mode 100755 htdocs/includes/ckeditor/_source/plugins/smiley/images/kiss.gif create mode 100755 htdocs/includes/ckeditor/_source/plugins/smiley/images/lightbulb.gif create mode 100755 htdocs/includes/ckeditor/_source/plugins/smiley/images/omg_smile.gif create mode 100755 htdocs/includes/ckeditor/_source/plugins/smiley/images/regular_smile.gif create mode 100755 htdocs/includes/ckeditor/_source/plugins/smiley/images/sad_smile.gif create mode 100755 htdocs/includes/ckeditor/_source/plugins/smiley/images/shades_smile.gif create mode 100755 htdocs/includes/ckeditor/_source/plugins/smiley/images/teeth_smile.gif create mode 100755 htdocs/includes/ckeditor/_source/plugins/smiley/images/thumbs_down.gif create mode 100755 htdocs/includes/ckeditor/_source/plugins/smiley/images/thumbs_up.gif create mode 100755 htdocs/includes/ckeditor/_source/plugins/smiley/images/tounge_smile.gif create mode 100755 htdocs/includes/ckeditor/_source/plugins/smiley/images/whatchutalkingabout_smile.gif create mode 100755 htdocs/includes/ckeditor/_source/plugins/smiley/images/wink_smile.gif create mode 100755 htdocs/includes/ckeditor/_source/plugins/smiley/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/sourcearea/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/specialchar/dialogs/specialchar.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/specialchar/lang/en.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/specialchar/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/styles/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/styles/styles/default.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/stylescombo/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/stylesheetparser/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/tab/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/table/dialogs/table.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/table/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/tableresize/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/tabletools/dialogs/tableCell.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/tabletools/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/templates/dialogs/templates.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/templates/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/templates/templates/default.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/templates/templates/images/template1.gif create mode 100755 htdocs/includes/ckeditor/_source/plugins/templates/templates/images/template2.gif create mode 100755 htdocs/includes/ckeditor/_source/plugins/templates/templates/images/template3.gif create mode 100755 htdocs/includes/ckeditor/_source/plugins/toolbar/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/uicolor/dialogs/uicolor.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/uicolor/lang/en.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/uicolor/lang/he.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/uicolor/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/uicolor/uicolor.gif create mode 100755 htdocs/includes/ckeditor/_source/plugins/uicolor/yui/assets/hue_bg.png create mode 100755 htdocs/includes/ckeditor/_source/plugins/uicolor/yui/assets/hue_thumb.png create mode 100755 htdocs/includes/ckeditor/_source/plugins/uicolor/yui/assets/picker_mask.png create mode 100755 htdocs/includes/ckeditor/_source/plugins/uicolor/yui/assets/picker_thumb.png create mode 100755 htdocs/includes/ckeditor/_source/plugins/uicolor/yui/assets/yui.css create mode 100755 htdocs/includes/ckeditor/_source/plugins/uicolor/yui/yui.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/undo/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/wsc/dialogs/ciframe.html create mode 100755 htdocs/includes/ckeditor/_source/plugins/wsc/dialogs/tmpFrameset.html create mode 100755 htdocs/includes/ckeditor/_source/plugins/wsc/dialogs/wsc.css create mode 100755 htdocs/includes/ckeditor/_source/plugins/wsc/dialogs/wsc.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/wsc/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/wysiwygarea/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/plugins/xml/plugin.js create mode 100755 htdocs/includes/ckeditor/_source/skins/kama/dialog.css create mode 100755 htdocs/includes/ckeditor/_source/skins/kama/editor.css create mode 100755 htdocs/includes/ckeditor/_source/skins/kama/elementspath.css create mode 100755 htdocs/includes/ckeditor/_source/skins/kama/icons.css create mode 100755 htdocs/includes/ckeditor/_source/skins/kama/icons.png create mode 100755 htdocs/includes/ckeditor/_source/skins/kama/icons_rtl.png create mode 100755 htdocs/includes/ckeditor/_source/skins/kama/images/dialog_sides.gif create mode 100755 htdocs/includes/ckeditor/_source/skins/kama/images/dialog_sides.png create mode 100755 htdocs/includes/ckeditor/_source/skins/kama/images/dialog_sides_rtl.png create mode 100755 htdocs/includes/ckeditor/_source/skins/kama/images/mini.gif create mode 100755 htdocs/includes/ckeditor/_source/skins/kama/images/noimage.png create mode 100755 htdocs/includes/ckeditor/_source/skins/kama/images/sprites.png create mode 100755 htdocs/includes/ckeditor/_source/skins/kama/images/sprites_ie6.png create mode 100755 htdocs/includes/ckeditor/_source/skins/kama/images/toolbar_start.gif create mode 100755 htdocs/includes/ckeditor/_source/skins/kama/mainui.css create mode 100755 htdocs/includes/ckeditor/_source/skins/kama/menu.css create mode 100755 htdocs/includes/ckeditor/_source/skins/kama/panel.css create mode 100755 htdocs/includes/ckeditor/_source/skins/kama/presets.css create mode 100755 htdocs/includes/ckeditor/_source/skins/kama/reset.css create mode 100755 htdocs/includes/ckeditor/_source/skins/kama/richcombo.css create mode 100755 htdocs/includes/ckeditor/_source/skins/kama/skin.js create mode 100755 htdocs/includes/ckeditor/_source/skins/kama/templates.css create mode 100755 htdocs/includes/ckeditor/_source/skins/kama/toolbar.css create mode 100755 htdocs/includes/ckeditor/_source/skins/office2003/dialog.css create mode 100755 htdocs/includes/ckeditor/_source/skins/office2003/editor.css create mode 100755 htdocs/includes/ckeditor/_source/skins/office2003/elementspath.css create mode 100755 htdocs/includes/ckeditor/_source/skins/office2003/icons.css create mode 100755 htdocs/includes/ckeditor/_source/skins/office2003/icons.png create mode 100755 htdocs/includes/ckeditor/_source/skins/office2003/icons_rtl.png create mode 100755 htdocs/includes/ckeditor/_source/skins/office2003/images/dialog_sides.gif create mode 100755 htdocs/includes/ckeditor/_source/skins/office2003/images/dialog_sides.png create mode 100755 htdocs/includes/ckeditor/_source/skins/office2003/images/dialog_sides_rtl.png create mode 100755 htdocs/includes/ckeditor/_source/skins/office2003/images/mini.gif create mode 100755 htdocs/includes/ckeditor/_source/skins/office2003/images/noimage.png create mode 100755 htdocs/includes/ckeditor/_source/skins/office2003/images/sprites.png create mode 100755 htdocs/includes/ckeditor/_source/skins/office2003/images/sprites_ie6.png create mode 100755 htdocs/includes/ckeditor/_source/skins/office2003/mainui.css create mode 100755 htdocs/includes/ckeditor/_source/skins/office2003/menu.css create mode 100755 htdocs/includes/ckeditor/_source/skins/office2003/panel.css create mode 100755 htdocs/includes/ckeditor/_source/skins/office2003/presets.css create mode 100755 htdocs/includes/ckeditor/_source/skins/office2003/reset.css create mode 100755 htdocs/includes/ckeditor/_source/skins/office2003/richcombo.css create mode 100755 htdocs/includes/ckeditor/_source/skins/office2003/skin.js create mode 100755 htdocs/includes/ckeditor/_source/skins/office2003/templates.css create mode 100755 htdocs/includes/ckeditor/_source/skins/office2003/toolbar.css create mode 100755 htdocs/includes/ckeditor/_source/skins/v2/dialog.css create mode 100755 htdocs/includes/ckeditor/_source/skins/v2/editor.css create mode 100755 htdocs/includes/ckeditor/_source/skins/v2/elementspath.css create mode 100755 htdocs/includes/ckeditor/_source/skins/v2/icons.css create mode 100755 htdocs/includes/ckeditor/_source/skins/v2/icons.png create mode 100755 htdocs/includes/ckeditor/_source/skins/v2/icons_rtl.png create mode 100755 htdocs/includes/ckeditor/_source/skins/v2/images/dialog_sides.gif create mode 100755 htdocs/includes/ckeditor/_source/skins/v2/images/dialog_sides.png create mode 100755 htdocs/includes/ckeditor/_source/skins/v2/images/dialog_sides_rtl.png create mode 100755 htdocs/includes/ckeditor/_source/skins/v2/images/mini.gif create mode 100755 htdocs/includes/ckeditor/_source/skins/v2/images/noimage.png create mode 100755 htdocs/includes/ckeditor/_source/skins/v2/images/sprites.png create mode 100755 htdocs/includes/ckeditor/_source/skins/v2/images/sprites_ie6.png create mode 100755 htdocs/includes/ckeditor/_source/skins/v2/images/toolbar_start.gif create mode 100755 htdocs/includes/ckeditor/_source/skins/v2/mainui.css create mode 100755 htdocs/includes/ckeditor/_source/skins/v2/menu.css create mode 100755 htdocs/includes/ckeditor/_source/skins/v2/panel.css create mode 100755 htdocs/includes/ckeditor/_source/skins/v2/presets.css create mode 100755 htdocs/includes/ckeditor/_source/skins/v2/reset.css create mode 100755 htdocs/includes/ckeditor/_source/skins/v2/richcombo.css create mode 100755 htdocs/includes/ckeditor/_source/skins/v2/skin.js create mode 100755 htdocs/includes/ckeditor/_source/skins/v2/templates.css create mode 100755 htdocs/includes/ckeditor/_source/skins/v2/toolbar.css create mode 100755 htdocs/includes/ckeditor/_source/themes/default/theme.js delete mode 100644 htdocs/includes/ckeditor/ckeditor.jgz create mode 100755 htdocs/includes/ckeditor/lang/ka.js create mode 100755 htdocs/includes/ckeditor/plugins/bbcode/plugin.js create mode 100755 htdocs/includes/ckeditor/plugins/devtools/lang/en.js create mode 100755 htdocs/includes/ckeditor/plugins/devtools/plugin.js create mode 100755 htdocs/includes/ckeditor/plugins/docprops/dialogs/docprops.js create mode 100755 htdocs/includes/ckeditor/plugins/docprops/plugin.js create mode 100755 htdocs/includes/ckeditor/plugins/stylesheetparser/plugin.js diff --git a/htdocs/includes/ckeditor/.htaccess b/htdocs/includes/ckeditor/.htaccess new file mode 100755 index 00000000000..94d69cddcf7 --- /dev/null +++ b/htdocs/includes/ckeditor/.htaccess @@ -0,0 +1,24 @@ +# +# Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved. +# For licensing, see LICENSE.html or http://ckeditor.com/license +# + +# +# On some specific Linux installations you could face problems with Firefox. +# It could give you errors when loading the editor saying that some illegal +# characters were found (three strange chars in the beginning of the file). +# This could happen if you map the .js or .css files to PHP, for example. +# +# Those characters are the Byte Order Mask (BOM) of the Unicode encoded files. +# All FCKeditor files are Unicode encoded. +# + +AddType application/x-javascript .js +AddType text/css .css + +# +# If PHP is mapped to handle XML files, you could have some issues. The +# following will disable it. +# + +AddType text/xml .xml diff --git a/htdocs/includes/ckeditor/CHANGES.html b/htdocs/includes/ckeditor/CHANGES.html index 900c1694b53..9cb20e69c63 100644 --- a/htdocs/includes/ckeditor/CHANGES.html +++ b/htdocs/includes/ckeditor/CHANGES.html @@ -34,6 +34,134 @@ For licensing, see LICENSE.html or http://ckeditor.com/license

CKEditor Changelog

+

+ CKEditor 3.6.1

+

+ New features:

+
    +
  • #4556 : Initial support for HTML5 elements.
  • +
  • #6492 : The Find/Replace dialog window will now be populated with text selected in the editor.
  • +
  • #7323 : New align property in dialog window UI elements for field alignment.
  • +
  • #6462 : A wider range of CSS length units (like pt and percentage) are now supported in related dialog window fields.
  • +
  • #7911 : New Remove Anchor option is now available in the context menu.
  • +
  • #7387 : Allow styleDefinition to be applied to a set of elements.
  • +
  • #4345 : A new langLoaded event added to CKEDITOR.editor in order to make it possible to perform "by code" language updates.
  • +
  • #7959 : The cursor will now blink in the first cell after a table is inserted.
  • +
  • #7885 : New editor::removeMenuItem API for removing plugin context menu items introduced.
  • +
  • #7991 : Introduce the controlStyle and inputStyle definitions to allow fine-grained controlling of dialog window element styles.
  • +
+

+ Fixed issues:

+
    +
  • #7914 : ATTENTION! The signature for the setReadOnly() function has been changed, reversing the meaning of the parameter to be passed to it. Please make sure to update your code when upgrading.
  • +
  • #7657 : Wrong margin mirroring when creating a list from RTL paragraphs.
  • +
  • #7620 : A glitch in list pasting from Microsoft Word caused by broken child references when filtering.
  • +
  • #7811 : [IE] Deleting table row throws a JavaScript error.
  • +
  • #6962 : Changed the CKEDITOR.CTRL, CKEDITOR.SHIFT and CKEDITOR.ALT constant values to avoid collision with any possible Unicode character.
  • +
  • #6263 : Some table cell context menu options may be incorrectly disabled.
  • +
  • #6247 : Focus is not restored properly after a drop-down menu is closed.
  • +
  • #7334 : [IE7] Indentation style does not apply to RTL lists.
  • +
  • #6845 : Spaces inside the URL field in the Link dialog window will now be removed.
  • +
  • #7840 : [IE] Opening the Table Properties dialog window via the context menu causes a JavaScript error.
  • +
  • #7733 : Flash movies inserted with the Flash Properties dialog window are not displaying properly when injected into the page.
  • +
  • #7837 : [IE<8] Inserting a page break results in an error.
  • +
  • #7804 : The HTML5 wbr tag is now recognized by the editor.
  • +
  • #7867 : The file browser for the background image in the Document Properties plugin dialog window does not work.
  • +
  • #7130 : The column resizer gripping area is invading adjacent table cells.
  • +
  • #7844 : [FF] Calling setData() on a hidden editor caused editor not to display.
  • +
  • #7860 : The BBCode plugin was stripping BBCode tags that were not implemented in the plugin, but from now on they will be handled as simple text.
  • +
  • #7321 : [IE6] Contents inside the RTL fields in dialog windows are overflowing.
  • +
  • #7323 : [IE Quirks] Some fields are not centered in the dialog window.
  • +
  • #5955 : Editor accessibility issue with JAWS when a drop-down menu is placed as the first item in the toolbar.
  • +
  • #6671 : [FF] Selection of an item from the Styles drop-down list is not refreshed after the style is removed.
  • +
  • #7879 : The Style and Height/Width fields of the Table Properties dialog window are not synchronized.
  • +
  • #7581 : [IE] The Enter key pressed at the end of a list item containing the start attribute crashes the browser.
  • +
  • #7266 : Dialog window fields that did not pass validation are now ARIA-compatible with aria-invalid.
  • +
  • #7742 : [WebKit] Indentation, alignment, and language direction are not applied on an empty document without the editor being in focus.
  • +
  • #7801 : [Opera] Pasted paragraphs now split partially selected blocks.
  • +
  • #6663 : Table caption that contains rich text is corrupted after an edit done with the Table Properties dialog window.
  • +
  • #7893 : [WebKit, Opera, IE<8] It is impossible to link to anchors in the document.
  • +
  • #7637 : Cursor position might in some cases cause problems after inserting a page break.
  • +
  • #5314 : The aria-selected attribute is not removed when toolbar drop-down menu items are deselected.
  • +
  • #7749 : Small check introduced to avoid issues with custom data processors and the insertHtml function.
  • +
  • #7269 : [WebKit] Paste from Word is including the full file:// URL path for anchor links.
  • +
  • #7584 : Start number of the List dialog window now works with numbered list items.
  • +
  • #6975 : [IE6, IE7] A definition list crashes Internet Explorer on HTML output.
  • +
  • #7841 : Deleting a column with a cell deleted in one of the rows does not work.
  • +
  • #7944 : The Enter key should not split or create new paragraphs inside caption elements.
  • +
  • #7639 : [IE9] Browser might crash when an object is selected in the document.
  • +
  • #7847 : [IE8] Inserting an image with non-secure source in a HTTPS page breaks the dialog window.
  • +
  • #7953 : [IE] Text selection lost after the browser context menu is opened.
  • +
  • #5239 : Inconsistent focus behavior after closing a toolbar drop-down menu.
  • +
  • #6470 : The Start attribute of a Numbered List is rendered incorrectly if the field is left empty.
  • +
  • #7324 : [IE6 Quirks] Context menus are not displayed correctly.
  • +
  • #7566 : BiDi: Increasing indentation of a list item changes the language direction.
  • +
  • #7839 : [IE] Pasting multi-level numbered lists from Microsoft Word does not work properly.
  • +
  • #188 : [IE] Object selection was making the toolbar inactive in some situations.
  • +
  • Updated the following language files:
  • +
+

+ CKEditor 3.6

+

+ New features:

+
    +
  • #7044 : New BBCode sample plugin that makes the editor output (one dialect of) BBCode format.
  • +
  • #5647 : Accessibility enhancements to the structure of the toolbar.
  • +
  • #5647 : The Kama skin now presents separators for the toolbar items, making it easier to group buttons and have a cleaner layout.
  • +
  • #5647 : Usability enhancements to keyboard navigation on the toolbar. The Tab key is now used to jump between toolbar groups, while the Arrow keys can be used to cycle within the group. The new toolbarGroupCycling setting can be used to change the Arrow keys behavior.
  • +
  • #1376 : It is now possible to put the editor in the "read-only" state, so that the users would not be able to introduce changes to the contents. Check out the new CKEDITOR.editor::setReadOnly method, the CKEDITOR.editor::readOnly property, the CKEDITOR.editor::readOnly event, and the readOnly setting.
  • +
  • #3582 : New presentation of anchor elements in the WYSIWYG mode.
  • +
  • #6737 : The Format drop-down list will now display the preview of its contents exactly as defined in their style configurations.
  • +
  • #6654 : A new autoParagraph configuration setting is added to disable the auto paragraphing feature.
  • +
  • #901 : New Stylesheet Parser (stylesheetparser) plugin that fills the Styles drop-down list based on the CSS classes available for the content. Check the new sample to learn how to use it.
  • +
  • #2988 : New Document Properties (docprops) plugin that sets the metadata of the page in the Full Page mode.
  • +
  • #7240 : New Developer Tools (devtools) plugin that shows information about dialog window UI elements to allow for easier customization.
  • +
  • #6841 : Pressing the Enter key at the end of a pre-formatted block will now exit from it.
  • +
  • #6850 : The About CKEditor dialog window now contains a link to CKEditor User's Guide.
  • +
  • #5745 : Extra configuration options for the iframeDialog can now be passed.
  • +
  • #6589 : The onDialogEvent function will now be used automatically in the iframeDialog contents if no callback is used on creation.
  • +
  • #7757 : Georgian localization added.
  • +
+

+ Fixed issues:

+
    +
  • #6774 : Internal styles are not included in the contents.css sample.
  • +
  • #6521 : Added sample for the TableResize plugin.
  • +
  • #6664 : Page break is sometimes merged into block-level elements.
  • +
  • #7594 : Toolbar keyboard navigation is not possible after recreating the editor.
  • +
  • #6657 : Allow to style the entire dialog window field when the input element is disabled.
  • +
  • Updated the following language files:
      +
    • Hebrew;
    • +
    • Polish;
    • +
  • +
+

+ CKEditor 3.5.4

+

+ Fixed issues:

+
    +
  • Added protection against XSS attacks in PHP samples when displaying element names.
  • +
  • #7347 : The Enter key will no longer be caught by the dialog window covering the editor.
  • +
  • #6718 : Paste from Word command overrides the Force Paste as Plain Text configuration.
  • +
  • #6629 : Padding body is no longer needed when the last block is pre-formatted.
  • +
  • #4844 : [IE] Dialog windows fail to load if there are too many editor instances on the page.
  • +
  • #5788 : HTML parser trims empty spaces following <br> elements.
  • +
  • #7513 : Invalid markup could cause the editor to hang.
  • +
  • #6109 : Paste and Paste as Plain Text dialog windows now use the standard commitContent and setupContent methods.
  • +
  • #7588 : The editor code now has a protection system to avoid issues when including ckeditor.js more than once in the page.
  • +
  • #7322 : Text font plugin now recognizes font family names that contain quotes.
  • +
  • #7540 : Paste from Word introduces wrong spaces.
  • +
  • #7697 : Successive calls of the replace() method did not work after SCAYT context menu initialization.
  • +
  • Updated the following language files:
  • +

CKEditor 3.5.3

diff --git a/htdocs/includes/ckeditor/LICENSE.html b/htdocs/includes/ckeditor/LICENSE.html index b049b25144b..90222429b80 100644 --- a/htdocs/includes/ckeditor/LICENSE.html +++ b/htdocs/includes/ckeditor/LICENSE.html @@ -28,9 +28,7 @@ You are not required to, but if you want to explicitly declare the license you have chosen to be bound to when using, reproducing, modifying and distributing this software, just include a text file titled "legal.txt" in your version of this software, indicating your -license choice. In any case, your choice will not restrict any -recipient of your version of this software to use, reproduce, modify -and distribute this software under any of the above licenses. +license choice. Sources of Intellectual Property Included in CKEditor ===================================================== @@ -40,11 +38,6 @@ CKSource engineers and consists of CKSource-owned intellectual property. In some specific instances, CKEditor will incorporate work done by developers outside of CKSource with their express permission. -YUI Test: At _source/tests/yuitest.js can be found part of the source -code of YUI, which is licensed under the terms of the BSD License -(http://developer.yahoo.com/yui/license.txt). YUI is Copyright (C) -2008, Yahoo! Inc. - Trademarks ========== diff --git a/htdocs/includes/ckeditor/_samples/adobeair/application.xml b/htdocs/includes/ckeditor/_samples/adobeair/application.xml new file mode 100755 index 00000000000..a6635a6fb96 --- /dev/null +++ b/htdocs/includes/ckeditor/_samples/adobeair/application.xml @@ -0,0 +1,32 @@ + + + + com.ckeditor.air.sample + CKEditor - Adobe AIR Sample Application + 1.0 + CKEditor AIR Samples + This is a sample AIR application of CKEditor. + Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved. + + _samples/adobeair/sample.html + CKEditor - Adobe AIR Sample + standard + false + true + true + true + true + 100 + 80 + 950 + 700 + 900 600 + + CKEditor/Sample AIR Application + CKEditor/Sample AIR Application + false + false + diff --git a/htdocs/includes/ckeditor/_samples/adobeair/run.bat b/htdocs/includes/ckeditor/_samples/adobeair/run.bat new file mode 100755 index 00000000000..2e1518ee20b --- /dev/null +++ b/htdocs/includes/ckeditor/_samples/adobeair/run.bat @@ -0,0 +1,9 @@ +@ECHO OFF +:: +:: Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved. +:: For licensing, see LICENSE.html or http://ckeditor.com/license +:: +:: Use this file to quickly run the sample in a Windows environment. +:: + +adl application.xml ../../ diff --git a/htdocs/includes/ckeditor/_samples/adobeair/run.sh b/htdocs/includes/ckeditor/_samples/adobeair/run.sh new file mode 100755 index 00000000000..be08d9eed6b --- /dev/null +++ b/htdocs/includes/ckeditor/_samples/adobeair/run.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +# Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved. +# For licensing, see LICENSE.html or http://ckeditor.com/license + +# Use this file to quickly run the sample under Linux. + +adl application.xml ../../ diff --git a/htdocs/includes/ckeditor/_samples/adobeair/sample.html b/htdocs/includes/ckeditor/_samples/adobeair/sample.html new file mode 100755 index 00000000000..493f4bf0942 --- /dev/null +++ b/htdocs/includes/ckeditor/_samples/adobeair/sample.html @@ -0,0 +1,45 @@ + + + + + Replace Textarea by Code - CKEditor Sample + + + + + +

+ CKEditor - Adobe AIR Sample +

+

+ This is a sample HTML/JavaScript Adobe AIR application with CKEditor with default features. +

+

+ + +

+ + + diff --git a/htdocs/includes/ckeditor/_samples/ajax.html b/htdocs/includes/ckeditor/_samples/ajax.html new file mode 100755 index 00000000000..c6fe8924b65 --- /dev/null +++ b/htdocs/includes/ckeditor/_samples/ajax.html @@ -0,0 +1,98 @@ + + + + + Ajax — CKEditor Sample + + + + + + + +

+ CKEditor Sample — Create and Destroy Editor Instances for Ajax Applications +

+
+

+ This sample shows how to create and destroy CKEditor instances on the fly. After the removal of CKEditor the content created inside the editing + area will be displayed in a <div> element. +

+

+ For details of how to create this setup check the source code of this sample page + for JavaScript code responsible for the creation and destruction of a CKEditor instance. +

+
+ + +
+ +
+

Click the buttons to create and remove a CKEditor instance.

+

+ + +

+ +
+
+ + + + diff --git a/htdocs/includes/ckeditor/_samples/api.html b/htdocs/includes/ckeditor/_samples/api.html new file mode 100755 index 00000000000..a3f20359c0e --- /dev/null +++ b/htdocs/includes/ckeditor/_samples/api.html @@ -0,0 +1,192 @@ + + + + + API Usage — CKEditor Sample + + + + + + + + +

+ CKEditor Sample — Using CKEditor JavaScript API +

+
+

+ This sample shows how to use the + CKEditor JavaScript API + to interact with the editor at runtime. +

+

+ For details on how to create this setup check the source code of this sample page. +

+
+ + +
+ +
+
+ + + + +
+
+ +
+ + + diff --git a/htdocs/includes/ckeditor/_samples/api_dialog.html b/htdocs/includes/ckeditor/_samples/api_dialog.html new file mode 100755 index 00000000000..9882d6a406c --- /dev/null +++ b/htdocs/includes/ckeditor/_samples/api_dialog.html @@ -0,0 +1,198 @@ + + + + + Using API to Customize Dialog Windows — CKEditor Sample + + + + + + + + + +

+ CKEditor Sample — Using CKEditor Dialog API +

+
+

+ This sample shows how to use the + CKEditor Dialog API + to customize CKEditor dialog windows without changing the original editor code. + The following customizations are being done in the example below: +

+
    +
  1. Adding dialog window tabs – "My Tab" in the "Link" dialog window.
  2. +
  3. Removing a dialog window tab – "Target" tab from the "Link" dialog window.
  4. +
  5. Adding dialog window fields – "My Custom Field" in the "Link" dialog window.
  6. +
  7. Removing dialog window fields – "Link Type" and "Browse Server" in the "Link" + dialog window.
  8. +
  9. Setting default values for dialog window fields – "URL" field in the + "Link" dialog window.
  10. +
  11. Creating a custom dialog window – "My Dialog" dialog window opened with the "My Dialog" toolbar button.
  12. +
+

+For details on how to create this setup check the source code of this sample page. +

+
+ + + +
+ +
+ + + + + + diff --git a/htdocs/includes/ckeditor/_samples/api_dialog/my_dialog.js b/htdocs/includes/ckeditor/_samples/api_dialog/my_dialog.js new file mode 100755 index 00000000000..638d4b0059a --- /dev/null +++ b/htdocs/includes/ckeditor/_samples/api_dialog/my_dialog.js @@ -0,0 +1,28 @@ +/* +Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +CKEDITOR.dialog.add( 'myDialog', function( editor ) +{ + return { + title : 'My Dialog', + minWidth : 400, + minHeight : 200, + contents : [ + { + id : 'tab1', + label : 'First Tab', + title : 'First Tab', + elements : + [ + { + id : 'input1', + type : 'text', + label : 'Input 1' + } + ] + } + ] + }; +} ); diff --git a/htdocs/includes/ckeditor/_samples/asp/advanced.asp b/htdocs/includes/ckeditor/_samples/asp/advanced.asp new file mode 100755 index 00000000000..b101c2ccc18 --- /dev/null +++ b/htdocs/includes/ckeditor/_samples/asp/advanced.asp @@ -0,0 +1,105 @@ +<%@ codepage="65001" language="VBScript" %> +<% Option Explicit %> + +<% + + ' You must set "Enable Parent Paths" on your web site + ' in order for the above relative include to work. + ' Or you can use #INCLUDE VIRTUAL="/full path/ckeditor.asp" + +%> + + + + + Sample - CKEditor + + + + +

+ CKEditor Sample +

+ +
+ +
+ +
+ Output +
+

+
+

+<% + ' Create class instance. + dim editor, initialValue, code, textareaAttributes + set editor = New CKEditor + + ' Do not print the code directly to the browser, return it instead + editor.returnOutput = true + + ' Path to CKEditor directory, ideally instead of relative dir, use an absolute path: + ' editor.basePath = "/ckeditor/" + ' If not set, CKEditor will default to /ckeditor/ + editor.basePath = "../../" + + ' Set global configuration (will be used by all instances of CKEditor). + editor.config("width") = 600 + + ' Change default textarea attributes + set textareaAttributes = CreateObject("Scripting.Dictionary") + textareaAttributes.Add "rows", 10 + textareaAttributes.Add "cols", 80 + Set editor.textareaAttributes = textareaAttributes + + ' The initial value to be displayed in the editor. + initialValue = "

This is some sample text. You are using CKEditor.

" + + ' Create first instance. + code = editor.editor("editor1", initialValue) + + response.write code +%> +

+
+

+<% +' Configuration that will be used only by the second editor. + +editor.instanceConfig("toolbar") = Array( _ + Array( "Source", "-", "Bold", "Italic", "Underline", "Strike" ), _ + Array( "Image", "Link", "Unlink", "Anchor" ) _ + ) + +editor.instanceConfig("skin") = "v2" + +' Create second instance. +response.write editor.editor("editor2", initialValue) +%> +

+ +

+
+
+ + + diff --git a/htdocs/includes/ckeditor/_samples/asp/events.asp b/htdocs/includes/ckeditor/_samples/asp/events.asp new file mode 100755 index 00000000000..c24e5e1fe83 --- /dev/null +++ b/htdocs/includes/ckeditor/_samples/asp/events.asp @@ -0,0 +1,136 @@ +<%@ codepage="65001" language="VBScript" %> +<% Option Explicit %> + +<% + + ' You must set "Enable Parent Paths" on your web site + ' in order for the above relative include to work. + ' Or you can use #INCLUDE VIRTUAL="/full path/ckeditor.asp" + +%> + + + + + Sample - CKEditor + + + + +

+ CKEditor Sample +

+ +
+ +
+ +
+ Output +
+

+
+

+<% + +'' + ' Adds global event, will hide "Target" tab in Link dialog in all instances. + ' +function CKEditorHideLinkTargetTab(editor) + dim functionCode + functionCode = "function (ev) {" & vbcrlf & _ + "// Take the dialog name and its definition from the event data" & vbcrlf & _ + "var dialogName = ev.data.name;" & vbcrlf & _ + "var dialogDefinition = ev.data.definition;" & vbcrlf & _ + "" & vbcrlf & _ + "// Check if the definition is from the Link dialog." & vbcrlf & _ + "if ( dialogName == 'link' )" & vbcrlf & _ + " dialogDefinition.removeContents('target')" & vbcrlf & _ + "}" & vbcrlf + + editor.addGlobalEventHandler "dialogDefinition", functionCode +end function + +'' + ' Adds global event, will notify about opened dialog. + ' +function CKEditorNotifyAboutOpenedDialog(editor) + dim functionCode + functionCode = "function (evt) {" & vbcrlf & _ + "alert('Loading dialog: ' + evt.data.name);" & vbcrlf & _ + "}" + + editor.addGlobalEventHandler "dialogDefinition", functionCode +end function + + +dim editor, initialValue + +' Create class instance. +set editor = new CKEditor + +' Set configuration option for all editors. +editor.config("width") = 750 + +' Path to CKEditor directory, ideally instead of relative dir, use an absolute path: +' editor.basePath = "/ckeditor/" +' If not set, CKEditor will default to /ckeditor/ +editor.basePath = "../../" + +' The initial value to be displayed in the editor. +initialValue = "

This is some sample text. You are using CKEditor.

" + +' Event that will be handled only by the first editor. +editor.addEventHandler "instanceReady", "function (evt) { alert('Loaded editor: ' + evt.editor.name );}" + +' Create first instance. +editor.editor "editor1", initialValue + +' Clear event handlers, instances that will be created later will not have +' the 'instanceReady' listener defined a couple of lines above. +editor.clearEventHandlers empty +%> +

+
+

+<% +' Configuration that will be used only by the second editor. +editor.instanceConfig("width") = 600 +editor.instanceConfig("toolbar") = "Basic" + +' Add some global event handlers (for all editors). +CKEditorHideLinkTargetTab(editor) +CKEditorNotifyAboutOpenedDialog(editor) + +' Event that will be handled only by the second editor. +editor.addInstanceEventHandler "instanceReady", "function (evt) { alert('Loaded second editor: ' + evt.editor.name );}" + +' Create second instance. +editor.editor "editor2", initialValue +%> +

+ +

+
+
+ + + diff --git a/htdocs/includes/ckeditor/_samples/asp/index.html b/htdocs/includes/ckeditor/_samples/asp/index.html new file mode 100755 index 00000000000..76522bff5d6 --- /dev/null +++ b/htdocs/includes/ckeditor/_samples/asp/index.html @@ -0,0 +1,103 @@ + + + + + ASP integration Samples List — CKEditor + + + +

+ CKEditor Samples List for ASP — CKEditor Sample +

+

+ Overview +

+

The ckeditor.asp file provides a wrapper to ease the work of creating CKEditor instances from classic Asp.

+

To use it, you must first include it into your page: + + <!-- #INCLUDE file="../../ckeditor.asp" --> + + Of course, you should adjust the path to make it point to the correct location, and maybe use a full path (with virtual="" instead of file="") +

+

After that script is included, you can use it in different ways, based on the following pattern:

+ +
    +
  1. + Create an instance of the CKEditor class: +
    dim editor
    +set editor = New CKEditor
    +
  2. +
  3. + Set the path to the folder where CKEditor has been installed, by default it will use /ckeditor/ +
    editor.basePath = "../../"
    +
  4. +
  5. + Now use one of the three main methods to create the CKEditor instances: +
      +
    • + Replace textarea with id (or name) "editor1". +
      editor.replaceInstance "editor1"
      +
    • +
    • + Replace all textareas with CKEditor. +
      editor.replaceAll empty
      +
    • +
    • + Create a textarea element and attach CKEditor to it. +
      editor.editor "editor1", initialValue
      +
    • +
    +
  6. +
+

Before step 3 you can use a number of methods and properties to adjust the behavior of this class and the CKEditor instances +that will be created:

+
    +
  • returnOutput : if set to true, the functions won't dump the code with response.write, but instead they will return it so + you can do anything you want
  • +
  • basePath: location of the CKEditor scripts
  • +
  • initialized: if you set it to true, it means that you have already included the CKEditor.js file into the page and it + doesn't have to be generated again.
  • +
  • textareaAttributes: You can set here a Dictionary object with the attributes that you want to output in the call to the "editor" method.
  • + +
  • config: Allows to set config values for all the instances from now on.
  • +
  • instanceConfig: Allows to set config values just for the next instance.
  • + +
  • addEventHandler: Adds an event handler for all the instances from now on.
  • +
  • addInstanceEventHandler: Adds an event handler just for the next instance.
  • +
  • addGlobalEventHandler: Adds an event handler for the global CKEDITOR object.
  • + +
  • clearEventHandlers: Removes one or all the event handlers from all the instances from now on.
  • +
  • clearInstanceEventHandlers: Removes one or all the event handlers from the next instance.
  • +
  • clearGlobalEventHandlers: Removes one or all the event handlers from the global CKEDITOR object.
  • + +
+ +

+ Basic Samples +

+ +

+ Advanced Samples +

+ + + + diff --git a/htdocs/includes/ckeditor/_samples/asp/replace.asp b/htdocs/includes/ckeditor/_samples/asp/replace.asp new file mode 100755 index 00000000000..5aafbb260f2 --- /dev/null +++ b/htdocs/includes/ckeditor/_samples/asp/replace.asp @@ -0,0 +1,72 @@ +<%@ codepage="65001" language="VBScript" %> +<% Option Explicit %> + +<% + + ' You must set "Enable Parent Paths" on your web site + ' in order for the above relative include to work. + ' Or you can use #INCLUDE VIRTUAL="/full path/ckeditor.asp" + +%> + + + + + Sample - CKEditor + + + + +

+ CKEditor Sample +

+ +
+ +
+ +
+ Output +
+

+
+ +

+

+ +

+
+
+ + <% + ' Create class instance. + dim editor + set editor = New CKEditor + ' Path to CKEditor directory, ideally instead of relative dir, use an absolute path: + ' editor.basePath = "/ckeditor/" + ' If not set, CKEditor will default to /ckeditor/ + editor.basePath = "../../" + ' Replace textarea with id (or name) "editor1". + editor.replaceInstance "editor1" + %> + + diff --git a/htdocs/includes/ckeditor/_samples/asp/replaceall.asp b/htdocs/includes/ckeditor/_samples/asp/replaceall.asp new file mode 100755 index 00000000000..1337f601bae --- /dev/null +++ b/htdocs/includes/ckeditor/_samples/asp/replaceall.asp @@ -0,0 +1,77 @@ +<%@ codepage="65001" language="VBScript" %> +<% Option Explicit %> + +<% + + ' You must set "Enable Parent Paths" on your web site + ' in order for the above relative include to work. + ' Or you can use #INCLUDE VIRTUAL="/full path/ckeditor.asp" + +%> + + + + + Sample - CKEditor + + + + +

+ CKEditor Sample +

+ +
+ +
+ +
+ Output +
+

+
+ +

+

+
+ +

+

+ +

+
+
+ + <% + ' Create class instance. + dim editor + set editor = New CKEditor + ' Path to CKEditor directory, ideally instead of relative dir, use an absolute path: + ' editor.basePath = "/ckeditor/" + ' If not set, CKEditor will default to /ckeditor/ + editor.basePath = "../../" + ' Replace all textareas with CKEditor. + editor.replaceAll empty + %> + + diff --git a/htdocs/includes/ckeditor/_samples/asp/sample_posteddata.asp b/htdocs/includes/ckeditor/_samples/asp/sample_posteddata.asp new file mode 100755 index 00000000000..81e6e49413b --- /dev/null +++ b/htdocs/includes/ckeditor/_samples/asp/sample_posteddata.asp @@ -0,0 +1,46 @@ +<%@ codepage="65001" language="VBScript" %> +<% Option Explicit %> + + + + + Sample - CKEditor + + + + +

+ CKEditor - Posted Data +

+ + + + + + + + + <% + Dim sForm + For Each sForm in Request.Form + %> + + + + + <% Next %> +
Field NameValue
<%=Server.HTMLEncode( sForm )%>
<%=Server.HTMLEncode( Request.Form(sForm) )%>
+ + + diff --git a/htdocs/includes/ckeditor/_samples/asp/standalone.asp b/htdocs/includes/ckeditor/_samples/asp/standalone.asp new file mode 100755 index 00000000000..0f453e9d92c --- /dev/null +++ b/htdocs/includes/ckeditor/_samples/asp/standalone.asp @@ -0,0 +1,72 @@ +<%@ codepage="65001" language="VBScript" %> +<% Option Explicit %> + +<% + + ' You must set "Enable Parent Paths" on your web site + ' in order for the above relative include to work. + ' Or you can use #INCLUDE VIRTUAL="/full path/ckeditor.asp" + +%> + + + + + Sample - CKEditor + + + + +

+ CKEditor Sample +

+ +
+ +
+ +
+ Output +
+

+ Editor 1: +

+

+ <% + dim initialValue, editor + ' The initial value to be displayed in the editor. + initialValue = "

This is some sample text.

" + ' Create class instance. + set editor = New CKEditor + ' Path to CKEditor directory, ideally instead of relative dir, use an absolute path: + ' editor.basePath = "/ckeditor/" + ' If not set, CKEditor will default to /ckeditor/ + editor.basePath = "../../" + ' Create textarea element and attach CKEditor to it. + editor.editor "editor1", initialValue + %> + +

+
+
+ + + diff --git a/htdocs/includes/ckeditor/_samples/assets/_posteddata.php b/htdocs/includes/ckeditor/_samples/assets/_posteddata.php new file mode 100755 index 00000000000..4d3e34ead20 --- /dev/null +++ b/htdocs/includes/ckeditor/_samples/assets/_posteddata.php @@ -0,0 +1,59 @@ + + + + + Sample — CKEditor + + + + +

+ CKEditor — Posted Data +

+ + + + + + + + + $value ) +{ + if ( get_magic_quotes_gpc() ) + $postedValue = htmlspecialchars( stripslashes( $value ) ) ; + else + $postedValue = htmlspecialchars( $value ) ; + +?> + + + + + +
Field NameValue
+ + + diff --git a/htdocs/includes/ckeditor/_samples/assets/output_for_flash.fla b/htdocs/includes/ckeditor/_samples/assets/output_for_flash.fla new file mode 100755 index 0000000000000000000000000000000000000000..27e68ccd1cb7192c8bda2418d198d90f1aff10a7 GIT binary patch literal 85504 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 literal 0 HcmV?d00001 diff --git a/htdocs/includes/ckeditor/_samples/assets/output_xhtml.css b/htdocs/includes/ckeditor/_samples/assets/output_xhtml.css new file mode 100755 index 00000000000..79ffb98d72d --- /dev/null +++ b/htdocs/includes/ckeditor/_samples/assets/output_xhtml.css @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.html or http://ckeditor.com/license + * + * Styles used by the XHTML 1.1 sample page (xhtml.html). + */ + +/** + * Basic definitions for the editing area. + */ +body +{ + font-family: Arial, Verdana, sans-serif; + font-size: 80%; + color: #000000; + background-color: #ffffff; + padding: 5px; + margin: 0px; +} + +/** + * Core styles. + */ + +.Bold +{ + font-weight: bold; +} + +.Italic +{ + font-style: italic; +} + +.Underline +{ + text-decoration: underline; +} + +.StrikeThrough +{ + text-decoration: line-through; +} + +.Subscript +{ + vertical-align: sub; + font-size: smaller; +} + +.Superscript +{ + vertical-align: super; + font-size: smaller; +} + +/** + * Font faces. + */ + +.FontComic +{ + font-family: 'Comic Sans MS'; +} + +.FontCourier +{ + font-family: 'Courier New'; +} + +.FontTimes +{ + font-family: 'Times New Roman'; +} + +/** + * Font sizes. + */ + +.FontSmaller +{ + font-size: smaller; +} + +.FontLarger +{ + font-size: larger; +} + +.FontSmall +{ + font-size: 8pt; +} + +.FontBig +{ + font-size: 14pt; +} + +.FontDouble +{ + font-size: 200%; +} + +/** + * Font colors. + */ +.FontColor1 +{ + color: #ff9900; +} + +.FontColor2 +{ + color: #0066cc; +} + +.FontColor3 +{ + color: #ff0000; +} + +.FontColor1BG +{ + background-color: #ff9900; +} + +.FontColor2BG +{ + background-color: #0066cc; +} + +.FontColor3BG +{ + background-color: #ff0000; +} + +/** + * Indentation. + */ + +.Indent1 +{ + margin-left: 40px; +} + +.Indent2 +{ + margin-left: 80px; +} + +.Indent3 +{ + margin-left: 120px; +} + +/** + * Alignment. + */ + +.JustifyLeft +{ + text-align: left; +} + +.JustifyRight +{ + text-align: right; +} + +.JustifyCenter +{ + text-align: center; +} + +.JustifyFull +{ + text-align: justify; +} + +/** + * Other. + */ + +code +{ + font-family: courier, monospace; + background-color: #eeeeee; + padding-left: 1px; + padding-right: 1px; + border: #c0c0c0 1px solid; +} + +kbd +{ + padding: 0px 1px 0px 1px; + border-width: 1px 2px 2px 1px; + border-style: solid; +} + +blockquote +{ + color: #808080; +} diff --git a/htdocs/includes/ckeditor/_samples/assets/parsesample.css b/htdocs/includes/ckeditor/_samples/assets/parsesample.css new file mode 100755 index 00000000000..ce545eeccc3 --- /dev/null +++ b/htdocs/includes/ckeditor/_samples/assets/parsesample.css @@ -0,0 +1,70 @@ +body +{ + font-family: Arial, Verdana, sans-serif; + font-size: 12px; + color: #222; + background-color: #fff; +} + +/* preserved spaces for rtl list item bullets. (#6249)*/ +ol,ul,dl +{ + padding-right:40px; +} + +h1,h2,h3,h4 +{ + font-family: Georgia, Times, serif; +} + +h1.lightBlue +{ + color: #00A6C7; + font-size: 1.8em; + font-weight:normal; +} + +h3.green +{ + color: #739E39; + font-weight:normal; +} + +span.markYellow { background-color: yellow; } +span.markGreen { background-color: lime; } + +img.left +{ + padding: 5px; + margin-right: 5px; + float:left; + border:2px solid #DDD; +} + +img.right +{ + padding: 5px; + margin-right: 5px; + float:right; + border:2px solid #DDD; +} + +a.green +{ + color:#739E39; +} + +table.grey +{ + background-color : #F5F5F5; +} + +table.grey th +{ + background-color : #DDD; +} + +ul.square +{ + list-style-type : square; +} diff --git a/htdocs/includes/ckeditor/_samples/assets/swfobject.js b/htdocs/includes/ckeditor/_samples/assets/swfobject.js new file mode 100755 index 00000000000..ef6d3b75615 --- /dev/null +++ b/htdocs/includes/ckeditor/_samples/assets/swfobject.js @@ -0,0 +1,18 @@ +/** + * SWFObject v1.5: Flash Player detection and embed - http://blog.deconcept.com/swfobject/ + * + * SWFObject is (c) 2007 Geoff Stearns and is released under the MIT License: + * http://www.opensource.org/licenses/mit-license.php + * + */ +/* +Copyright (c) 2007 Geoff Stearns + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +/*jsl:ignoreall*/ +if(typeof deconcept=="undefined"){var deconcept=new Object();}if(typeof deconcept.util=="undefined"){deconcept.util=new Object();}if(typeof deconcept.SWFObjectUtil=="undefined"){deconcept.SWFObjectUtil=new Object();}deconcept.SWFObject=function(_1,id,w,h,_5,c,_7,_8,_9,_a){if(!document.getElementById){return;}this.DETECT_KEY=_a?_a:"detectflash";this.skipDetect=deconcept.util.getRequestParameter(this.DETECT_KEY);this.params=new Object();this.variables=new Object();this.attributes=new Array();if(_1){this.setAttribute("swf",_1);}if(id){this.setAttribute("id",id);}if(w){this.setAttribute("width",w);}if(h){this.setAttribute("height",h);}if(_5){this.setAttribute("version",new deconcept.PlayerVersion(_5.toString().split(".")));}this.installedVer=deconcept.SWFObjectUtil.getPlayerVersion();if(!window.opera&&document.all&&this.installedVer.major>7){deconcept.SWFObject.doPrepUnload=true;}if(c){this.addParam("bgcolor",c);}var q=_7?_7:"high";this.addParam("quality",q);this.setAttribute("useExpressInstall",false);this.setAttribute("doExpressInstall",false);var _c=(_8)?_8:window.location;this.setAttribute("xiRedirectUrl",_c);this.setAttribute("redirectUrl","");if(_9){this.setAttribute("redirectUrl",_9);}};deconcept.SWFObject.prototype={useExpressInstall:function(_d){this.xiSWFPath=!_d?"expressinstall.swf":_d;this.setAttribute("useExpressInstall",true);},setAttribute:function(_e,_f){this.attributes[_e]=_f;},getAttribute:function(_10){return this.attributes[_10];},addParam:function(_11,_12){this.params[_11]=_12;},getParams:function(){return this.params;},addVariable:function(_13,_14){this.variables[_13]=_14;},getVariable:function(_15){return this.variables[_15];},getVariables:function(){return this.variables;},getVariablePairs:function(){var _16=new Array();var key;var _18=this.getVariables();for(key in _18){_16[_16.length]=key+"="+_18[key];}return _16;},getSWFHTML:function(){var _19="";if(navigator.plugins&&navigator.mimeTypes&&navigator.mimeTypes.length){if(this.getAttribute("doExpressInstall")){this.addVariable("MMplayerType","PlugIn");this.setAttribute("swf",this.xiSWFPath);}_19="0){_19+="flashvars=\""+_1c+"\"";}_19+="/>";}else{if(this.getAttribute("doExpressInstall")){this.addVariable("MMplayerType","ActiveX");this.setAttribute("swf",this.xiSWFPath);}_19="";_19+="";var _1d=this.getParams();for(var key in _1d){_19+="";}var _1f=this.getVariablePairs().join("&");if(_1f.length>0){_19+="";}_19+="";}return _19;},write:function(_20){if(this.getAttribute("useExpressInstall")){var _21=new deconcept.PlayerVersion([6,0,65]);if(this.installedVer.versionIsValid(_21)&&!this.installedVer.versionIsValid(this.getAttribute("version"))){this.setAttribute("doExpressInstall",true);this.addVariable("MMredirectURL",escape(this.getAttribute("xiRedirectUrl")));document.title=document.title.slice(0,47)+" - Flash Player Installation";this.addVariable("MMdoctitle",document.title);}}if(this.skipDetect||this.getAttribute("doExpressInstall")||this.installedVer.versionIsValid(this.getAttribute("version"))){var n=(typeof _20=="string")?document.getElementById(_20):_20;n.innerHTML=this.getSWFHTML();return true;}else{if(this.getAttribute("redirectUrl")!=""){document.location.replace(this.getAttribute("redirectUrl"));}}return false;}};deconcept.SWFObjectUtil.getPlayerVersion=function(){var _23=new deconcept.PlayerVersion([0,0,0]);if(navigator.plugins&&navigator.mimeTypes.length){var x=navigator.plugins["Shockwave Flash"];if(x&&x.description){_23=new deconcept.PlayerVersion(x.description.replace(/([a-zA-Z]|\s)+/,"").replace(/(\s+r|\s+b[0-9]+)/,".").split("."));}}else{if(navigator.userAgent&&navigator.userAgent.indexOf("Windows CE")>=0){var axo=1;var _26=3;while(axo){try{_26++;axo=new ActiveXObject("ShockwaveFlash.ShockwaveFlash."+_26);_23=new deconcept.PlayerVersion([_26,0,0]);}catch(e){axo=null;}}}else{try{var axo=new ActiveXObject("ShockwaveFlash.ShockwaveFlash.7");}catch(e){try{var axo=new ActiveXObject("ShockwaveFlash.ShockwaveFlash.6");_23=new deconcept.PlayerVersion([6,0,21]);axo.AllowScriptAccess="always";}catch(e){if(_23.major==6){return _23;}}try{axo=new ActiveXObject("ShockwaveFlash.ShockwaveFlash");}catch(e){}}if(axo!=null){_23=new deconcept.PlayerVersion(axo.GetVariable("$version").split(" ")[1].split(","));}}}return _23;};deconcept.PlayerVersion=function(_29){this.major=_29[0]!=null?parseInt(_29[0]):0;this.minor=_29[1]!=null?parseInt(_29[1]):0;this.rev=_29[2]!=null?parseInt(_29[2]):0;};deconcept.PlayerVersion.prototype.versionIsValid=function(fv){if(this.majorfv.major){return true;}if(this.minorfv.minor){return true;}if(this.rev=0;i--){_2f[i].style.display="none";for(var x in _2f[i]){if(typeof _2f[i][x]=="function"){_2f[i][x]=function(){};}}}};if(deconcept.SWFObject.doPrepUnload){if(!deconcept.unloadSet){deconcept.SWFObjectUtil.prepUnload=function(){__flash_unloadHandler=function(){};__flash_savedUnloadHandler=function(){};window.attachEvent("onunload",deconcept.SWFObjectUtil.cleanupSWFs);};window.attachEvent("onbeforeunload",deconcept.SWFObjectUtil.prepUnload);deconcept.unloadSet=true;}}if(!document.getElementById&&document.all){document.getElementById=function(id){return document.all[id];};}var getQueryParamValue=deconcept.util.getRequestParameter;var FlashObject=deconcept.SWFObject;var SWFObject=deconcept.SWFObject; diff --git a/htdocs/includes/ckeditor/_samples/autogrow.html b/htdocs/includes/ckeditor/_samples/autogrow.html new file mode 100755 index 00000000000..560474fbfd3 --- /dev/null +++ b/htdocs/includes/ckeditor/_samples/autogrow.html @@ -0,0 +1,107 @@ + + + + + AutoGrow Plugin — CKEditor Sample + + + + + + +

+ CKEditor Sample — Using AutoGrow Plugin +

+
+

+ This sample shows how to configure CKEditor instances to use the + autogrow plugin that lets the editor window expand and shrink + depending on the amount and size of content entered in the editing area. +

+

+ In its default implementation the AutoGrow feature can expand the + CKEditor window infinitely in order to avoid introducing scrollbars to the editing area. +

+

+ It is also possible to set a maximum height for the editor window. Once CKEditor + editing area reaches the value in pixels specified in the autoGrow_maxHeight + attribute, scrollbars will be added and the editor window will no longer expand. +

+

+ To add a CKEditor instance using the autogrow plugin and its + autoGrow_maxHeight attribute, insert the following JavaScript call to your code: +

+
CKEDITOR.replace( 'textarea_id',
+	{
+		extraPlugins : 'autogrow',
+		autoGrow_maxHeight : 800,
+		// Remove the resize plugin, as it doesn't make sense to use it in conjunction with the AutoGrow plugin.
+		removePlugins : 'resize'
+	});
+

+ Note that textarea_id in the code above is the id attribute of + the <textarea> element to be replaced with CKEditor. The maximum height should + be given in pixels. +

+
+ +
+ +
+
+

+ + + +

+

+ + + +

+

+ +

+
+ + + diff --git a/htdocs/includes/ckeditor/_samples/bbcode.html b/htdocs/includes/ckeditor/_samples/bbcode.html new file mode 100755 index 00000000000..6dafd02fb5c --- /dev/null +++ b/htdocs/includes/ckeditor/_samples/bbcode.html @@ -0,0 +1,125 @@ + + + + + BBCode Plugin — CKEditor Sample + + + + + + +

+ CKEditor Sample — BBCode Plugin +

+
+

+ This sample shows how to configure CKEditor to output BBCode format instead of HTML. + Please note that the editor configuration was modified to reflect what is needed in a BBCode editing environment. + Smiley images, for example, were stripped to the emoticons that are commonly used in some BBCode dialects. +

+

+ Please note that currently there is no standard for the BBCode markup language, so its implementation + for different platforms (message boards, blogs etc.) can vary. This means that before using CKEditor to + output BBCode you may need to adjust the implementation to your own environment. +

+

+ A snippet of the configuration code can be seen below; check the source of this page for + a full definition: +

+
+CKEDITOR.replace( 'editor1',
+	{
+		extraPlugins : 'bbcode',
+		toolbar :
+		[
+			['Source', '-', 'Save','NewPage','-','Undo','Redo'],
+			['Find','Replace','-','SelectAll','RemoveFormat'],
+			['Link', 'Unlink', 'Image'],
+			'/',
+			['FontSize', 'Bold', 'Italic','Underline'],
+			['NumberedList','BulletedList','-','Blockquote'],
+			['TextColor', '-', 'Smiley','SpecialChar', '-', 'Maximize']
+		],
+		... some other configurations omitted here
+	});	
+
+ + +
+ +
+
+

+ + + +

+

+ +

+
+ + + diff --git a/htdocs/includes/ckeditor/_samples/devtools.html b/htdocs/includes/ckeditor/_samples/devtools.html new file mode 100755 index 00000000000..87a3a7f67ec --- /dev/null +++ b/htdocs/includes/ckeditor/_samples/devtools.html @@ -0,0 +1,94 @@ + + + + + Using DevTools Plugin — CKEditor Sample + + + + + + +

+ CKEditor Sample — Using the Developer Tools Plugin +

+
+

+ This sample shows how to configure CKEditor instances to use the + Developer Tools (devtools) plugin that displays + information about dialog window elements, including the name of the dialog window, + tab, and UI element. Please note that the tooltip also contains a link to the + CKEditor JavaScript API + documentation for each of the selected elements. +

+

+ This plugin is aimed at developers who would like to customize their CKEditor + instances and create their own plugins. By default it is turned off; it is + usually useful to only turn it on in the development phase. Note that it works with + all CKEditor dialog windows, including the ones that were created by custom plugins. +

+

+ To add a CKEditor instance using the devtools plugin, insert + the following JavaScript call into your code: +

+
CKEDITOR.replace( 'textarea_id',
+	{
+		extraPlugins : 'devtools'
+	});
+

+ Note that textarea_id in the code above is the id attribute of + the <textarea> element to be replaced with CKEditor. +

+
+ + +
+ +
+
+

+ + + +

+

+ +

+
+ + + diff --git a/htdocs/includes/ckeditor/_samples/divreplace.html b/htdocs/includes/ckeditor/_samples/divreplace.html new file mode 100755 index 00000000000..4568f5285d0 --- /dev/null +++ b/htdocs/includes/ckeditor/_samples/divreplace.html @@ -0,0 +1,154 @@ + + + + + Replace DIV — CKEditor Sample + + + + + + + + + +

+ CKEditor Sample — Replace DIV with CKEditor on the Fly +

+
+

+ This sample shows how to automatically replace <div> elements + with a CKEditor instance on the fly, following user's doubleclick. The content + that was previously placed inside the <div> element will now + be moved into CKEditor editing area. +

+

+ For details on how to create this setup check the source code of this sample page. +

+
+ +
+ +
+

+ Double-click any of the following <div> elements to transform them into + editor instances.

+
+

+ Part 1

+

+ Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Cras et ipsum quis mi + semper accumsan. Integer pretium dui id massa. Suspendisse in nisl sit amet urna + rutrum imperdiet. Nulla eu tellus. Donec ante nisi, ullamcorper quis, fringilla + nec, sagittis eleifend, pede. Nulla commodo interdum massa. Donec id metus. Fusce + eu ipsum. Suspendisse auctor. Phasellus fermentum porttitor risus. +

+
+
+

+ Part 2

+

+ Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Cras et ipsum quis mi + semper accumsan. Integer pretium dui id massa. Suspendisse in nisl sit amet urna + rutrum imperdiet. Nulla eu tellus. Donec ante nisi, ullamcorper quis, fringilla + nec, sagittis eleifend, pede. Nulla commodo interdum massa. Donec id metus. Fusce + eu ipsum. Suspendisse auctor. Phasellus fermentum porttitor risus. +

+

+ Donec velit. Mauris massa. Vestibulum non nulla. Nam suscipit arcu nec elit. Phasellus + sollicitudin iaculis ante. Ut non mauris et sapien tincidunt adipiscing. Vestibulum + vitae leo. Suspendisse nec mi tristique nulla laoreet vulputate. +

+
+
+

+ Part 3

+

+ Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Cras et ipsum quis mi + semper accumsan. Integer pretium dui id massa. Suspendisse in nisl sit amet urna + rutrum imperdiet. Nulla eu tellus. Donec ante nisi, ullamcorper quis, fringilla + nec, sagittis eleifend, pede. Nulla commodo interdum massa. Donec id metus. Fusce + eu ipsum. Suspendisse auctor. Phasellus fermentum porttitor risus. +

+
+ + + diff --git a/htdocs/includes/ckeditor/_samples/enterkey.html b/htdocs/includes/ckeditor/_samples/enterkey.html new file mode 100755 index 00000000000..e6e6c8ffa2f --- /dev/null +++ b/htdocs/includes/ckeditor/_samples/enterkey.html @@ -0,0 +1,115 @@ + + + + + ENTER Key Configuration — CKEditor Sample + + + + + + + +

+ CKEditor Sample — ENTER Key Configuration +

+
+

+ This sample shows how to configure the Enter and Shift+Enter keys + to perform actions specified in the + enterMode + and shiftEnterMode + parameters, respectively. + You can choose from the following options: +

+
    +
  • ENTER_P – new <p> paragraphs are created;
  • +
  • ENTER_BR – lines are broken with <br> elements;
  • +
  • ENTER_DIV – new <div> blocks are created.
  • +
+

+ The sample code below shows how to configure CKEditor to create a <div> block when Enter key is pressed. +

+
CKEDITOR.replace( 'textarea_id',
+	{
+		enterMode : CKEDITOR.ENTER_DIV
+	});
+

+ Note that textarea_id in the code above is the id attribute of + the <textarea> element to be replaced. +

+
+ + +
+ +
+
+ When Enter is pressed:
+ +
+
+ When Shift+Enter is pressed:
+ +
+
+
+

+
+ +

+

+ +

+
+ + + diff --git a/htdocs/includes/ckeditor/_samples/fullpage.html b/htdocs/includes/ckeditor/_samples/fullpage.html new file mode 100755 index 00000000000..0b0070e0d32 --- /dev/null +++ b/htdocs/includes/ckeditor/_samples/fullpage.html @@ -0,0 +1,82 @@ + + + + + Full Page Editing with Document Properties Plugin — CKEditor Sample + + + + + + +

+ CKEditor Sample — Full Page Editing with Document Properties Plugin +

+
+

+ This sample shows how to configure CKEditor to edit entire HTML pages, from the + <html> tag to the </html> tag. +

+

+ The Document Properties (docprops) plugin is also turned on. + This plugin allows you to set the metadata of the page, including the page encoding, margins, + meta tags, or background. +

+

+ The CKEditor instance below is inserted with a JavaScript call using the following code: +

+
CKEDITOR.replace( 'textarea_id',
+	{
+		fullPage : true,
+		extraPlugins : 'docprops'
+	});
+

+ Note that textarea_id in the code above is the id attribute of + the <textarea> element to be replaced. +

+
+ + +
+ +
+
+ + + +

+ +

+
+ + + diff --git a/htdocs/includes/ckeditor/_samples/index.html b/htdocs/includes/ckeditor/_samples/index.html new file mode 100755 index 00000000000..dabc9afe9f9 --- /dev/null +++ b/htdocs/includes/ckeditor/_samples/index.html @@ -0,0 +1,116 @@ + + + + + CKEditor Samples + + + + +

+ CKEditor Samples Site +

+

+ Basic Samples +

+ +

+ Basic Customization +

+
    +
  • Skins
    + Changing the CKEditor skin by adjusting a single configuration option. +
  • +
  • User Interface color
    + Changing CKEditor User Interface color and adding a toolbar button that lets the user set the UI color. +
  • +
  • User Interface languages
    + Changing CKEditor User Interface language and adding a drop-down list that lets the user choose the UI language. +
  • +
+

+ Advanced Samples +

+ +

+ Additional plugins +

+
    +
  • AutoGrow plugin
    + Using the AutoGrow plugin in order to make the editor grow to fit the size of its content. +
  • +
  • Output for BBCode
    + Configuring CKEditor to produce BBCode tags instead of HTML. +
  • +
  • Stylesheet Parser plugin
    + Using the Stylesheet Parser plugin to fill the Styles drop-down list based on the CSS classes available in the document stylesheet. +
  • +
  • Developer Tools plugin
    + Using the Developer Tools plugin to display information about dialog window UI elements to allow for easier customization. +
  • +
  • Placeholder plugin
    + Using the Placeholder plugin to create uneditable sections that can only be created and modified with a proper dialog window. +
  • +
  • TableResize plugin
    + Using the TableResize plugin to enable table column resizing. +
  • +
+ + + diff --git a/htdocs/includes/ckeditor/_samples/jqueryadapter.html b/htdocs/includes/ckeditor/_samples/jqueryadapter.html new file mode 100755 index 00000000000..2772878e967 --- /dev/null +++ b/htdocs/includes/ckeditor/_samples/jqueryadapter.html @@ -0,0 +1,99 @@ + + + + + jQuery Adapter — CKEditor Sample + + + + + + + + + +

+ CKEditor Sample — Using jQuery Adapter +

+
+

+ This sample shows how to load CKEditor and configure it using the + jQuery adapter. + In this case the jQuery adapter is responsible for transforming a <textarea> + element into a CKEditor instance and setting the configuration of the toolbar. +

+

+ CKEditor instance with custom configuration set in jQuery can be inserted with the + following JavaScript code: +

+
$(function()
+{
+	var config = {
+		skin:'v2'
+	};
+
+	$('.textarea_class').ckeditor(config);
+});
+

+ Note that textarea_class in the code above is the + class attribute of the <textarea> element to be replaced with + CKEditor. Any other jQuery selector can be used to match the target element. +

+
+ + +
+ +
+ +
+

+ + +

+

+ +

+
+ + + diff --git a/htdocs/includes/ckeditor/_samples/output_for_flash.html b/htdocs/includes/ckeditor/_samples/output_for_flash.html new file mode 100755 index 00000000000..5dc63a40af0 --- /dev/null +++ b/htdocs/includes/ckeditor/_samples/output_for_flash.html @@ -0,0 +1,275 @@ + + + + + Output for Flash — CKEditor Sample + + + + + + + + +

+ CKEditor Sample — Producing Flash Compliant HTML Output +

+
+

+ This sample shows how to configure CKEditor to output + HTML code that can be used with + + Adobe Flash. + The code will contain a subset of standard HTML elements like <b>, + <i>, and <p> as well as HTML attributes. +

+

+ To add a CKEditor instance outputting Flash compliant HTML code, load the editor using a standard + JavaScript call, and define CKEditor features to use HTML elements and attributes. +

+

+ For details on how to create this setup check the source code of this sample page. +

+
+

+ To see how it works, create some content in the editing area of CKEditor on the left + and send it to the Flash object on the right side of the page by using the + Send to Flash button. +

+ + +
+ +
+
+ + + + + +
+ + + + +
+ + + + diff --git a/htdocs/includes/ckeditor/_samples/output_html.html b/htdocs/includes/ckeditor/_samples/output_html.html new file mode 100755 index 00000000000..ad63716d663 --- /dev/null +++ b/htdocs/includes/ckeditor/_samples/output_html.html @@ -0,0 +1,285 @@ + + + + + HTML Compliant Output — CKEditor Sample + + + + + + +

+ CKEditor Sample — Producing HTML Compliant Output +

+
+

+ This sample shows how to configure CKEditor to output valid + HTML 4.01 code. + Traditional HTML elements like <b>, + <i>, and <font> are used in place of + <strong>, <em>, and CSS styles. +

+

+ To add a CKEditor instance outputting legacy HTML 4.01 code, load the editor using a standard + JavaScript call, and define CKEditor features to use the HTML compliant elements and attributes. +

+

+ A snippet of the configuration code can be seen below; check the source of this page for + full definition: +

+
CKEDITOR.replace( 'textarea_id',
+	{
+		coreStyles_bold	: { element : 'b' },
+		coreStyles_italic : { element : 'i' },
+
+		fontSize_style :
+			{
+				element		: 'font',
+				attributes	: { 'size' : '#(size)' }
+			}
+
+		// More definitions follow.
+	});
+
+ + +
+ +
+
+

+ + + +

+

+ +

+
+ + + diff --git a/htdocs/includes/ckeditor/_samples/output_xhtml.html b/htdocs/includes/ckeditor/_samples/output_xhtml.html new file mode 100755 index 00000000000..2fcd5b27c68 --- /dev/null +++ b/htdocs/includes/ckeditor/_samples/output_xhtml.html @@ -0,0 +1,177 @@ + + + + + XHTML Compliant Output — CKEditor Sample + + + + + + +

+ CKEditor Sample — Producing XHTML Compliant Output +

+
+

+ This sample shows how to configure CKEditor to output valid + XHTML 1.1 code. + Deprecated elements (<font>, <u>) or attributes + (size, face) will be replaced with XHTML compliant code. +

+

+ To add a CKEditor instance outputting valid XHTML code, load the editor using a standard + JavaScript call and define CKEditor features to use the XHTML compliant elements and styles. +

+

+ A snippet of the configuration code can be seen below; check the source of this page for + full definition: +

+
CKEDITOR.replace( 'textarea_id',
+	{
+		contentsCss : 'assets/output_xhtml.css',
+
+		coreStyles_bold	: { element : 'span', attributes : {'class': 'Bold'} },
+		coreStyles_italic : { element : 'span', attributes : {'class': 'Italic'} },
+
+		// More definitions follow.
+	});
+
+ + +
+ +
+
+

+ + + +

+

+ +

+
+ + + diff --git a/htdocs/includes/ckeditor/_samples/php/advanced.php b/htdocs/includes/ckeditor/_samples/php/advanced.php new file mode 100755 index 00000000000..1d62ce5e2c5 --- /dev/null +++ b/htdocs/includes/ckeditor/_samples/php/advanced.php @@ -0,0 +1,120 @@ + + + + + Setting Configuration Options — CKEditor Sample + + + + +

+ CKEditor Sample — Setting Configuration Options +

+

+ This sample shows how to insert a CKEditor instance with custom configuration options. +

+

+ To set configuration options, use the config property. To set the attributes of a <textarea> element (which is displayed instead of CKEditor in unsupported browsers), use the textareaAttributes property. +

+
+<?php
+// Include the CKEditor class.
+include_once "ckeditor/ckeditor.php";
+
+// Create a class instance.
+$CKEditor = new CKEditor();
+
+// Path to the CKEditor directory.
+$CKEditor->basePath = '/ckeditor/';
+
+// Set global configuration (used by every instance of CKEditor).
+$CKEditor->config['width'] = 600;
+
+// Change default textarea attributes.
+$CKEditor->textareaAttributes = array("cols" => 80, "rows" => 10);
+
+// The initial value to be displayed in the editor.
+$initialValue = 'This is some sample text.';
+
+// Create the first instance.
+$CKEditor->editor("textarea_id", $initialValue);
+?>
+

+ Note that textarea_id in the code above is the name attribute of + the <textarea> element to be created. +

+ + +
+ +
+
+ +returnOutput = true; + +// Path to the CKEditor directory, ideally use an absolute path instead of a relative dir. +// $CKEditor->basePath = '/ckeditor/' +// If not set, CKEditor will try to detect the correct path. +$CKEditor->basePath = '../../'; + +// Set global configuration (will be used by all instances of CKEditor). +$CKEditor->config['width'] = 600; + +// Change default textarea attributes. +$CKEditor->textareaAttributes = array("cols" => 80, "rows" => 10); + +// The initial value to be displayed in the editor. +$initialValue = '

This is some sample text. You are using CKEditor.

'; + +// Create the first instance. +$code = $CKEditor->editor("editor1", $initialValue); + +echo $code; +?> +
+ +editor("editor2", $initialValue, $config); +?> +

+ +

+
+ + + diff --git a/htdocs/includes/ckeditor/_samples/php/events.php b/htdocs/includes/ckeditor/_samples/php/events.php new file mode 100755 index 00000000000..a6251921124 --- /dev/null +++ b/htdocs/includes/ckeditor/_samples/php/events.php @@ -0,0 +1,153 @@ + + + + + Adding Event Handlers — CKEditor Sample + + + + +

+ CKEditor Sample — Adding Event Handlers +

+
+

+ This sample shows how to add event handlers to CKEditor with PHP. +

+

+ A snippet of the configuration code can be seen below; check the source code of this page for + the full definition: +

+
<?php
+// Include the CKEditor class.
+include("ckeditor/ckeditor.php");
+
+// Create a class instance.
+$CKEditor = new CKEditor();
+
+// Path to the CKEditor directory.
+$CKEditor->basePath = '/ckeditor/';
+
+// The initial value to be displayed in the editor.
+$initialValue = 'This is some sample text.';
+
+// Add event handler, instanceReady is fired when editor is loaded.
+$CKEditor->addEventHandler('instanceReady', 'function (evt) {
+	alert("Loaded editor: " + evt.editor.name);
+}');
+
+// Create an editor instance.
+$CKEditor->editor("editor1", $initialValue);
+
+
+ +
+ +
+
+ +addGlobalEventHandler('dialogDefinition', $function); +} + +/** + * Adds a global event, will notify about an open dialog window. + */ +function CKEditorNotifyAboutOpenedDialog(&$CKEditor) { + $function = 'function (evt) { + alert("Loading a dialog window: " + evt.data.name); + }'; + + $CKEditor->addGlobalEventHandler('dialogDefinition', $function); +} + +// Include the CKEditor class. +include("../../ckeditor.php"); + +// Create a class instance. +$CKEditor = new CKEditor(); + +// Set a configuration option for all editors. +$CKEditor->config['width'] = 750; + +// Path to the CKEditor directory, ideally use an absolute path instead of a relative dir. +// $CKEditor->basePath = '/ckeditor/' +// If not set, CKEditor will try to detect the correct path. +$CKEditor->basePath = '../../'; + +// The initial value to be displayed in the editor. +$initialValue = '

This is some sample text. You are using CKEditor.

'; + +// Event that will be handled only by the first editor. +$CKEditor->addEventHandler('instanceReady', 'function (evt) { + alert("Loaded editor: " + evt.editor.name); +}'); + +// Create the first instance. +$CKEditor->editor("editor1", $initialValue); + +// Clear event handlers. Instances that will be created later will not have +// the 'instanceReady' listener defined a couple of lines above. +$CKEditor->clearEventHandlers(); +?> +
+ +editor("editor2", $initialValue, $config, $events); +?> +

+ +

+
+ + + diff --git a/htdocs/includes/ckeditor/_samples/php/index.html b/htdocs/includes/ckeditor/_samples/php/index.html new file mode 100755 index 00000000000..b26792d7d50 --- /dev/null +++ b/htdocs/includes/ckeditor/_samples/php/index.html @@ -0,0 +1,47 @@ + + + + + + CKEditor Samples — PHP Integration + + + +

+ CKEditor Samples List for PHP +

+

+ Basic Samples +

+ +

+ Advanced Samples +

+ + + + diff --git a/htdocs/includes/ckeditor/_samples/php/replace.php b/htdocs/includes/ckeditor/_samples/php/replace.php new file mode 100755 index 00000000000..d22dcb7cc4f --- /dev/null +++ b/htdocs/includes/ckeditor/_samples/php/replace.php @@ -0,0 +1,87 @@ + + + + + Replace Selected Textarea Elements — CKEditor Sample + + + + +

+ CKEditor Sample — Replace Selected Textarea Elements Using PHP Code +

+
+

+ This sample shows how to replace a selected <textarea> element + with a CKEditor instance by using PHP code. +

+

+ To replace a <textarea> element, place the following call at any point + after the <textarea> element: +

+
+<?php
+// Include the CKEditor class.
+include_once "ckeditor/ckeditor.php";
+
+// Create a class instance.
+$CKEditor = new CKEditor();
+
+// Path to the CKEditor directory.
+$CKEditor->basePath = '/ckeditor/';
+
+// Replace a textarea element with an id (or name) of "textarea_id".
+$CKEditor->replace("textarea_id");
+?>
+

+ Note that textarea_id in the code above is the id attribute of + the <textarea> element to be replaced. +

+
+ +
+ +
+
+

+ + +

+

+ +

+
+ + basePath = '/ckeditor/' + // If not set, CKEditor will try to detect the correct path. + $CKEditor->basePath = '../../'; + // Replace a textarea element with an id (or name) of "editor1". + $CKEditor->replace("editor1"); + ?> + + diff --git a/htdocs/includes/ckeditor/_samples/php/replaceall.php b/htdocs/includes/ckeditor/_samples/php/replaceall.php new file mode 100755 index 00000000000..4415fbdc1be --- /dev/null +++ b/htdocs/includes/ckeditor/_samples/php/replaceall.php @@ -0,0 +1,88 @@ + + + + + Replace All Textarea Elements — CKEditor Sample + + + + +

+ CKEditor Sample — Replace All Textarea Elements Using PHP Code +

+
+

+ This sample shows how to replace all <textarea> elements + with CKEditor by using PHP code. +

+

+ To replace all <textarea> elements, place the following call at any point + after the last <textarea> element: +

+
+<?php
+// Include the CKEditor class.
+include("ckeditor/ckeditor.php");
+
+// Create a class instance.
+$CKEditor = new CKEditor();
+
+// Path to the CKEditor directory.
+$CKEditor->basePath = '/ckeditor/';
+
+// Replace all textarea elements with CKEditor.
+$CKEditor->replaceAll();
+?>
+
+ +
+ +
+
+

+ + +

+

+ + +

+

+ +

+
+ + basePath = '/ckeditor/' + // If not set, CKEditor will try to detect the correct path. + $CKEditor->basePath = '../../'; + // Replace all textarea elements with CKEditor. + $CKEditor->replaceAll(); + ?> + + diff --git a/htdocs/includes/ckeditor/_samples/php/standalone.php b/htdocs/includes/ckeditor/_samples/php/standalone.php new file mode 100755 index 00000000000..cb8d2dee49c --- /dev/null +++ b/htdocs/includes/ckeditor/_samples/php/standalone.php @@ -0,0 +1,83 @@ + + + + + Creating CKEditor Instances — CKEditor Sample + + + + +

+ CKEditor Sample — Creating CKEditor Instances +

+
+

+ This sample shows how to create a CKEditor instance with PHP. +

+
+<?php
+include_once "ckeditor/ckeditor.php";
+
+// Create a class instance.
+$CKEditor = new CKEditor();
+
+// Path to the CKEditor directory.
+$CKEditor->basePath = '/ckeditor/';
+
+// Create a textarea element and attach CKEditor to it.
+$CKEditor->editor("textarea_id", "This is some sample text");
+?>
+

+ Note that textarea_id in the code above is the id and name attribute of + the <textarea> element that will be created. +

+
+ +
+ +
+ +
+

+ +

+

+ This is some sample text.

'; + // Create a class instance. + $CKEditor = new CKEditor(); + // Path to the CKEditor directory, ideally use an absolute path instead of a relative dir. + // $CKEditor->basePath = '/ckeditor/' + // If not set, CKEditor will try to detect the correct path. + $CKEditor->basePath = '../../'; + // Create a textarea element and attach CKEditor to it. + $CKEditor->editor("editor1", $initialValue); + ?> + +

+
+ + + diff --git a/htdocs/includes/ckeditor/_samples/placeholder.html b/htdocs/includes/ckeditor/_samples/placeholder.html new file mode 100755 index 00000000000..015e01b6e79 --- /dev/null +++ b/htdocs/includes/ckeditor/_samples/placeholder.html @@ -0,0 +1,81 @@ + + + + + Placeholder Plugin — CKEditor Sample + + + + + + +

+ CKEditor Sample — Using the Placeholder Plugin +

+
+

+ This sample shows how to configure CKEditor instances to use the + Placeholder plugin that lets you insert read-only elements + into your content. To enter and modify read-only text, use the + Create Placeholder button and its matching dialog window. +

+

+ To add a CKEditor instance that uses the placeholder plugin and a related + Create Placeholder toolbar button, insert the following JavaScript + call to your code: +

+
CKEDITOR.replace( 'textarea_id',
+	{
+		extraPlugins : 'placeholder',
+		toolbar : [ [ 'Source', 'Bold' ], ['CreatePlaceholder'] ]
+	});
+

+ Note that textarea_id in the code above is the id attribute of + the <textarea> element to be replaced with CKEditor. +

+
+ +
+ +
+
+

+ + + +

+

+ +

+
+ + + diff --git a/htdocs/includes/ckeditor/_samples/readonly.html b/htdocs/includes/ckeditor/_samples/readonly.html new file mode 100755 index 00000000000..8b6c3a894be --- /dev/null +++ b/htdocs/includes/ckeditor/_samples/readonly.html @@ -0,0 +1,91 @@ + + + + + Read-only State — CKEditor Sample + + + + + + + + +

+ CKEditor Sample — Using the CKEditor Read-Only API +

+
+

+ This sample shows how to use the + setReadOnly + API to put editor into the read-only state that makes it impossible for users to change the editor contents. +

+

+ For details on how to create this setup check the source code of this sample page. +

+
+ + +
+ +
+
+

+ +

+

+ + +

+
+ + + diff --git a/htdocs/includes/ckeditor/_samples/replacebyclass.html b/htdocs/includes/ckeditor/_samples/replacebyclass.html new file mode 100755 index 00000000000..117640f82e7 --- /dev/null +++ b/htdocs/includes/ckeditor/_samples/replacebyclass.html @@ -0,0 +1,64 @@ + + + + + Replace Textareas by Class Name — CKEditor Sample + + + + + + +

+ CKEditor Sample — Replace Textarea Elements by Class Name +

+
+

+ This sample shows how to automatically replace all <textarea> elements + of a given class with a CKEditor instance. +

+

+ To replace a <textarea> element, simply assign it the ckeditor + class, as in the code below: +

+
<textarea class="ckeditor" name="editor1"></textarea>
+

+ Note that other <textarea> attributes (like id or name) need to be adjusted to your document. +

+
+ + +
+ +
+
+

+ + +

+

+ +

+
+ + + diff --git a/htdocs/includes/ckeditor/_samples/replacebycode.html b/htdocs/includes/ckeditor/_samples/replacebycode.html new file mode 100755 index 00000000000..beb779e5e56 --- /dev/null +++ b/htdocs/includes/ckeditor/_samples/replacebycode.html @@ -0,0 +1,97 @@ + + + + + Replace Textarea by Code — CKEditor Sample + + + + + + +

+ CKEditor Sample — Replace Textarea Elements Using JavaScript Code +

+
+

+ This sample shows how to automatically replace all <textarea> elements + with a CKEditor instance by using a JavaScript call. +

+

+ To replace a <textarea> element, place the following call at any point + after the <textarea> element or inside a <script> element located + in the <head> section of the page, in a window.onload event handler: +

+
CKEDITOR.replace( 'textarea_id' );
+

+ Note that textarea_id in the code above is the id attribute of + the <textarea> element to be replaced. +

+
+ + +
+ +
+
+

+ + + +

+

+ + + +

+

+ +

+
+ + + diff --git a/htdocs/includes/ckeditor/_samples/sample.css b/htdocs/includes/ckeditor/_samples/sample.css new file mode 100755 index 00000000000..a9a57ee1a29 --- /dev/null +++ b/htdocs/includes/ckeditor/_samples/sample.css @@ -0,0 +1,163 @@ +/* +Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +html, body, h1, h2, h3, h4, h5, h6, div, span, blockquote, p, address, form, fieldset, img, ul, ol, dl, dt, dd, li, hr, table, td, th, strong, em, sup, sub, dfn, ins, del, q, cite, var, samp, code, kbd, tt, pre { + line-height: 1.5em; +} + +body { + padding:10px 30px; +} + +input, textarea, select, option, optgroup, button, td, th { + font-size: 100%; +} + +pre, +code, +kbd, +samp, +tt{ + font-family: monospace,monospace; + font-size: 1em; +} + +h1.samples { + color:#0782C1; + font-size:200%; + font-weight:normal; + margin: 0; + padding: 0; +} + +h2.samples { + color:#000000; + font-size:130%; + margin: 0; + padding: 0; +} + +p, blockquote, address, form, pre, dl, h1.samples, h2.samples { + margin-bottom:15px; +} + +ul.samples { + margin-bottom:15px; +} + +.clear { + clear:both; +} + +fieldset +{ + margin: 0; + padding: 10px; +} + +body, input, textarea { + color: #333333; + font-family: Arial, Helvetica, sans-serif; +} + +body { + font-size: 75%; +} + +a.samples { + color:#189DE1; + text-decoration:none; +} + +a.samples:hover { + text-decoration:underline; +} + +form +{ + margin: 0; + padding: 0; +} + +pre.samples +{ + background-color: #F7F7F7; + border: 1px solid #D7D7D7; + overflow: auto; + padding: 0.25em; +} + +#alerts +{ + color: Red; +} + +#footer hr +{ + margin: 10px 0 15px 0; + height: 1px; + border: solid 1px gray; + border-bottom: none; +} + +#footer p +{ + margin: 0 10px 10px 10px; + float: left; +} + +#footer #copy +{ + float: right; +} + +#outputSample +{ + width: 100%; + table-layout: fixed; +} + +#outputSample thead th +{ + color: #dddddd; + background-color: #999999; + padding: 4px; + white-space: nowrap; +} + +#outputSample tbody th +{ + vertical-align: top; + text-align: left; +} + +#outputSample pre +{ + margin: 0; + padding: 0; + white-space: pre; /* CSS2 */ + white-space: -moz-pre-wrap; /* Mozilla*/ + white-space: -o-pre-wrap; /* Opera 7 */ + white-space: pre-wrap; /* CSS 2.1 */ + white-space: pre-line; /* CSS 3 (and 2.1 as well, actually) */ + word-wrap: break-word; /* IE */ +} + +.description { + border: 1px dotted #B7B7B7; + margin-bottom: 10px; + padding: 10px 10px 0; +} + +label { + display: block; + margin-bottom:6px; +} + +.cke_dialog label +{ + display: inline; + margin-bottom: auto; +} diff --git a/htdocs/includes/ckeditor/_samples/sample.js b/htdocs/includes/ckeditor/_samples/sample.js new file mode 100755 index 00000000000..9e0b41100cd --- /dev/null +++ b/htdocs/includes/ckeditor/_samples/sample.js @@ -0,0 +1,65 @@ +/* +Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +// This file is not required by CKEditor and may be safely ignored. +// It is just a helper file that displays a red message about browser compatibility +// at the top of the samples (if incompatible browser is detected). + +if ( window.CKEDITOR ) +{ + (function() + { + var showCompatibilityMsg = function() + { + var env = CKEDITOR.env; + + var html = '

Your browser is not compatible with CKEditor.'; + + var browsers = + { + gecko : 'Firefox 2.0', + ie : 'Internet Explorer 6.0', + opera : 'Opera 9.5', + webkit : 'Safari 3.0' + }; + + var alsoBrowsers = ''; + + for ( var key in env ) + { + if ( browsers[ key ] ) + { + if ( env[key] ) + html += ' CKEditor is compatible with ' + browsers[ key ] + ' or higher.'; + else + alsoBrowsers += browsers[ key ] + '+, '; + } + } + + alsoBrowsers = alsoBrowsers.replace( /\+,([^,]+), $/, '+ and $1' ); + + html += ' It is also compatible with ' + alsoBrowsers + '.'; + + html += '

With non compatible browsers, you should still be able to see and edit the contents (HTML) in a plain text field.

'; + + var alertsEl = document.getElementById( 'alerts' ); + alertsEl && ( alertsEl.innerHTML = html ); + }; + + var onload = function() + { + // Show a friendly compatibility message as soon as the page is loaded, + // for those browsers that are not compatible with CKEditor. + if ( !CKEDITOR.env.isCompatible ) + showCompatibilityMsg(); + }; + + // Register the onload listener. + if ( window.addEventListener ) + window.addEventListener( 'load', onload, false ); + else if ( window.attachEvent ) + window.attachEvent( 'onload', onload ); + })(); +} diff --git a/htdocs/includes/ckeditor/_samples/sample_posteddata.php b/htdocs/includes/ckeditor/_samples/sample_posteddata.php new file mode 100755 index 00000000000..71cdd84ea6b --- /dev/null +++ b/htdocs/includes/ckeditor/_samples/sample_posteddata.php @@ -0,0 +1,21 @@ + diff --git a/htdocs/includes/ckeditor/_samples/sharedspaces.html b/htdocs/includes/ckeditor/_samples/sharedspaces.html new file mode 100755 index 00000000000..624b5fc2de5 --- /dev/null +++ b/htdocs/includes/ckeditor/_samples/sharedspaces.html @@ -0,0 +1,153 @@ + + + + + Shared Toolbars — CKEditor Sample + + + + + + + +

+ CKEditor Sample — Shared Toolbars +

+
+

+ This sample shows how to configure multiple CKEditor instances to share some parts of the interface. + You can choose to share the toolbar (topSpace), the elements path + (bottomSpace), or both. +

+

+ CKEditor instances with shared spaces can be inserted with a JavaScript call using the following code: +

+
CKEDITOR.replace( 'textarea_id',
+	{
+		sharedSpaces :
+		{
+			top : 'topSpace',
+			bottom : 'bottomSpace'
+		}
+	});
+

+ Note that textarea_id in the code above is the id attribute of + the <textarea> element to be replaced with CKEditor. +

+
+ +
+ +
+
+
+
+

+ + +

+

+ + +

+

+ + +

+

+ + +

+

+ +

+
+
+
+ + + + diff --git a/htdocs/includes/ckeditor/_samples/skins.html b/htdocs/includes/ckeditor/_samples/skins.html new file mode 100755 index 00000000000..a9d7b6e2488 --- /dev/null +++ b/htdocs/includes/ckeditor/_samples/skins.html @@ -0,0 +1,110 @@ + + + + + Skins — CKEditor Sample + + + + + + +

+ CKEditor Sample — Skins +

+
+

+ This sample shows how to automatically replace <textarea> elements + with a CKEditor instance using a specific skin. +

+

+ CKEditor with a specified skin (in this case, the "Office 2003" skin) is inserted with a JavaScript call using the following code: +

+
CKEDITOR.replace( 'textarea_id',
+	{
+		skin : 'office2003'
+	});
+

+ Note that textarea_id in the code above is the id attribute of + the <textarea> element to be replaced. +

+
+ + +
+ +
+
+

"Kama" skin

+

The default skin used in CKEditor. No additional configuration is required.

+

+ + +

+

"Office 2003" skin

+

Use the following code to configure a CKEditor instance to use the "Office 2003" skin.

+
CKEDITOR.replace( 'textarea_id',
+	{
+		skin : 'office2003'
+	});
+

+ + +

+

"V2" skin

+

Use the following code to configure a CKEditor instance to use the "V2" skin.

+
CKEDITOR.replace( 'textarea_id',
+	{
+		skin : 'v2'
+	});
+ + +
+ + + diff --git a/htdocs/includes/ckeditor/_samples/stylesheetparser.html b/htdocs/includes/ckeditor/_samples/stylesheetparser.html new file mode 100755 index 00000000000..a938802e3b4 --- /dev/null +++ b/htdocs/includes/ckeditor/_samples/stylesheetparser.html @@ -0,0 +1,93 @@ + + + + + Using Stylesheet Parser Plugin — CKEditor Sample + + + + + + +

+ CKEditor Sample — Using the Stylesheet Parser Plugin +

+
+

+ This sample shows how to configure CKEditor instances to use the + Stylesheet Parser (stylesheetparser) plugin that fills + the Styles drop-down list based on the CSS rules available in the document stylesheet. +

+

+ To add a CKEditor instance using the stylesheetparser plugin, insert + the following JavaScript call into your code: +

+
CKEDITOR.replace( 'textarea_id',
+	{
+		extraPlugins : 'stylesheetparser'
+	});
+

+ Note that textarea_id in the code above is the id attribute of + the <textarea> element to be replaced with CKEditor. +

+
+ + +
+ +
+
+

+ + + +

+

+ +

+
+ + + diff --git a/htdocs/includes/ckeditor/_samples/tableresize.html b/htdocs/includes/ckeditor/_samples/tableresize.html new file mode 100755 index 00000000000..0bb3919bf00 --- /dev/null +++ b/htdocs/includes/ckeditor/_samples/tableresize.html @@ -0,0 +1,115 @@ + + + + + Using TableResize Plugin — CKEditor Sample + + + + + + +

+ CKEditor Sample — Using the TableResize Plugin +

+
+

+ This sample shows how to configure CKEditor instances to use the + TableResize (tableresize) plugin that allows + the user to edit table columns by using the mouse. +

+

+ The TableResize plugin makes it possible to modify table column width. Hover + your mouse over the column border to see the cursor change to indicate that + the column can be resized. Click and drag your mouse to set the desired width. +

+

+ By default the plugin is turned off. To add a CKEditor instance using the + TableResize plugin, insert the following JavaScript call into your code: +

+
CKEDITOR.replace( 'textarea_id',
+	{
+		extraPlugins : 'tableresize'
+	});
+

+ Note that textarea_id in the code above is the id attribute of + the <textarea> element to be replaced with CKEditor. +

+
+ +
+ +
+
+

+ + + +

+

+ +

+
+ + + diff --git a/htdocs/includes/ckeditor/_samples/ui_color.html b/htdocs/includes/ckeditor/_samples/ui_color.html new file mode 100755 index 00000000000..78e0104899d --- /dev/null +++ b/htdocs/includes/ckeditor/_samples/ui_color.html @@ -0,0 +1,129 @@ + + + + + UI Color Picker — CKEditor Sample + + + + + + +

+ CKEditor Sample — UI Color Picker +

+
+

+ This sample shows how to automatically replace <textarea> elements + with a CKEditor instance with an option to change the color of its user interface. +

+

Setting the User Interface Color

+

+ To specify the color of the user interface, set the uiColor property: +

+
CKEDITOR.replace( 'textarea_id',
+	{
+		uiColor: '#EE0000'
+	});
+

+ Note that textarea_id in the code above is the id attribute of + the <textarea> element to be replaced. +

+

Enabling the Color Picker

+

+ If the uicolor plugin along with the dedicated UIColor + toolbar button is added to CKEditor, the user will also be able to pick the color of the + UI from the color palette available in the UI Color Picker dialog window. +

+

+ To insert a CKEditor instance with the uicolor plugin enabled, + use the following JavaScript call: +

+
CKEDITOR.replace( 'textarea_id',
+	{
+		extraPlugins : 'uicolor',
+		toolbar : [ [ 'Bold', 'Italic' ], [ 'UIColor' ] ]
+	});
+
+ +
+ +
+

+ Click the UI Color Picker button to test your color preferences at runtime. +

+

+ The first editor instance includes the UI Color Picker toolbar button, + but the default UI color is not defined, so the editor uses the skin color. +

+
+

+ + +

+

+ The second editor instance includes the UI Color Picker toolbar button. The + default UI color was defined, so the skin color is not used. +

+

+ + +

+

+ +

+
+ + + diff --git a/htdocs/includes/ckeditor/_samples/ui_languages.html b/htdocs/includes/ckeditor/_samples/ui_languages.html new file mode 100755 index 00000000000..0832a9afadb --- /dev/null +++ b/htdocs/includes/ckeditor/_samples/ui_languages.html @@ -0,0 +1,134 @@ + + + + + User Interface Globalization — CKEditor Sample + + + + + + + +

+ CKEditor Sample — User Interface Languages +

+
+

+ This sample shows how to automatically replace <textarea> elements + with a CKEditor instance with an option to change the language of its user interface. +

+

+ It pulls the language list from CKEditor _languages.js file that contains the list of supported languages and creates + a drop-down list that lets the user change the UI language. +

+

+ By default, CKEditor automatically localizes the editor to the language of the user. + The UI language can be controlled with two configuration options: + + language and + defaultLanguage. The defaultLanguage setting specifies the + default CKEditor language to be used when a localization suitable for user's settings is not available. +

+

+ To specify the user interface language that will be used no matter what language is + specified in user's browser or operating system, set the language property: +

+
CKEDITOR.replace( 'textarea_id',
+	{
+		// Load the German interface.
+		language: 'de'
+	});
+

+ Note that textarea_id in the code above is the id attribute of + the <textarea> element to be replaced. +

+
+ +
+ +
+
+

+ Available languages ( languages!):
+ +
+ (You may see strange characters if your system does not + support the selected language) +

+

+ + +

+
+ + + diff --git a/htdocs/includes/ckeditor/_source/adapters/jquery.js b/htdocs/includes/ckeditor/_source/adapters/jquery.js new file mode 100755 index 00000000000..1b0a994ea4e --- /dev/null +++ b/htdocs/includes/ckeditor/_source/adapters/jquery.js @@ -0,0 +1,306 @@ +/* +Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @fileOverview jQuery adapter provides easy use of basic CKEditor functions + * and access to internal API. It also integrates some aspects of CKEditor with + * jQuery framework. + * + * Every TEXTAREA, DIV and P elements can be converted to working editor. + * + * Plugin exposes some of editor's event to jQuery event system. All of those are namespaces inside + * ".ckeditor" namespace and can be binded/listened on supported textarea, div and p nodes. + * + * Available jQuery events: + * - instanceReady.ckeditor( editor, rootNode ) + * Triggered when new instance is ready. + * - destroy.ckeditor( editor ) + * Triggered when instance is destroyed. + * - getData.ckeditor( editor, eventData ) + * Triggered when getData event is fired inside editor. It can change returned data using eventData reference. + * - setData.ckeditor( editor ) + * Triggered when getData event is fired inside editor. + * + * @example + * + * + * + */ + +(function() +{ + /** + * Allows CKEditor to override jQuery.fn.val(), making it possible to use the val() + * function on textareas, as usual, having it synchronized with CKEditor.
+ *
+ * This configuration option is global and executed during the jQuery Adapter loading. + * It can't be customized across editor instances. + * @type Boolean + * @example + * <script> + * CKEDITOR.config.jqueryOverrideVal = true; + * </script> + * <!-- Important: The JQuery adapter is loaded *after* setting jqueryOverrideVal --> + * <script src="/ckeditor/adapters/jquery.js"></script> + * @example + * // ... then later in the code ... + * + * $( 'textarea' ).ckeditor(); + * // ... + * $( 'textarea' ).val( 'New content' ); + */ + CKEDITOR.config.jqueryOverrideVal = typeof CKEDITOR.config.jqueryOverrideVal == 'undefined' + ? true : CKEDITOR.config.jqueryOverrideVal; + + var jQuery = window.jQuery; + + if ( typeof jQuery == 'undefined' ) + return; + + // jQuery object methods. + jQuery.extend( jQuery.fn, + /** @lends jQuery.fn */ + { + /** + * Return existing CKEditor instance for first matched element. + * Allows to easily use internal API. Doesn't return jQuery object. + * + * Raised exception if editor doesn't exist or isn't ready yet. + * + * @name jQuery.ckeditorGet + * @return CKEDITOR.editor + * @see CKEDITOR.editor + */ + ckeditorGet: function() + { + var instance = this.eq( 0 ).data( 'ckeditorInstance' ); + if ( !instance ) + throw "CKEditor not yet initialized, use ckeditor() with callback."; + return instance; + }, + /** + * Triggers creation of CKEditor in all matched elements (reduced to DIV, P and TEXTAREAs). + * Binds callback to instanceReady event of all instances. If editor is already created, than + * callback is fired right away. + * + * Mixed parameter order allowed. + * + * @param callback Function to be run on editor instance. Passed parameters: [ textarea ]. + * Callback is fiered in "this" scope being ckeditor instance and having source textarea as first param. + * + * @param config Configuration options for new instance(s) if not already created. + * See URL + * + * @example + * $( 'textarea' ).ckeditor( function( textarea ) { + * $( textarea ).val( this.getData() ) + * } ); + * + * @name jQuery.fn.ckeditor + * @return jQuery.fn + */ + ckeditor: function( callback, config ) + { + if ( !CKEDITOR.env.isCompatible ) + return this; + + if ( !jQuery.isFunction( callback )) + { + var tmp = config; + config = callback; + callback = tmp; + } + config = config || {}; + + this.filter( 'textarea, div, p' ).each( function() + { + var $element = jQuery( this ), + editor = $element.data( 'ckeditorInstance' ), + instanceLock = $element.data( '_ckeditorInstanceLock' ), + element = this; + + if ( editor && !instanceLock ) + { + if ( callback ) + callback.apply( editor, [ this ] ); + } + else if ( !instanceLock ) + { + // CREATE NEW INSTANCE + + // Handle config.autoUpdateElement inside this plugin if desired. + if ( config.autoUpdateElement + || ( typeof config.autoUpdateElement == 'undefined' && CKEDITOR.config.autoUpdateElement ) ) + { + config.autoUpdateElementJquery = true; + } + + // Always disable config.autoUpdateElement. + config.autoUpdateElement = false; + $element.data( '_ckeditorInstanceLock', true ); + + // Set instance reference in element's data. + editor = CKEDITOR.replace( element, config ); + $element.data( 'ckeditorInstance', editor ); + + // Register callback. + editor.on( 'instanceReady', function( event ) + { + var editor = event.editor; + setTimeout( function() + { + // Delay bit more if editor is still not ready. + if ( !editor.element ) + { + setTimeout( arguments.callee, 100 ); + return; + } + + // Remove this listener. + event.removeListener( 'instanceReady', this.callee ); + + // Forward setData on dataReady. + editor.on( 'dataReady', function() + { + $element.trigger( 'setData' + '.ckeditor', [ editor ] ); + }); + + // Forward getData. + editor.on( 'getData', function( event ) { + $element.trigger( 'getData' + '.ckeditor', [ editor, event.data ] ); + }, 999 ); + + // Forward destroy event. + editor.on( 'destroy', function() + { + $element.trigger( 'destroy.ckeditor', [ editor ] ); + }); + + // Integrate with form submit. + if ( editor.config.autoUpdateElementJquery && $element.is( 'textarea' ) && $element.parents( 'form' ).length ) + { + var onSubmit = function() + { + $element.ckeditor( function() + { + editor.updateElement(); + }); + }; + + // Bind to submit event. + $element.parents( 'form' ).submit( onSubmit ); + + // Bind to form-pre-serialize from jQuery Forms plugin. + $element.parents( 'form' ).bind( 'form-pre-serialize', onSubmit ); + + // Unbind when editor destroyed. + $element.bind( 'destroy.ckeditor', function() + { + $element.parents( 'form' ).unbind( 'submit', onSubmit ); + $element.parents( 'form' ).unbind( 'form-pre-serialize', onSubmit ); + }); + } + + // Garbage collect on destroy. + editor.on( 'destroy', function() + { + $element.data( 'ckeditorInstance', null ); + }); + + // Remove lock. + $element.data( '_ckeditorInstanceLock', null ); + + // Fire instanceReady event. + $element.trigger( 'instanceReady.ckeditor', [ editor ] ); + + // Run given (first) code. + if ( callback ) + callback.apply( editor, [ element ] ); + }, 0 ); + }, null, null, 9999); + } + else + { + // Editor is already during creation process, bind our code to the event. + CKEDITOR.on( 'instanceReady', function( event ) + { + var editor = event.editor; + setTimeout( function() + { + // Delay bit more if editor is still not ready. + if ( !editor.element ) + { + setTimeout( arguments.callee, 100 ); + return; + } + + if ( editor.element.$ == element ) + { + // Run given code. + if ( callback ) + callback.apply( editor, [ element ] ); + } + }, 0 ); + }, null, null, 9999); + } + }); + return this; + } + }); + + // New val() method for objects. + if ( CKEDITOR.config.jqueryOverrideVal ) + { + jQuery.fn.val = CKEDITOR.tools.override( jQuery.fn.val, function( oldValMethod ) + { + /** + * CKEditor-aware val() method. + * + * Acts same as original jQuery val(), but for textareas which have CKEditor instances binded to them, method + * returns editor's content. It also works for settings values. + * + * @param oldValMethod + * @name jQuery.fn.val + */ + return function( newValue, forceNative ) + { + var isSetter = typeof newValue != 'undefined', + result; + + this.each( function() + { + var $this = jQuery( this ), + editor = $this.data( 'ckeditorInstance' ); + + if ( !forceNative && $this.is( 'textarea' ) && editor ) + { + if ( isSetter ) + editor.setData( newValue ); + else + { + result = editor.getData(); + // break; + return null; + } + } + else + { + if ( isSetter ) + oldValMethod.call( $this, newValue ); + else + { + result = oldValMethod.call( $this ); + // break; + return null; + } + } + + return true; + }); + return isSetter ? this : result; + }; + }); + } +})(); diff --git a/htdocs/includes/ckeditor/_source/core/_bootstrap.js b/htdocs/includes/ckeditor/_source/core/_bootstrap.js new file mode 100755 index 00000000000..1fa5f5e3bb6 --- /dev/null +++ b/htdocs/includes/ckeditor/_source/core/_bootstrap.js @@ -0,0 +1,87 @@ +/* +Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @fileOverview API initialization code. + */ + +(function() +{ + // Disable HC detaction in WebKit. (#5429) + if ( CKEDITOR.env.webkit ) + { + CKEDITOR.env.hc = false; + return; + } + + // Check whether high contrast is active by creating a colored border. + var hcDetect = CKEDITOR.dom.element.createFromHtml( + '
', CKEDITOR.document ); + + hcDetect.appendTo( CKEDITOR.document.getHead() ); + + // Update CKEDITOR.env. + // Catch exception needed sometimes for FF. (#4230) + try + { + CKEDITOR.env.hc = hcDetect.getComputedStyle( 'border-top-color' ) == hcDetect.getComputedStyle( 'border-right-color' ); + } + catch (e) + { + CKEDITOR.env.hc = false; + } + + if ( CKEDITOR.env.hc ) + CKEDITOR.env.cssClass += ' cke_hc'; + + hcDetect.remove(); +})(); + +// Load core plugins. +CKEDITOR.plugins.load( CKEDITOR.config.corePlugins.split( ',' ), function() + { + CKEDITOR.status = 'loaded'; + CKEDITOR.fire( 'loaded' ); + + // Process all instances created by the "basic" implementation. + var pending = CKEDITOR._.pending; + if ( pending ) + { + delete CKEDITOR._.pending; + + for ( var i = 0 ; i < pending.length ; i++ ) + CKEDITOR.add( pending[ i ] ); + } + }); + +// Needed for IE6 to not request image (HTTP 200 or 304) for every CSS background. (#6187) +if ( CKEDITOR.env.ie ) +{ + // Remove IE mouse flickering on IE6 because of background images. + try + { + document.execCommand( 'BackgroundImageCache', false, true ); + } + catch (e) + { + // We have been reported about loading problems caused by the above + // line. For safety, let's just ignore errors. + } +} + +/** + * Indicates that CKEditor is running on a High Contrast environment. + * @name CKEDITOR.env.hc + * @example + * if ( CKEDITOR.env.hc ) + * alert( 'You're running on High Contrast mode. The editor interface will get adapted to provide you a better experience.' ); + */ + +/** + * Fired when a CKEDITOR core object is fully loaded and ready for interaction. + * @name CKEDITOR#loaded + * @event + */ diff --git a/htdocs/includes/ckeditor/_source/core/ckeditor.js b/htdocs/includes/ckeditor/_source/core/ckeditor.js new file mode 100755 index 00000000000..201c19570c2 --- /dev/null +++ b/htdocs/includes/ckeditor/_source/core/ckeditor.js @@ -0,0 +1,141 @@ +/* +Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @fileOverview Contains the third and last part of the {@link CKEDITOR} object + * definition. + */ + +// Remove the CKEDITOR.loadFullCore reference defined on ckeditor_basic. +delete CKEDITOR.loadFullCore; + +/** + * Holds references to all editor instances created. The name of the properties + * in this object correspond to instance names, and their values contains the + * {@link CKEDITOR.editor} object representing them. + * @type {Object} + * @example + * alert( CKEDITOR.instances.editor1.name ); // "editor1" + */ +CKEDITOR.instances = {}; + +/** + * The document of the window holding the CKEDITOR object. + * @type {CKEDITOR.dom.document} + * @example + * alert( CKEDITOR.document.getBody().getName() ); // "body" + */ +CKEDITOR.document = new CKEDITOR.dom.document( document ); + +/** + * Adds an editor instance to the global {@link CKEDITOR} object. This function + * is available for internal use mainly. + * @param {CKEDITOR.editor} editor The editor instance to be added. + * @example + */ +CKEDITOR.add = function( editor ) +{ + CKEDITOR.instances[ editor.name ] = editor; + + editor.on( 'focus', function() + { + if ( CKEDITOR.currentInstance != editor ) + { + CKEDITOR.currentInstance = editor; + CKEDITOR.fire( 'currentInstance' ); + } + }); + + editor.on( 'blur', function() + { + if ( CKEDITOR.currentInstance == editor ) + { + CKEDITOR.currentInstance = null; + CKEDITOR.fire( 'currentInstance' ); + } + }); +}; + +/** + * Removes an editor instance from the global {@link CKEDITOR} object. This function + * is available for internal use only. External code must use {@link CKEDITOR.editor.prototype.destroy} + * to avoid memory leaks. + * @param {CKEDITOR.editor} editor The editor instance to be removed. + * @example + */ +CKEDITOR.remove = function( editor ) +{ + delete CKEDITOR.instances[ editor.name ]; +}; + +/** + * Perform global clean up to free as much memory as possible + * when there are no instances left + */ +CKEDITOR.on( 'instanceDestroyed', function () + { + if ( CKEDITOR.tools.isEmpty( this.instances ) ) + CKEDITOR.fire( 'reset' ); + }); + +// Load the bootstrap script. +CKEDITOR.loader.load( 'core/_bootstrap' ); // @Packager.RemoveLine + +// Tri-state constants. + +/** + * Used to indicate the ON or ACTIVE state. + * @constant + * @example + */ +CKEDITOR.TRISTATE_ON = 1; + +/** + * Used to indicate the OFF or NON ACTIVE state. + * @constant + * @example + */ +CKEDITOR.TRISTATE_OFF = 2; + +/** + * Used to indicate DISABLED state. + * @constant + * @example + */ +CKEDITOR.TRISTATE_DISABLED = 0; + +/** + * The editor which is currently active (have user focus). + * @name CKEDITOR.currentInstance + * @type CKEDITOR.editor + * @see CKEDITOR#currentInstance + * @example + * function showCurrentEditorName() + * { + * if ( CKEDITOR.currentInstance ) + * alert( CKEDITOR.currentInstance.name ); + * else + * alert( 'Please focus an editor first.' ); + * } + */ + +/** + * Fired when the CKEDITOR.currentInstance object reference changes. This may + * happen when setting the focus on different editor instances in the page. + * @name CKEDITOR#currentInstance + * @event + * var editor; // Variable to hold a reference to the current editor. + * CKEDITOR.on( 'currentInstance' , function( e ) + * { + * editor = CKEDITOR.currentInstance; + * }); + */ + +/** + * Fired when the last instance has been destroyed. This event is used to perform + * global memory clean up. + * @name CKEDITOR#reset + * @event + */ diff --git a/htdocs/includes/ckeditor/_source/core/ckeditor_base.js b/htdocs/includes/ckeditor/_source/core/ckeditor_base.js new file mode 100755 index 00000000000..4ad3d87d61e --- /dev/null +++ b/htdocs/includes/ckeditor/_source/core/ckeditor_base.js @@ -0,0 +1,227 @@ +/* +Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @fileOverview Contains the first and essential part of the {@link CKEDITOR} + * object definition. + */ + +// #### Compressed Code +// Must be updated on changes in the script as well as updated in the +// ckeditor_source.js and ckeditor_basic_source.js files. + +// if(!window.CKEDITOR)window.CKEDITOR=(function(){var a={timestamp:'',version:'3.6.1',rev:'7072',_:{},status:'unloaded',basePath:(function(){var d=window.CKEDITOR_BASEPATH||'';if(!d){var e=document.getElementsByTagName('script');for(var f=0;f=0?'&':'?')+('t=')+this.timestamp;return d;}},b=window.CKEDITOR_GETURL;if(b){var c=a.getUrl;a.getUrl=function(d){return b.call(a,d)||c.call(a,d);};}return a;})(); + +// #### Raw code +// ATTENTION: read the above "Compressed Code" notes when changing this code. + +/* @Packager.RemoveLine +// Avoid having the editor code initialized twice. (#7588) +// Use CKEDITOR.dom to check whether the full ckeditor.js code has been loaded +// or just ckeditor_basic.js. +// Remove these lines when compressing manually. +if ( window.CKEDITOR && window.CKEDITOR.dom ) + return; +@Packager.RemoveLine */ + +if ( !window.CKEDITOR ) +{ + /** + * @name CKEDITOR + * @namespace This is the API entry point. The entire CKEditor code runs under this object. + * @example + */ + window.CKEDITOR = (function() + { + var CKEDITOR = + /** @lends CKEDITOR */ + { + + /** + * A constant string unique for each release of CKEditor. Its value + * is used, by default, to build the URL for all resources loaded + * by the editor code, guaranteeing clean cache results when + * upgrading. + * @type String + * @example + * alert( CKEDITOR.timestamp ); // e.g. '87dm' + */ + // The production implementation contains a fixed timestamp, unique + // for each release and generated by the releaser. + // (Base 36 value of each component of YYMMDDHH - 4 chars total - e.g. 87bm == 08071122) + timestamp : 'B5GJ5GG', + + /** + * Contains the CKEditor version number. + * @type String + * @example + * alert( CKEDITOR.version ); // e.g. 'CKEditor 3.4.1' + */ + version : '3.6.1', + + /** + * Contains the CKEditor revision number. + * The revision number is incremented automatically, following each + * modification to the CKEditor source code. + * @type String + * @example + * alert( CKEDITOR.revision ); // e.g. '3975' + */ + revision : '7072', + + /** + * Private object used to hold core stuff. It should not be used outside of + * the API code as properties defined here may change at any time + * without notice. + * @private + */ + _ : {}, + + /** + * Indicates the API loading status. The following statuses are available: + *
    + *
  • unloaded: the API is not yet loaded.
  • + *
  • basic_loaded: the basic API features are available.
  • + *
  • basic_ready: the basic API is ready to load the full core code.
  • + *
  • loading: the full API is being loaded.
  • + *
  • loaded: the API can be fully used.
  • + *
+ * @type String + * @example + * if ( CKEDITOR.status == 'loaded' ) + * { + * // The API can now be fully used. + * } + */ + status : 'unloaded', + + /** + * Contains the full URL for the CKEditor installation directory. + * It is possible to manually provide the base path by setting a + * global variable named CKEDITOR_BASEPATH. This global variable + * must be set before the editor script loading. + * @type String + * @example + * alert( CKEDITOR.basePath ); // "http://www.example.com/ckeditor/" (e.g.) + */ + basePath : (function() + { + // ATTENTION: fixes to this code must be ported to + // var basePath in "core/loader.js". + + // Find out the editor directory path, based on its ")' ); + } + } + + return $ && new CKEDITOR.dom.document( $.contentWindow.document ); + }, + + /** + * Copy all the attributes from one node to the other, kinda like a clone + * skipAttributes is an object with the attributes that must NOT be copied. + * @param {CKEDITOR.dom.element} dest The destination element. + * @param {Object} skipAttributes A dictionary of attributes to skip. + * @example + */ + copyAttributes : function( dest, skipAttributes ) + { + var attributes = this.$.attributes; + skipAttributes = skipAttributes || {}; + + for ( var n = 0 ; n < attributes.length ; n++ ) + { + var attribute = attributes[n]; + + // Lowercase attribute name hard rule is broken for + // some attribute on IE, e.g. CHECKED. + var attrName = attribute.nodeName.toLowerCase(), + attrValue; + + // We can set the type only once, so do it with the proper value, not copying it. + if ( attrName in skipAttributes ) + continue; + + if ( attrName == 'checked' && ( attrValue = this.getAttribute( attrName ) ) ) + dest.setAttribute( attrName, attrValue ); + // IE BUG: value attribute is never specified even if it exists. + else if ( attribute.specified || + ( CKEDITOR.env.ie && attribute.nodeValue && attrName == 'value' ) ) + { + attrValue = this.getAttribute( attrName ); + if ( attrValue === null ) + attrValue = attribute.nodeValue; + + dest.setAttribute( attrName, attrValue ); + } + } + + // The style: + if ( this.$.style.cssText !== '' ) + dest.$.style.cssText = this.$.style.cssText; + }, + + /** + * Changes the tag name of the current element. + * @param {String} newTag The new tag for the element. + */ + renameNode : function( newTag ) + { + // If it's already correct exit here. + if ( this.getName() == newTag ) + return; + + var doc = this.getDocument(); + + // Create the new node. + var newNode = new CKEDITOR.dom.element( newTag, doc ); + + // Copy all attributes. + this.copyAttributes( newNode ); + + // Move children to the new node. + this.moveChildren( newNode ); + + // Replace the node. + this.getParent() && this.$.parentNode.replaceChild( newNode.$, this.$ ); + newNode.$[ 'data-cke-expando' ] = this.$[ 'data-cke-expando' ]; + this.$ = newNode.$; + }, + + /** + * Gets a DOM tree descendant under the current node. + * @param {Array|Number} indices The child index or array of child indices under the node. + * @returns {CKEDITOR.dom.node} The specified DOM child under the current node. Null if child does not exist. + * @example + * var strong = p.getChild(0); + */ + getChild : function( indices ) + { + var rawNode = this.$; + + if ( !indices.slice ) + rawNode = rawNode.childNodes[ indices ]; + else + { + while ( indices.length > 0 && rawNode ) + rawNode = rawNode.childNodes[ indices.shift() ]; + } + + return rawNode ? new CKEDITOR.dom.node( rawNode ) : null; + }, + + getChildCount : function() + { + return this.$.childNodes.length; + }, + + disableContextMenu : function() + { + this.on( 'contextmenu', function( event ) + { + // Cancel the browser context menu. + if ( !event.data.getTarget().hasClass( 'cke_enable_context_menu' ) ) + event.data.preventDefault(); + } ); + }, + + /** + * Gets element's direction. Supports both CSS 'direction' prop and 'dir' attr. + */ + getDirection : function( useComputed ) + { + return useComputed ? + this.getComputedStyle( 'direction' ) + // Webkit: offline element returns empty direction (#8053). + || this.getDirection() + || this.getDocument().$.dir + || this.getDocument().getBody().getDirection( 1 ) + : this.getStyle( 'direction' ) || this.getAttribute( 'dir' ); + }, + + /** + * Gets, sets and removes custom data to be stored as HTML5 data-* attributes. + * @param {String} name The name of the attribute, excluding the 'data-' part. + * @param {String} [value] The value to set. If set to false, the attribute will be removed. + * @example + * element.data( 'extra-info', 'test' ); // appended the attribute data-extra-info="test" to the element + * alert( element.data( 'extra-info' ) ); // "test" + * element.data( 'extra-info', false ); // remove the data-extra-info attribute from the element + */ + data : function ( name, value ) + { + name = 'data-' + name; + if ( value === undefined ) + return this.getAttribute( name ); + else if ( value === false ) + this.removeAttribute( name ); + else + this.setAttribute( name, value ); + + return null; + } + }); + +( function() +{ + var sides = { + width : [ "border-left-width", "border-right-width","padding-left", "padding-right" ], + height : [ "border-top-width", "border-bottom-width", "padding-top", "padding-bottom" ] + }; + + function marginAndPaddingSize( type ) + { + var adjustment = 0; + for ( var i = 0, len = sides[ type ].length; i < len; i++ ) + adjustment += parseInt( this.getComputedStyle( sides [ type ][ i ] ) || 0, 10 ) || 0; + return adjustment; + } + + /** + * Sets the element size considering the box model. + * @name CKEDITOR.dom.element.prototype.setSize + * @function + * @param {String} type The dimension to set. It accepts "width" and "height". + * @param {Number} size The length unit in px. + * @param {Boolean} isBorderBox Apply the size based on the border box model. + */ + CKEDITOR.dom.element.prototype.setSize = function( type, size, isBorderBox ) + { + if ( typeof size == 'number' ) + { + if ( isBorderBox && !( CKEDITOR.env.ie && CKEDITOR.env.quirks ) ) + size -= marginAndPaddingSize.call( this, type ); + + this.setStyle( type, size + 'px' ); + } + }; + + /** + * Gets the element size, possibly considering the box model. + * @name CKEDITOR.dom.element.prototype.getSize + * @function + * @param {String} type The dimension to get. It accepts "width" and "height". + * @param {Boolean} isBorderBox Get the size based on the border box model. + */ + CKEDITOR.dom.element.prototype.getSize = function( type, isBorderBox ) + { + var size = Math.max( this.$[ 'offset' + CKEDITOR.tools.capitalize( type ) ], + this.$[ 'client' + CKEDITOR.tools.capitalize( type ) ] ) || 0; + + if ( isBorderBox ) + size -= marginAndPaddingSize.call( this, type ); + + return size; + }; +})(); diff --git a/htdocs/includes/ckeditor/_source/core/dom/elementpath.js b/htdocs/includes/ckeditor/_source/core/dom/elementpath.js new file mode 100755 index 00000000000..6dcd4d47a33 --- /dev/null +++ b/htdocs/includes/ckeditor/_source/core/dom/elementpath.js @@ -0,0 +1,119 @@ +/* +Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +(function() +{ + // Elements that may be considered the "Block boundary" in an element path. + var pathBlockElements = { address:1,blockquote:1,dl:1,h1:1,h2:1,h3:1,h4:1,h5:1,h6:1,p:1,pre:1,li:1,dt:1,dd:1, legend:1,caption:1 }; + + // Elements that may be considered the "Block limit" in an element path. + var pathBlockLimitElements = { body:1,div:1,table:1,tbody:1,tr:1,td:1,th:1,form:1,fieldset:1 }; + + // Check if an element contains any block element. + var checkHasBlock = function( element ) + { + var childNodes = element.getChildren(); + + for ( var i = 0, count = childNodes.count() ; i < count ; i++ ) + { + var child = childNodes.getItem( i ); + + if ( child.type == CKEDITOR.NODE_ELEMENT && CKEDITOR.dtd.$block[ child.getName() ] ) + return true; + } + + return false; + }; + + /** + * @class + */ + CKEDITOR.dom.elementPath = function( lastNode ) + { + var block = null; + var blockLimit = null; + var elements = []; + + var e = lastNode; + + while ( e ) + { + if ( e.type == CKEDITOR.NODE_ELEMENT ) + { + if ( !this.lastElement ) + this.lastElement = e; + + var elementName = e.getName(); + if ( CKEDITOR.env.ie && e.$.scopeName != 'HTML' ) + elementName = e.$.scopeName.toLowerCase() + ':' + elementName; + + if ( !blockLimit ) + { + if ( !block && pathBlockElements[ elementName ] ) + block = e; + + if ( pathBlockLimitElements[ elementName ] ) + { + // DIV is considered the Block, if no block is available (#525) + // and if it doesn't contain other blocks. + if ( !block && elementName == 'div' && !checkHasBlock( e ) ) + block = e; + else + blockLimit = e; + } + } + + elements.push( e ); + + if ( elementName == 'body' ) + break; + } + e = e.getParent(); + } + + this.block = block; + this.blockLimit = blockLimit; + this.elements = elements; + }; +})(); + +CKEDITOR.dom.elementPath.prototype = +{ + /** + * Compares this element path with another one. + * @param {CKEDITOR.dom.elementPath} otherPath The elementPath object to be + * compared with this one. + * @returns {Boolean} "true" if the paths are equal, containing the same + * number of elements and the same elements in the same order. + */ + compare : function( otherPath ) + { + var thisElements = this.elements; + var otherElements = otherPath && otherPath.elements; + + if ( !otherElements || thisElements.length != otherElements.length ) + return false; + + for ( var i = 0 ; i < thisElements.length ; i++ ) + { + if ( !thisElements[ i ].equals( otherElements[ i ] ) ) + return false; + } + + return true; + }, + + contains : function( tagNames ) + { + var elements = this.elements; + for ( var i = 0 ; i < elements.length ; i++ ) + { + if ( elements[ i ].getName() in tagNames ) + return elements[ i ]; + } + + return null; + } +}; diff --git a/htdocs/includes/ckeditor/_source/core/dom/event.js b/htdocs/includes/ckeditor/_source/core/dom/event.js new file mode 100755 index 00000000000..618a11035c1 --- /dev/null +++ b/htdocs/includes/ckeditor/_source/core/dom/event.js @@ -0,0 +1,145 @@ +/* +Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @fileOverview Defines the {@link CKEDITOR.dom.event} class, which + * represents the a native DOM event object. + */ + +/** + * Represents a native DOM event object. + * @constructor + * @param {Object} domEvent A native DOM event object. + * @example + */ +CKEDITOR.dom.event = function( domEvent ) +{ + /** + * The native DOM event object represented by this class instance. + * @type Object + * @example + */ + this.$ = domEvent; +}; + +CKEDITOR.dom.event.prototype = +{ + /** + * Gets the key code associated to the event. + * @returns {Number} The key code. + * @example + * alert( event.getKey() ); "65" is "a" has been pressed + */ + getKey : function() + { + return this.$.keyCode || this.$.which; + }, + + /** + * Gets a number represeting the combination of the keys pressed during the + * event. It is the sum with the current key code and the {@link CKEDITOR.CTRL}, + * {@link CKEDITOR.SHIFT} and {@link CKEDITOR.ALT} constants. + * @returns {Number} The number representing the keys combination. + * @example + * alert( event.getKeystroke() == 65 ); // "a" key + * alert( event.getKeystroke() == CKEDITOR.CTRL + 65 ); // CTRL + "a" key + * alert( event.getKeystroke() == CKEDITOR.CTRL + CKEDITOR.SHIFT + 65 ); // CTRL + SHIFT + "a" key + */ + getKeystroke : function() + { + var keystroke = this.getKey(); + + if ( this.$.ctrlKey || this.$.metaKey ) + keystroke += CKEDITOR.CTRL; + + if ( this.$.shiftKey ) + keystroke += CKEDITOR.SHIFT; + + if ( this.$.altKey ) + keystroke += CKEDITOR.ALT; + + return keystroke; + }, + + /** + * Prevents the original behavior of the event to happen. It can optionally + * stop propagating the event in the event chain. + * @param {Boolean} [stopPropagation] Stop propagating this event in the + * event chain. + * @example + * var element = CKEDITOR.document.getById( 'myElement' ); + * element.on( 'click', function( ev ) + * { + * // The DOM event object is passed by the "data" property. + * var domEvent = ev.data; + * // Prevent the click to chave any effect in the element. + * domEvent.preventDefault(); + * }); + */ + preventDefault : function( stopPropagation ) + { + var $ = this.$; + if ( $.preventDefault ) + $.preventDefault(); + else + $.returnValue = false; + + if ( stopPropagation ) + this.stopPropagation(); + }, + + stopPropagation : function() + { + var $ = this.$; + if ( $.stopPropagation ) + $.stopPropagation(); + else + $.cancelBubble = true; + }, + + /** + * Returns the DOM node where the event was targeted to. + * @returns {CKEDITOR.dom.node} The target DOM node. + * @example + * var element = CKEDITOR.document.getById( 'myElement' ); + * element.on( 'click', function( ev ) + * { + * // The DOM event object is passed by the "data" property. + * var domEvent = ev.data; + * // Add a CSS class to the event target. + * domEvent.getTarget().addClass( 'clicked' ); + * }); + */ + + getTarget : function() + { + var rawNode = this.$.target || this.$.srcElement; + return rawNode ? new CKEDITOR.dom.node( rawNode ) : null; + } +}; + +// For the followind constants, we need to go over the Unicode boundaries +// (0x10FFFF) to avoid collision. + +/** + * CTRL key (0x110000). + * @constant + * @example + */ +CKEDITOR.CTRL = 0x110000; + +/** + * SHIFT key (0x220000). + * @constant + * @example + */ +CKEDITOR.SHIFT = 0x220000; + +/** + * ALT key (0x440000). + * @constant + * @example + */ +CKEDITOR.ALT = 0x440000; diff --git a/htdocs/includes/ckeditor/_source/core/dom/node.js b/htdocs/includes/ckeditor/_source/core/dom/node.js new file mode 100755 index 00000000000..293ce2304c3 --- /dev/null +++ b/htdocs/includes/ckeditor/_source/core/dom/node.js @@ -0,0 +1,695 @@ +/* +Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @fileOverview Defines the {@link CKEDITOR.dom.node} class which is the base + * class for classes that represent DOM nodes. + */ + +/** + * Base class for classes representing DOM nodes. This constructor may return + * an instance of a class that inherits from this class, like + * {@link CKEDITOR.dom.element} or {@link CKEDITOR.dom.text}. + * @augments CKEDITOR.dom.domObject + * @param {Object} domNode A native DOM node. + * @constructor + * @see CKEDITOR.dom.element + * @see CKEDITOR.dom.text + * @example + */ +CKEDITOR.dom.node = function( domNode ) +{ + if ( domNode ) + { + switch ( domNode.nodeType ) + { + // Safari don't consider document as element node type. (#3389) + case CKEDITOR.NODE_DOCUMENT : + return new CKEDITOR.dom.document( domNode ); + + case CKEDITOR.NODE_ELEMENT : + return new CKEDITOR.dom.element( domNode ); + + case CKEDITOR.NODE_TEXT : + return new CKEDITOR.dom.text( domNode ); + } + + // Call the base constructor. + CKEDITOR.dom.domObject.call( this, domNode ); + } + + return this; +}; + +CKEDITOR.dom.node.prototype = new CKEDITOR.dom.domObject(); + +/** + * Element node type. + * @constant + * @example + */ +CKEDITOR.NODE_ELEMENT = 1; + +/** + * Document node type. + * @constant + * @example + */ +CKEDITOR.NODE_DOCUMENT = 9; + +/** + * Text node type. + * @constant + * @example + */ +CKEDITOR.NODE_TEXT = 3; + +/** + * Comment node type. + * @constant + * @example + */ +CKEDITOR.NODE_COMMENT = 8; + +CKEDITOR.NODE_DOCUMENT_FRAGMENT = 11; + +CKEDITOR.POSITION_IDENTICAL = 0; +CKEDITOR.POSITION_DISCONNECTED = 1; +CKEDITOR.POSITION_FOLLOWING = 2; +CKEDITOR.POSITION_PRECEDING = 4; +CKEDITOR.POSITION_IS_CONTAINED = 8; +CKEDITOR.POSITION_CONTAINS = 16; + +CKEDITOR.tools.extend( CKEDITOR.dom.node.prototype, + /** @lends CKEDITOR.dom.node.prototype */ + { + /** + * Makes this node a child of another element. + * @param {CKEDITOR.dom.element} element The target element to which + * this node will be appended. + * @returns {CKEDITOR.dom.element} The target element. + * @example + * var p = new CKEDITOR.dom.element( 'p' ); + * var strong = new CKEDITOR.dom.element( 'strong' ); + * strong.appendTo( p ); + * + * // result: "<p><strong></strong></p>" + */ + appendTo : function( element, toStart ) + { + element.append( this, toStart ); + return element; + }, + + clone : function( includeChildren, cloneId ) + { + var $clone = this.$.cloneNode( includeChildren ); + + var removeIds = function( node ) + { + if ( node.nodeType != CKEDITOR.NODE_ELEMENT ) + return; + + if ( !cloneId ) + node.removeAttribute( 'id', false ); + node.removeAttribute( 'data-cke-expando', false ); + + if ( includeChildren ) + { + var childs = node.childNodes; + for ( var i=0; i < childs.length; i++ ) + removeIds( childs[ i ] ); + } + }; + + // The "id" attribute should never be cloned to avoid duplication. + removeIds( $clone ); + + return new CKEDITOR.dom.node( $clone ); + }, + + hasPrevious : function() + { + return !!this.$.previousSibling; + }, + + hasNext : function() + { + return !!this.$.nextSibling; + }, + + /** + * Inserts this element after a node. + * @param {CKEDITOR.dom.node} node The node that will precede this element. + * @returns {CKEDITOR.dom.node} The node preceding this one after + * insertion. + * @example + * var em = new CKEDITOR.dom.element( 'em' ); + * var strong = new CKEDITOR.dom.element( 'strong' ); + * strong.insertAfter( em ); + * + * // result: "<em></em><strong></strong>" + */ + insertAfter : function( node ) + { + node.$.parentNode.insertBefore( this.$, node.$.nextSibling ); + return node; + }, + + /** + * Inserts this element before a node. + * @param {CKEDITOR.dom.node} node The node that will succeed this element. + * @returns {CKEDITOR.dom.node} The node being inserted. + * @example + * var em = new CKEDITOR.dom.element( 'em' ); + * var strong = new CKEDITOR.dom.element( 'strong' ); + * strong.insertBefore( em ); + * + * // result: "<strong></strong><em></em>" + */ + insertBefore : function( node ) + { + node.$.parentNode.insertBefore( this.$, node.$ ); + return node; + }, + + insertBeforeMe : function( node ) + { + this.$.parentNode.insertBefore( node.$, this.$ ); + return node; + }, + + /** + * Retrieves a uniquely identifiable tree address for this node. + * The tree address returned is an array of integers, with each integer + * indicating a child index of a DOM node, starting from + * document.documentElement. + * + * For example, assuming <body> is the second child + * of <html> (<head> being the first), + * and we would like to address the third child under the + * fourth child of <body>, the tree address returned would be: + * [1, 3, 2] + * + * The tree address cannot be used for finding back the DOM tree node once + * the DOM tree structure has been modified. + */ + getAddress : function( normalized ) + { + var address = []; + var $documentElement = this.getDocument().$.documentElement; + var node = this.$; + + while ( node && node != $documentElement ) + { + var parentNode = node.parentNode; + + if ( parentNode ) + { + // Get the node index. For performance, call getIndex + // directly, instead of creating a new node object. + address.unshift( this.getIndex.call( { $ : node }, normalized ) ); + } + + node = parentNode; + } + + return address; + }, + + /** + * Gets the document containing this element. + * @returns {CKEDITOR.dom.document} The document. + * @example + * var element = CKEDITOR.document.getById( 'example' ); + * alert( element.getDocument().equals( CKEDITOR.document ) ); // "true" + */ + getDocument : function() + { + return new CKEDITOR.dom.document( this.$.ownerDocument || this.$.parentNode.ownerDocument ); + }, + + getIndex : function( normalized ) + { + // Attention: getAddress depends on this.$ + + var current = this.$, + index = 0; + + while ( ( current = current.previousSibling ) ) + { + // When normalizing, do not count it if this is an + // empty text node or if it's a text node following another one. + if ( normalized && current.nodeType == 3 && + ( !current.nodeValue.length || + ( current.previousSibling && current.previousSibling.nodeType == 3 ) ) ) + { + continue; + } + + index++; + } + + return index; + }, + + getNextSourceNode : function( startFromSibling, nodeType, guard ) + { + // If "guard" is a node, transform it in a function. + if ( guard && !guard.call ) + { + var guardNode = guard; + guard = function( node ) + { + return !node.equals( guardNode ); + }; + } + + var node = ( !startFromSibling && this.getFirst && this.getFirst() ), + parent; + + // Guarding when we're skipping the current element( no children or 'startFromSibling' ). + // send the 'moving out' signal even we don't actually dive into. + if ( !node ) + { + if ( this.type == CKEDITOR.NODE_ELEMENT && guard && guard( this, true ) === false ) + return null; + node = this.getNext(); + } + + while ( !node && ( parent = ( parent || this ).getParent() ) ) + { + // The guard check sends the "true" paramenter to indicate that + // we are moving "out" of the element. + if ( guard && guard( parent, true ) === false ) + return null; + + node = parent.getNext(); + } + + if ( !node ) + return null; + + if ( guard && guard( node ) === false ) + return null; + + if ( nodeType && nodeType != node.type ) + return node.getNextSourceNode( false, nodeType, guard ); + + return node; + }, + + getPreviousSourceNode : function( startFromSibling, nodeType, guard ) + { + if ( guard && !guard.call ) + { + var guardNode = guard; + guard = function( node ) + { + return !node.equals( guardNode ); + }; + } + + var node = ( !startFromSibling && this.getLast && this.getLast() ), + parent; + + // Guarding when we're skipping the current element( no children or 'startFromSibling' ). + // send the 'moving out' signal even we don't actually dive into. + if ( !node ) + { + if ( this.type == CKEDITOR.NODE_ELEMENT && guard && guard( this, true ) === false ) + return null; + node = this.getPrevious(); + } + + while ( !node && ( parent = ( parent || this ).getParent() ) ) + { + // The guard check sends the "true" paramenter to indicate that + // we are moving "out" of the element. + if ( guard && guard( parent, true ) === false ) + return null; + + node = parent.getPrevious(); + } + + if ( !node ) + return null; + + if ( guard && guard( node ) === false ) + return null; + + if ( nodeType && node.type != nodeType ) + return node.getPreviousSourceNode( false, nodeType, guard ); + + return node; + }, + + getPrevious : function( evaluator ) + { + var previous = this.$, retval; + do + { + previous = previous.previousSibling; + retval = previous && new CKEDITOR.dom.node( previous ); + } + while ( retval && evaluator && !evaluator( retval ) ) + return retval; + }, + + /** + * Gets the node that follows this element in its parent's child list. + * @param {Function} evaluator Filtering the result node. + * @returns {CKEDITOR.dom.node} The next node or null if not available. + * @example + * var element = CKEDITOR.dom.element.createFromHtml( '<div><b>Example</b> <i>next</i></div>' ); + * var first = element.getFirst().getNext(); + * alert( first.getName() ); // "i" + */ + getNext : function( evaluator ) + { + var next = this.$, retval; + do + { + next = next.nextSibling; + retval = next && new CKEDITOR.dom.node( next ); + } + while ( retval && evaluator && !evaluator( retval ) ) + return retval; + }, + + /** + * Gets the parent element for this node. + * @returns {CKEDITOR.dom.element} The parent element. + * @example + * var node = editor.document.getBody().getFirst(); + * var parent = node.getParent(); + * alert( node.getName() ); // "body" + */ + getParent : function() + { + var parent = this.$.parentNode; + return ( parent && parent.nodeType == 1 ) ? new CKEDITOR.dom.node( parent ) : null; + }, + + getParents : function( closerFirst ) + { + var node = this; + var parents = []; + + do + { + parents[ closerFirst ? 'push' : 'unshift' ]( node ); + } + while ( ( node = node.getParent() ) ) + + return parents; + }, + + getCommonAncestor : function( node ) + { + if ( node.equals( this ) ) + return this; + + if ( node.contains && node.contains( this ) ) + return node; + + var start = this.contains ? this : this.getParent(); + + do + { + if ( start.contains( node ) ) + return start; + } + while ( ( start = start.getParent() ) ); + + return null; + }, + + getPosition : function( otherNode ) + { + var $ = this.$; + var $other = otherNode.$; + + if ( $.compareDocumentPosition ) + return $.compareDocumentPosition( $other ); + + // IE and Safari have no support for compareDocumentPosition. + + if ( $ == $other ) + return CKEDITOR.POSITION_IDENTICAL; + + // Only element nodes support contains and sourceIndex. + if ( this.type == CKEDITOR.NODE_ELEMENT && otherNode.type == CKEDITOR.NODE_ELEMENT ) + { + if ( $.contains ) + { + if ( $.contains( $other ) ) + return CKEDITOR.POSITION_CONTAINS + CKEDITOR.POSITION_PRECEDING; + + if ( $other.contains( $ ) ) + return CKEDITOR.POSITION_IS_CONTAINED + CKEDITOR.POSITION_FOLLOWING; + } + + if ( 'sourceIndex' in $ ) + { + return ( $.sourceIndex < 0 || $other.sourceIndex < 0 ) ? CKEDITOR.POSITION_DISCONNECTED : + ( $.sourceIndex < $other.sourceIndex ) ? CKEDITOR.POSITION_PRECEDING : + CKEDITOR.POSITION_FOLLOWING; + } + } + + // For nodes that don't support compareDocumentPosition, contains + // or sourceIndex, their "address" is compared. + + var addressOfThis = this.getAddress(), + addressOfOther = otherNode.getAddress(), + minLevel = Math.min( addressOfThis.length, addressOfOther.length ); + + // Determinate preceed/follow relationship. + for ( var i = 0 ; i <= minLevel - 1 ; i++ ) + { + if ( addressOfThis[ i ] != addressOfOther[ i ] ) + { + if ( i < minLevel ) + { + return addressOfThis[ i ] < addressOfOther[ i ] ? + CKEDITOR.POSITION_PRECEDING : CKEDITOR.POSITION_FOLLOWING; + } + break; + } + } + + // Determinate contains/contained relationship. + return ( addressOfThis.length < addressOfOther.length ) ? + CKEDITOR.POSITION_CONTAINS + CKEDITOR.POSITION_PRECEDING : + CKEDITOR.POSITION_IS_CONTAINED + CKEDITOR.POSITION_FOLLOWING; + }, + + /** + * Gets the closest ancestor node of this node, specified by its name. + * @param {String} reference The name of the ancestor node to search or + * an object with the node names to search for. + * @param {Boolean} [includeSelf] Whether to include the current + * node in the search. + * @returns {CKEDITOR.dom.node} The located ancestor node or null if not found. + * @since 3.6.1 + * @example + * // Suppose we have the following HTML structure: + * // <div id="outer"><div id="inner"><p><b>Some text</b></p></div></div> + * // If node == <b> + * ascendant = node.getAscendant( 'div' ); // ascendant == <div id="inner"> + * ascendant = node.getAscendant( 'b' ); // ascendant == null + * ascendant = node.getAscendant( 'b', true ); // ascendant == <b> + * ascendant = node.getAscendant( { div: 1, p: 1} ); // Searches for the first 'div' or 'p': ascendant == <div id="inner"> + */ + getAscendant : function( reference, includeSelf ) + { + var $ = this.$, + name; + + if ( !includeSelf ) + $ = $.parentNode; + + while ( $ ) + { + if ( $.nodeName && ( name = $.nodeName.toLowerCase(), ( typeof reference == 'string' ? name == reference : name in reference ) ) ) + return new CKEDITOR.dom.node( $ ); + + $ = $.parentNode; + } + return null; + }, + + hasAscendant : function( name, includeSelf ) + { + var $ = this.$; + + if ( !includeSelf ) + $ = $.parentNode; + + while ( $ ) + { + if ( $.nodeName && $.nodeName.toLowerCase() == name ) + return true; + + $ = $.parentNode; + } + return false; + }, + + move : function( target, toStart ) + { + target.append( this.remove(), toStart ); + }, + + /** + * Removes this node from the document DOM. + * @param {Boolean} [preserveChildren] Indicates that the children + * elements must remain in the document, removing only the outer + * tags. + * @example + * var element = CKEDITOR.dom.element.getById( 'MyElement' ); + * element.remove(); + */ + remove : function( preserveChildren ) + { + var $ = this.$; + var parent = $.parentNode; + + if ( parent ) + { + if ( preserveChildren ) + { + // Move all children before the node. + for ( var child ; ( child = $.firstChild ) ; ) + { + parent.insertBefore( $.removeChild( child ), $ ); + } + } + + parent.removeChild( $ ); + } + + return this; + }, + + replace : function( nodeToReplace ) + { + this.insertBefore( nodeToReplace ); + nodeToReplace.remove(); + }, + + trim : function() + { + this.ltrim(); + this.rtrim(); + }, + + ltrim : function() + { + var child; + while ( this.getFirst && ( child = this.getFirst() ) ) + { + if ( child.type == CKEDITOR.NODE_TEXT ) + { + var trimmed = CKEDITOR.tools.ltrim( child.getText() ), + originalLength = child.getLength(); + + if ( !trimmed ) + { + child.remove(); + continue; + } + else if ( trimmed.length < originalLength ) + { + child.split( originalLength - trimmed.length ); + + // IE BUG: child.remove() may raise JavaScript errors here. (#81) + this.$.removeChild( this.$.firstChild ); + } + } + break; + } + }, + + rtrim : function() + { + var child; + while ( this.getLast && ( child = this.getLast() ) ) + { + if ( child.type == CKEDITOR.NODE_TEXT ) + { + var trimmed = CKEDITOR.tools.rtrim( child.getText() ), + originalLength = child.getLength(); + + if ( !trimmed ) + { + child.remove(); + continue; + } + else if ( trimmed.length < originalLength ) + { + child.split( trimmed.length ); + + // IE BUG: child.getNext().remove() may raise JavaScript errors here. + // (#81) + this.$.lastChild.parentNode.removeChild( this.$.lastChild ); + } + } + break; + } + + if ( !CKEDITOR.env.ie && !CKEDITOR.env.opera ) + { + child = this.$.lastChild; + + if ( child && child.type == 1 && child.nodeName.toLowerCase() == 'br' ) + { + // Use "eChildNode.parentNode" instead of "node" to avoid IE bug (#324). + child.parentNode.removeChild( child ) ; + } + } + }, + + /** + * Checks if this node is read-only (should not be changed). Additionally + * it returns the element that defines the read-only state of this node + * (if present). It may be the node itself or any of its parent + * nodes. + * @returns {CKEDITOR.dom.element|Boolean} An element containing + * read-only attributes or "false" if none is found. + * @since 3.5 + * @example + * // For the following HTML: + * // <div contenteditable="false">Some <b>text</b></div> + * + * // If "ele" is the above <div> + * ele.isReadOnly(); // the <div> element + * + * // If "ele" is the above <b> + * ele.isReadOnly(); // the <div> element + */ + isReadOnly : function() + { + var current = this; + while( current ) + { + if ( current.type == CKEDITOR.NODE_ELEMENT ) + { + if ( current.is( 'body' ) || !!current.data( 'cke-editable' ) ) + break; + + if ( current.getAttribute( 'contentEditable' ) == 'false' ) + return current; + else if ( current.getAttribute( 'contentEditable' ) == 'true' ) + break; + } + current = current.getParent(); + } + + return false; + } + } +); diff --git a/htdocs/includes/ckeditor/_source/core/dom/nodelist.js b/htdocs/includes/ckeditor/_source/core/dom/nodelist.js new file mode 100755 index 00000000000..42efafc70fb --- /dev/null +++ b/htdocs/includes/ckeditor/_source/core/dom/nodelist.js @@ -0,0 +1,26 @@ +/* +Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @class + */ +CKEDITOR.dom.nodeList = function( nativeList ) +{ + this.$ = nativeList; +}; + +CKEDITOR.dom.nodeList.prototype = +{ + count : function() + { + return this.$.length; + }, + + getItem : function( index ) + { + var $node = this.$[ index ]; + return $node ? new CKEDITOR.dom.node( $node ) : null; + } +}; diff --git a/htdocs/includes/ckeditor/_source/core/dom/range.js b/htdocs/includes/ckeditor/_source/core/dom/range.js new file mode 100755 index 00000000000..477197ef053 --- /dev/null +++ b/htdocs/includes/ckeditor/_source/core/dom/range.js @@ -0,0 +1,2032 @@ +/* +Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * Creates a CKEDITOR.dom.range instance that can be used inside a specific + * DOM Document. + * @class Represents a delimited piece of content in a DOM Document. + * It is contiguous in the sense that it can be characterized as selecting all + * of the content between a pair of boundary-points.
+ *
+ * This class shares much of the W3C + * Document Object Model Range + * ideas and features, adding several range manipulation tools to it, but it's + * not intended to be compatible with it. + * @param {CKEDITOR.dom.document} document The document into which the range + * features will be available. + * @example + * // Create a range for the entire contents of the editor document body. + * var range = new CKEDITOR.dom.range( editor.document ); + * range.selectNodeContents( editor.document.getBody() ); + * // Delete the contents. + * range.deleteContents(); + */ +CKEDITOR.dom.range = function( document ) +{ + /** + * Node within which the range begins. + * @type {CKEDITOR.NODE_ELEMENT|CKEDITOR.NODE_TEXT} + * @example + * var range = new CKEDITOR.dom.range( editor.document ); + * range.selectNodeContents( editor.document.getBody() ); + * alert( range.startContainer.getName() ); // "body" + */ + this.startContainer = null; + + /** + * Offset within the starting node of the range. + * @type {Number} + * @example + * var range = new CKEDITOR.dom.range( editor.document ); + * range.selectNodeContents( editor.document.getBody() ); + * alert( range.startOffset ); // "0" + */ + this.startOffset = null; + + /** + * Node within which the range ends. + * @type {CKEDITOR.NODE_ELEMENT|CKEDITOR.NODE_TEXT} + * @example + * var range = new CKEDITOR.dom.range( editor.document ); + * range.selectNodeContents( editor.document.getBody() ); + * alert( range.endContainer.getName() ); // "body" + */ + this.endContainer = null; + + /** + * Offset within the ending node of the range. + * @type {Number} + * @example + * var range = new CKEDITOR.dom.range( editor.document ); + * range.selectNodeContents( editor.document.getBody() ); + * alert( range.endOffset ); // == editor.document.getBody().getChildCount() + */ + this.endOffset = null; + + /** + * Indicates that this is a collapsed range. A collapsed range has it's + * start and end boudaries at the very same point so nothing is contained + * in it. + * @example + * var range = new CKEDITOR.dom.range( editor.document ); + * range.selectNodeContents( editor.document.getBody() ); + * alert( range.collapsed ); // "false" + * range.collapse(); + * alert( range.collapsed ); // "true" + */ + this.collapsed = true; + + /** + * The document within which the range can be used. + * @type {CKEDITOR.dom.document} + * @example + * // Selects the body contents of the range document. + * range.selectNodeContents( range.document.getBody() ); + */ + this.document = document; +}; + +(function() +{ + // Updates the "collapsed" property for the given range object. + var updateCollapsed = function( range ) + { + range.collapsed = ( + range.startContainer && + range.endContainer && + range.startContainer.equals( range.endContainer ) && + range.startOffset == range.endOffset ); + }; + + // This is a shared function used to delete, extract and clone the range + // contents. + // V2 + var execContentsAction = function( range, action, docFrag, mergeThen ) + { + range.optimizeBookmark(); + + var startNode = range.startContainer; + var endNode = range.endContainer; + + var startOffset = range.startOffset; + var endOffset = range.endOffset; + + var removeStartNode; + var removeEndNode; + + // For text containers, we must simply split the node and point to the + // second part. The removal will be handled by the rest of the code . + if ( endNode.type == CKEDITOR.NODE_TEXT ) + endNode = endNode.split( endOffset ); + else + { + // If the end container has children and the offset is pointing + // to a child, then we should start from it. + if ( endNode.getChildCount() > 0 ) + { + // If the offset points after the last node. + if ( endOffset >= endNode.getChildCount() ) + { + // Let's create a temporary node and mark it for removal. + endNode = endNode.append( range.document.createText( '' ) ); + removeEndNode = true; + } + else + endNode = endNode.getChild( endOffset ); + } + } + + // For text containers, we must simply split the node. The removal will + // be handled by the rest of the code . + if ( startNode.type == CKEDITOR.NODE_TEXT ) + { + startNode.split( startOffset ); + + // In cases the end node is the same as the start node, the above + // splitting will also split the end, so me must move the end to + // the second part of the split. + if ( startNode.equals( endNode ) ) + endNode = startNode.getNext(); + } + else + { + // If the start container has children and the offset is pointing + // to a child, then we should start from its previous sibling. + + // If the offset points to the first node, we don't have a + // sibling, so let's use the first one, but mark it for removal. + if ( !startOffset ) + { + // Let's create a temporary node and mark it for removal. + startNode = startNode.getFirst().insertBeforeMe( range.document.createText( '' ) ); + removeStartNode = true; + } + else if ( startOffset >= startNode.getChildCount() ) + { + // Let's create a temporary node and mark it for removal. + startNode = startNode.append( range.document.createText( '' ) ); + removeStartNode = true; + } + else + startNode = startNode.getChild( startOffset ).getPrevious(); + } + + // Get the parent nodes tree for the start and end boundaries. + var startParents = startNode.getParents(); + var endParents = endNode.getParents(); + + // Compare them, to find the top most siblings. + var i, topStart, topEnd; + + for ( i = 0 ; i < startParents.length ; i++ ) + { + topStart = startParents[ i ]; + topEnd = endParents[ i ]; + + // The compared nodes will match until we find the top most + // siblings (different nodes that have the same parent). + // "i" will hold the index in the parents array for the top + // most element. + if ( !topStart.equals( topEnd ) ) + break; + } + + var clone = docFrag, levelStartNode, levelClone, currentNode, currentSibling; + + // Remove all successive sibling nodes for every node in the + // startParents tree. + for ( var j = i ; j < startParents.length ; j++ ) + { + levelStartNode = startParents[j]; + + // For Extract and Clone, we must clone this level. + if ( clone && !levelStartNode.equals( startNode ) ) // action = 0 = Delete + levelClone = clone.append( levelStartNode.clone() ); + + currentNode = levelStartNode.getNext(); + + while ( currentNode ) + { + // Stop processing when the current node matches a node in the + // endParents tree or if it is the endNode. + if ( currentNode.equals( endParents[ j ] ) || currentNode.equals( endNode ) ) + break; + + // Cache the next sibling. + currentSibling = currentNode.getNext(); + + // If cloning, just clone it. + if ( action == 2 ) // 2 = Clone + clone.append( currentNode.clone( true ) ); + else + { + // Both Delete and Extract will remove the node. + currentNode.remove(); + + // When Extracting, move the removed node to the docFrag. + if ( action == 1 ) // 1 = Extract + clone.append( currentNode ); + } + + currentNode = currentSibling; + } + + if ( clone ) + clone = levelClone; + } + + clone = docFrag; + + // Remove all previous sibling nodes for every node in the + // endParents tree. + for ( var k = i ; k < endParents.length ; k++ ) + { + levelStartNode = endParents[ k ]; + + // For Extract and Clone, we must clone this level. + if ( action > 0 && !levelStartNode.equals( endNode ) ) // action = 0 = Delete + levelClone = clone.append( levelStartNode.clone() ); + + // The processing of siblings may have already been done by the parent. + if ( !startParents[ k ] || levelStartNode.$.parentNode != startParents[ k ].$.parentNode ) + { + currentNode = levelStartNode.getPrevious(); + + while ( currentNode ) + { + // Stop processing when the current node matches a node in the + // startParents tree or if it is the startNode. + if ( currentNode.equals( startParents[ k ] ) || currentNode.equals( startNode ) ) + break; + + // Cache the next sibling. + currentSibling = currentNode.getPrevious(); + + // If cloning, just clone it. + if ( action == 2 ) // 2 = Clone + clone.$.insertBefore( currentNode.$.cloneNode( true ), clone.$.firstChild ) ; + else + { + // Both Delete and Extract will remove the node. + currentNode.remove(); + + // When Extracting, mode the removed node to the docFrag. + if ( action == 1 ) // 1 = Extract + clone.$.insertBefore( currentNode.$, clone.$.firstChild ); + } + + currentNode = currentSibling; + } + } + + if ( clone ) + clone = levelClone; + } + + if ( action == 2 ) // 2 = Clone. + { + // No changes in the DOM should be done, so fix the split text (if any). + + var startTextNode = range.startContainer; + if ( startTextNode.type == CKEDITOR.NODE_TEXT ) + { + startTextNode.$.data += startTextNode.$.nextSibling.data; + startTextNode.$.parentNode.removeChild( startTextNode.$.nextSibling ); + } + + var endTextNode = range.endContainer; + if ( endTextNode.type == CKEDITOR.NODE_TEXT && endTextNode.$.nextSibling ) + { + endTextNode.$.data += endTextNode.$.nextSibling.data; + endTextNode.$.parentNode.removeChild( endTextNode.$.nextSibling ); + } + } + else + { + // Collapse the range. + + // If a node has been partially selected, collapse the range between + // topStart and topEnd. Otherwise, simply collapse it to the start. (W3C specs). + if ( topStart && topEnd && ( startNode.$.parentNode != topStart.$.parentNode || endNode.$.parentNode != topEnd.$.parentNode ) ) + { + var endIndex = topEnd.getIndex(); + + // If the start node is to be removed, we must correct the + // index to reflect the removal. + if ( removeStartNode && topEnd.$.parentNode == startNode.$.parentNode ) + endIndex--; + + // Merge splitted parents. + if ( mergeThen && topStart.type == CKEDITOR.NODE_ELEMENT ) + { + var span = CKEDITOR.dom.element.createFromHtml( ' ', range.document ); + span.insertAfter( topStart ); + topStart.mergeSiblings( false ); + range.moveToBookmark( { startNode : span } ); + } + else + range.setStart( topEnd.getParent(), endIndex ); + } + + // Collapse it to the start. + range.collapse( true ); + } + + // Cleanup any marked node. + if ( removeStartNode ) + startNode.remove(); + + if ( removeEndNode && endNode.$.parentNode ) + endNode.remove(); + }; + + var inlineChildReqElements = { abbr:1,acronym:1,b:1,bdo:1,big:1,cite:1,code:1,del:1,dfn:1,em:1,font:1,i:1,ins:1,label:1,kbd:1,q:1,samp:1,small:1,span:1,strike:1,strong:1,sub:1,sup:1,tt:1,u:1,'var':1 }; + + // Creates the appropriate node evaluator for the dom walker used inside + // check(Start|End)OfBlock. + function getCheckStartEndBlockEvalFunction( isStart ) + { + var hadBr = false, bookmarkEvaluator = CKEDITOR.dom.walker.bookmark( true ); + return function( node ) + { + // First ignore bookmark nodes. + if ( bookmarkEvaluator( node ) ) + return true; + + if ( node.type == CKEDITOR.NODE_TEXT ) + { + // If there's any visible text, then we're not at the start. + if ( node.hasAscendant( 'pre' ) || CKEDITOR.tools.trim( node.getText() ).length ) + return false; + } + else if ( node.type == CKEDITOR.NODE_ELEMENT ) + { + // If there are non-empty inline elements (e.g. ), then we're not + // at the start. + if ( !inlineChildReqElements[ node.getName() ] ) + { + // If we're working at the end-of-block, forgive the first
in non-IE + // browsers. + if ( !isStart && !CKEDITOR.env.ie && node.getName() == 'br' && !hadBr ) + hadBr = true; + else + return false; + } + } + return true; + }; + } + + // Evaluator for CKEDITOR.dom.element::checkBoundaryOfElement, reject any + // text node and non-empty elements unless it's being bookmark text. + function elementBoundaryEval( node ) + { + // Reject any text node unless it's being bookmark + // OR it's spaces. (#3883) + return node.type != CKEDITOR.NODE_TEXT + && node.getName() in CKEDITOR.dtd.$removeEmpty + || !CKEDITOR.tools.trim( node.getText() ) + || !!node.getParent().data( 'cke-bookmark' ); + } + + var whitespaceEval = new CKEDITOR.dom.walker.whitespaces(), + bookmarkEval = new CKEDITOR.dom.walker.bookmark(); + + function nonWhitespaceOrBookmarkEval( node ) + { + // Whitespaces and bookmark nodes are to be ignored. + return !whitespaceEval( node ) && !bookmarkEval( node ); + } + + CKEDITOR.dom.range.prototype = + { + clone : function() + { + var clone = new CKEDITOR.dom.range( this.document ); + + clone.startContainer = this.startContainer; + clone.startOffset = this.startOffset; + clone.endContainer = this.endContainer; + clone.endOffset = this.endOffset; + clone.collapsed = this.collapsed; + + return clone; + }, + + collapse : function( toStart ) + { + if ( toStart ) + { + this.endContainer = this.startContainer; + this.endOffset = this.startOffset; + } + else + { + this.startContainer = this.endContainer; + this.startOffset = this.endOffset; + } + + this.collapsed = true; + }, + + /** + * The content nodes of the range are cloned and added to a document fragment, which is returned. + * Note: Text selection may lost after invoking this method. (caused by text node splitting). + */ + cloneContents : function() + { + var docFrag = new CKEDITOR.dom.documentFragment( this.document ); + + if ( !this.collapsed ) + execContentsAction( this, 2, docFrag ); + + return docFrag; + }, + + /** + * Deletes the content nodes of the range permanently from the DOM tree. + * @param {Boolean} [mergeThen] Merge any splitted elements result in DOM true due to partial selection. + */ + deleteContents : function( mergeThen ) + { + if ( this.collapsed ) + return; + + execContentsAction( this, 0, null, mergeThen ); + }, + + /** + * The content nodes of the range are cloned and added to a document fragment, + * meanwhile they're removed permanently from the DOM tree. + * @param {Boolean} [mergeThen] Merge any splitted elements result in DOM true due to partial selection. + */ + extractContents : function( mergeThen ) + { + var docFrag = new CKEDITOR.dom.documentFragment( this.document ); + + if ( !this.collapsed ) + execContentsAction( this, 1, docFrag, mergeThen ); + + return docFrag; + }, + + /** + * Creates a bookmark object, which can be later used to restore the + * range by using the moveToBookmark function. + * This is an "intrusive" way to create a bookmark. It includes tags + * in the range boundaries. The advantage of it is that it is possible to + * handle DOM mutations when moving back to the bookmark. + * Attention: the inclusion of nodes in the DOM is a design choice and + * should not be changed as there are other points in the code that may be + * using those nodes to perform operations. See GetBookmarkNode. + * @param {Boolean} [serializable] Indicates that the bookmark nodes + * must contain ids, which can be used to restore the range even + * when these nodes suffer mutations (like a clonation or innerHTML + * change). + * @returns {Object} And object representing a bookmark. + */ + createBookmark : function( serializable ) + { + var startNode, endNode; + var baseId; + var clone; + var collapsed = this.collapsed; + + startNode = this.document.createElement( 'span' ); + startNode.data( 'cke-bookmark', 1 ); + startNode.setStyle( 'display', 'none' ); + + // For IE, it must have something inside, otherwise it may be + // removed during DOM operations. + startNode.setHtml( ' ' ); + + if ( serializable ) + { + baseId = 'cke_bm_' + CKEDITOR.tools.getNextNumber(); + startNode.setAttribute( 'id', baseId + 'S' ); + } + + // If collapsed, the endNode will not be created. + if ( !collapsed ) + { + endNode = startNode.clone(); + endNode.setHtml( ' ' ); + + if ( serializable ) + endNode.setAttribute( 'id', baseId + 'E' ); + + clone = this.clone(); + clone.collapse(); + clone.insertNode( endNode ); + } + + clone = this.clone(); + clone.collapse( true ); + clone.insertNode( startNode ); + + // Update the range position. + if ( endNode ) + { + this.setStartAfter( startNode ); + this.setEndBefore( endNode ); + } + else + this.moveToPosition( startNode, CKEDITOR.POSITION_AFTER_END ); + + return { + startNode : serializable ? baseId + 'S' : startNode, + endNode : serializable ? baseId + 'E' : endNode, + serializable : serializable, + collapsed : collapsed + }; + }, + + /** + * Creates a "non intrusive" and "mutation sensible" bookmark. This + * kind of bookmark should be used only when the DOM is supposed to + * remain stable after its creation. + * @param {Boolean} [normalized] Indicates that the bookmark must + * normalized. When normalized, the successive text nodes are + * considered a single node. To sucessful load a normalized + * bookmark, the DOM tree must be also normalized before calling + * moveToBookmark. + * @returns {Object} An object representing the bookmark. + */ + createBookmark2 : function( normalized ) + { + var startContainer = this.startContainer, + endContainer = this.endContainer; + + var startOffset = this.startOffset, + endOffset = this.endOffset; + + var collapsed = this.collapsed; + + var child, previous; + + // If there is no range then get out of here. + // It happens on initial load in Safari #962 and if the editor it's + // hidden also in Firefox + if ( !startContainer || !endContainer ) + return { start : 0, end : 0 }; + + if ( normalized ) + { + // Find out if the start is pointing to a text node that will + // be normalized. + if ( startContainer.type == CKEDITOR.NODE_ELEMENT ) + { + child = startContainer.getChild( startOffset ); + + // In this case, move the start information to that text + // node. + if ( child && child.type == CKEDITOR.NODE_TEXT + && startOffset > 0 && child.getPrevious().type == CKEDITOR.NODE_TEXT ) + { + startContainer = child; + startOffset = 0; + } + + // Get the normalized offset. + if ( child && child.type == CKEDITOR.NODE_ELEMENT ) + startOffset = child.getIndex( 1 ); + } + + // Normalize the start. + while ( startContainer.type == CKEDITOR.NODE_TEXT + && ( previous = startContainer.getPrevious() ) + && previous.type == CKEDITOR.NODE_TEXT ) + { + startContainer = previous; + startOffset += previous.getLength(); + } + + // Process the end only if not normalized. + if ( !collapsed ) + { + // Find out if the start is pointing to a text node that + // will be normalized. + if ( endContainer.type == CKEDITOR.NODE_ELEMENT ) + { + child = endContainer.getChild( endOffset ); + + // In this case, move the start information to that + // text node. + if ( child && child.type == CKEDITOR.NODE_TEXT + && endOffset > 0 && child.getPrevious().type == CKEDITOR.NODE_TEXT ) + { + endContainer = child; + endOffset = 0; + } + + // Get the normalized offset. + if ( child && child.type == CKEDITOR.NODE_ELEMENT ) + endOffset = child.getIndex( 1 ); + } + + // Normalize the end. + while ( endContainer.type == CKEDITOR.NODE_TEXT + && ( previous = endContainer.getPrevious() ) + && previous.type == CKEDITOR.NODE_TEXT ) + { + endContainer = previous; + endOffset += previous.getLength(); + } + } + } + + return { + start : startContainer.getAddress( normalized ), + end : collapsed ? null : endContainer.getAddress( normalized ), + startOffset : startOffset, + endOffset : endOffset, + normalized : normalized, + collapsed : collapsed, + is2 : true // It's a createBookmark2 bookmark. + }; + }, + + moveToBookmark : function( bookmark ) + { + if ( bookmark.is2 ) // Created with createBookmark2(). + { + // Get the start information. + var startContainer = this.document.getByAddress( bookmark.start, bookmark.normalized ), + startOffset = bookmark.startOffset; + + // Get the end information. + var endContainer = bookmark.end && this.document.getByAddress( bookmark.end, bookmark.normalized ), + endOffset = bookmark.endOffset; + + // Set the start boundary. + this.setStart( startContainer, startOffset ); + + // Set the end boundary. If not available, collapse it. + if ( endContainer ) + this.setEnd( endContainer, endOffset ); + else + this.collapse( true ); + } + else // Created with createBookmark(). + { + var serializable = bookmark.serializable, + startNode = serializable ? this.document.getById( bookmark.startNode ) : bookmark.startNode, + endNode = serializable ? this.document.getById( bookmark.endNode ) : bookmark.endNode; + + // Set the range start at the bookmark start node position. + this.setStartBefore( startNode ); + + // Remove it, because it may interfere in the setEndBefore call. + startNode.remove(); + + // Set the range end at the bookmark end node position, or simply + // collapse it if it is not available. + if ( endNode ) + { + this.setEndBefore( endNode ); + endNode.remove(); + } + else + this.collapse( true ); + } + }, + + getBoundaryNodes : function() + { + var startNode = this.startContainer, + endNode = this.endContainer, + startOffset = this.startOffset, + endOffset = this.endOffset, + childCount; + + if ( startNode.type == CKEDITOR.NODE_ELEMENT ) + { + childCount = startNode.getChildCount(); + if ( childCount > startOffset ) + startNode = startNode.getChild( startOffset ); + else if ( childCount < 1 ) + startNode = startNode.getPreviousSourceNode(); + else // startOffset > childCount but childCount is not 0 + { + // Try to take the node just after the current position. + startNode = startNode.$; + while ( startNode.lastChild ) + startNode = startNode.lastChild; + startNode = new CKEDITOR.dom.node( startNode ); + + // Normally we should take the next node in DFS order. But it + // is also possible that we've already reached the end of + // document. + startNode = startNode.getNextSourceNode() || startNode; + } + } + if ( endNode.type == CKEDITOR.NODE_ELEMENT ) + { + childCount = endNode.getChildCount(); + if ( childCount > endOffset ) + endNode = endNode.getChild( endOffset ).getPreviousSourceNode( true ); + else if ( childCount < 1 ) + endNode = endNode.getPreviousSourceNode(); + else // endOffset > childCount but childCount is not 0 + { + // Try to take the node just before the current position. + endNode = endNode.$; + while ( endNode.lastChild ) + endNode = endNode.lastChild; + endNode = new CKEDITOR.dom.node( endNode ); + } + } + + // Sometimes the endNode will come right before startNode for collapsed + // ranges. Fix it. (#3780) + if ( startNode.getPosition( endNode ) & CKEDITOR.POSITION_FOLLOWING ) + startNode = endNode; + + return { startNode : startNode, endNode : endNode }; + }, + + /** + * Find the node which fully contains the range. + * @param includeSelf + * @param {Boolean} ignoreTextNode Whether ignore CKEDITOR.NODE_TEXT type. + */ + getCommonAncestor : function( includeSelf , ignoreTextNode ) + { + var start = this.startContainer, + end = this.endContainer, + ancestor; + + if ( start.equals( end ) ) + { + if ( includeSelf + && start.type == CKEDITOR.NODE_ELEMENT + && this.startOffset == this.endOffset - 1 ) + ancestor = start.getChild( this.startOffset ); + else + ancestor = start; + } + else + ancestor = start.getCommonAncestor( end ); + + return ignoreTextNode && !ancestor.is ? ancestor.getParent() : ancestor; + }, + + /** + * Transforms the startContainer and endContainer properties from text + * nodes to element nodes, whenever possible. This is actually possible + * if either of the boundary containers point to a text node, and its + * offset is set to zero, or after the last char in the node. + */ + optimize : function() + { + var container = this.startContainer; + var offset = this.startOffset; + + if ( container.type != CKEDITOR.NODE_ELEMENT ) + { + if ( !offset ) + this.setStartBefore( container ); + else if ( offset >= container.getLength() ) + this.setStartAfter( container ); + } + + container = this.endContainer; + offset = this.endOffset; + + if ( container.type != CKEDITOR.NODE_ELEMENT ) + { + if ( !offset ) + this.setEndBefore( container ); + else if ( offset >= container.getLength() ) + this.setEndAfter( container ); + } + }, + + /** + * Move the range out of bookmark nodes if they'd been the container. + */ + optimizeBookmark: function() + { + var startNode = this.startContainer, + endNode = this.endContainer; + + if ( startNode.is && startNode.is( 'span' ) + && startNode.data( 'cke-bookmark' ) ) + this.setStartAt( startNode, CKEDITOR.POSITION_BEFORE_START ); + if ( endNode && endNode.is && endNode.is( 'span' ) + && endNode.data( 'cke-bookmark' ) ) + this.setEndAt( endNode, CKEDITOR.POSITION_AFTER_END ); + }, + + trim : function( ignoreStart, ignoreEnd ) + { + var startContainer = this.startContainer, + startOffset = this.startOffset, + collapsed = this.collapsed; + if ( ( !ignoreStart || collapsed ) + && startContainer && startContainer.type == CKEDITOR.NODE_TEXT ) + { + // If the offset is zero, we just insert the new node before + // the start. + if ( !startOffset ) + { + startOffset = startContainer.getIndex(); + startContainer = startContainer.getParent(); + } + // If the offset is at the end, we'll insert it after the text + // node. + else if ( startOffset >= startContainer.getLength() ) + { + startOffset = startContainer.getIndex() + 1; + startContainer = startContainer.getParent(); + } + // In other case, we split the text node and insert the new + // node at the split point. + else + { + var nextText = startContainer.split( startOffset ); + + startOffset = startContainer.getIndex() + 1; + startContainer = startContainer.getParent(); + + // Check all necessity of updating the end boundary. + if ( this.startContainer.equals( this.endContainer ) ) + this.setEnd( nextText, this.endOffset - this.startOffset ); + else if ( startContainer.equals( this.endContainer ) ) + this.endOffset += 1; + } + + this.setStart( startContainer, startOffset ); + + if ( collapsed ) + { + this.collapse( true ); + return; + } + } + + var endContainer = this.endContainer; + var endOffset = this.endOffset; + + if ( !( ignoreEnd || collapsed ) + && endContainer && endContainer.type == CKEDITOR.NODE_TEXT ) + { + // If the offset is zero, we just insert the new node before + // the start. + if ( !endOffset ) + { + endOffset = endContainer.getIndex(); + endContainer = endContainer.getParent(); + } + // If the offset is at the end, we'll insert it after the text + // node. + else if ( endOffset >= endContainer.getLength() ) + { + endOffset = endContainer.getIndex() + 1; + endContainer = endContainer.getParent(); + } + // In other case, we split the text node and insert the new + // node at the split point. + else + { + endContainer.split( endOffset ); + + endOffset = endContainer.getIndex() + 1; + endContainer = endContainer.getParent(); + } + + this.setEnd( endContainer, endOffset ); + } + }, + + /** + * Expands the range so that partial units are completely contained. + * @param unit {Number} The unit type to expand with. + * @param {Boolean} [excludeBrs=false] Whether include line-breaks when expanding. + */ + enlarge : function( unit, excludeBrs ) + { + switch ( unit ) + { + case CKEDITOR.ENLARGE_ELEMENT : + + if ( this.collapsed ) + return; + + // Get the common ancestor. + var commonAncestor = this.getCommonAncestor(); + + var body = this.document.getBody(); + + // For each boundary + // a. Depending on its position, find out the first node to be checked (a sibling) or, if not available, to be enlarge. + // b. Go ahead checking siblings and enlarging the boundary as much as possible until the common ancestor is not reached. After reaching the common ancestor, just save the enlargeable node to be used later. + + var startTop, endTop; + + var enlargeable, sibling, commonReached; + + // Indicates that the node can be added only if whitespace + // is available before it. + var needsWhiteSpace = false; + var isWhiteSpace; + var siblingText; + + // Process the start boundary. + + var container = this.startContainer; + var offset = this.startOffset; + + if ( container.type == CKEDITOR.NODE_TEXT ) + { + if ( offset ) + { + // Check if there is any non-space text before the + // offset. Otherwise, container is null. + container = !CKEDITOR.tools.trim( container.substring( 0, offset ) ).length && container; + + // If we found only whitespace in the node, it + // means that we'll need more whitespace to be able + // to expand. For example, can be expanded in + // "A [B]", but not in "A [B]". + needsWhiteSpace = !!container; + } + + if ( container ) + { + if ( !( sibling = container.getPrevious() ) ) + enlargeable = container.getParent(); + } + } + else + { + // If we have offset, get the node preceeding it as the + // first sibling to be checked. + if ( offset ) + sibling = container.getChild( offset - 1 ) || container.getLast(); + + // If there is no sibling, mark the container to be + // enlarged. + if ( !sibling ) + enlargeable = container; + } + + while ( enlargeable || sibling ) + { + if ( enlargeable && !sibling ) + { + // If we reached the common ancestor, mark the flag + // for it. + if ( !commonReached && enlargeable.equals( commonAncestor ) ) + commonReached = true; + + if ( !body.contains( enlargeable ) ) + break; + + // If we don't need space or this element breaks + // the line, then enlarge it. + if ( !needsWhiteSpace || enlargeable.getComputedStyle( 'display' ) != 'inline' ) + { + needsWhiteSpace = false; + + // If the common ancestor has been reached, + // we'll not enlarge it immediately, but just + // mark it to be enlarged later if the end + // boundary also enlarges it. + if ( commonReached ) + startTop = enlargeable; + else + this.setStartBefore( enlargeable ); + } + + sibling = enlargeable.getPrevious(); + } + + // Check all sibling nodes preceeding the enlargeable + // node. The node wil lbe enlarged only if none of them + // blocks it. + while ( sibling ) + { + // This flag indicates that this node has + // whitespaces at the end. + isWhiteSpace = false; + + if ( sibling.type == CKEDITOR.NODE_TEXT ) + { + siblingText = sibling.getText(); + + if ( /[^\s\ufeff]/.test( siblingText ) ) + sibling = null; + + isWhiteSpace = /[\s\ufeff]$/.test( siblingText ); + } + else + { + // If this is a visible element. + // We need to check for the bookmark attribute because IE insists on + // rendering the display:none nodes we use for bookmarks. (#3363) + // Line-breaks (br) are rendered with zero width, which we don't want to include. (#7041) + if ( ( sibling.$.offsetWidth > 0 || excludeBrs && sibling.is( 'br' ) ) && !sibling.data( 'cke-bookmark' ) ) + { + // We'll accept it only if we need + // whitespace, and this is an inline + // element with whitespace only. + if ( needsWhiteSpace && CKEDITOR.dtd.$removeEmpty[ sibling.getName() ] ) + { + // It must contains spaces and inline elements only. + + siblingText = sibling.getText(); + + if ( (/[^\s\ufeff]/).test( siblingText ) ) // Spaces + Zero Width No-Break Space (U+FEFF) + sibling = null; + else + { + var allChildren = sibling.$.all || sibling.$.getElementsByTagName( '*' ); + for ( var i = 0, child ; child = allChildren[ i++ ] ; ) + { + if ( !CKEDITOR.dtd.$removeEmpty[ child.nodeName.toLowerCase() ] ) + { + sibling = null; + break; + } + } + } + + if ( sibling ) + isWhiteSpace = !!siblingText.length; + } + else + sibling = null; + } + } + + // A node with whitespaces has been found. + if ( isWhiteSpace ) + { + // Enlarge the last enlargeable node, if we + // were waiting for spaces. + if ( needsWhiteSpace ) + { + if ( commonReached ) + startTop = enlargeable; + else if ( enlargeable ) + this.setStartBefore( enlargeable ); + } + else + needsWhiteSpace = true; + } + + if ( sibling ) + { + var next = sibling.getPrevious(); + + if ( !enlargeable && !next ) + { + // Set the sibling as enlargeable, so it's + // parent will be get later outside this while. + enlargeable = sibling; + sibling = null; + break; + } + + sibling = next; + } + else + { + // If sibling has been set to null, then we + // need to stop enlarging. + enlargeable = null; + } + } + + if ( enlargeable ) + enlargeable = enlargeable.getParent(); + } + + // Process the end boundary. This is basically the same + // code used for the start boundary, with small changes to + // make it work in the oposite side (to the right). This + // makes it difficult to reuse the code here. So, fixes to + // the above code are likely to be replicated here. + + container = this.endContainer; + offset = this.endOffset; + + // Reset the common variables. + enlargeable = sibling = null; + commonReached = needsWhiteSpace = false; + + if ( container.type == CKEDITOR.NODE_TEXT ) + { + // Check if there is any non-space text after the + // offset. Otherwise, container is null. + container = !CKEDITOR.tools.trim( container.substring( offset ) ).length && container; + + // If we found only whitespace in the node, it + // means that we'll need more whitespace to be able + // to expand. For example, can be expanded in + // "A [B]", but not in "A [B]". + needsWhiteSpace = !( container && container.getLength() ); + + if ( container ) + { + if ( !( sibling = container.getNext() ) ) + enlargeable = container.getParent(); + } + } + else + { + // Get the node right after the boudary to be checked + // first. + sibling = container.getChild( offset ); + + if ( !sibling ) + enlargeable = container; + } + + while ( enlargeable || sibling ) + { + if ( enlargeable && !sibling ) + { + if ( !commonReached && enlargeable.equals( commonAncestor ) ) + commonReached = true; + + if ( !body.contains( enlargeable ) ) + break; + + if ( !needsWhiteSpace || enlargeable.getComputedStyle( 'display' ) != 'inline' ) + { + needsWhiteSpace = false; + + if ( commonReached ) + endTop = enlargeable; + else if ( enlargeable ) + this.setEndAfter( enlargeable ); + } + + sibling = enlargeable.getNext(); + } + + while ( sibling ) + { + isWhiteSpace = false; + + if ( sibling.type == CKEDITOR.NODE_TEXT ) + { + siblingText = sibling.getText(); + + if ( /[^\s\ufeff]/.test( siblingText ) ) + sibling = null; + + isWhiteSpace = /^[\s\ufeff]/.test( siblingText ); + } + else + { + // If this is a visible element. + // We need to check for the bookmark attribute because IE insists on + // rendering the display:none nodes we use for bookmarks. (#3363) + // Line-breaks (br) are rendered with zero width, which we don't want to include. (#7041) + if ( ( sibling.$.offsetWidth > 0 || excludeBrs && sibling.is( 'br' ) ) && !sibling.data( 'cke-bookmark' ) ) + { + // We'll accept it only if we need + // whitespace, and this is an inline + // element with whitespace only. + if ( needsWhiteSpace && CKEDITOR.dtd.$removeEmpty[ sibling.getName() ] ) + { + // It must contains spaces and inline elements only. + + siblingText = sibling.getText(); + + if ( (/[^\s\ufeff]/).test( siblingText ) ) + sibling = null; + else + { + allChildren = sibling.$.all || sibling.$.getElementsByTagName( '*' ); + for ( i = 0 ; child = allChildren[ i++ ] ; ) + { + if ( !CKEDITOR.dtd.$removeEmpty[ child.nodeName.toLowerCase() ] ) + { + sibling = null; + break; + } + } + } + + if ( sibling ) + isWhiteSpace = !!siblingText.length; + } + else + sibling = null; + } + } + + if ( isWhiteSpace ) + { + if ( needsWhiteSpace ) + { + if ( commonReached ) + endTop = enlargeable; + else + this.setEndAfter( enlargeable ); + } + } + + if ( sibling ) + { + next = sibling.getNext(); + + if ( !enlargeable && !next ) + { + enlargeable = sibling; + sibling = null; + break; + } + + sibling = next; + } + else + { + // If sibling has been set to null, then we + // need to stop enlarging. + enlargeable = null; + } + } + + if ( enlargeable ) + enlargeable = enlargeable.getParent(); + } + + // If the common ancestor can be enlarged by both boundaries, then include it also. + if ( startTop && endTop ) + { + commonAncestor = startTop.contains( endTop ) ? endTop : startTop; + + this.setStartBefore( commonAncestor ); + this.setEndAfter( commonAncestor ); + } + break; + + case CKEDITOR.ENLARGE_BLOCK_CONTENTS: + case CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS: + + // Enlarging the start boundary. + var walkerRange = new CKEDITOR.dom.range( this.document ); + + body = this.document.getBody(); + + walkerRange.setStartAt( body, CKEDITOR.POSITION_AFTER_START ); + walkerRange.setEnd( this.startContainer, this.startOffset ); + + var walker = new CKEDITOR.dom.walker( walkerRange ), + blockBoundary, // The node on which the enlarging should stop. + tailBr, // In case BR as block boundary. + notBlockBoundary = CKEDITOR.dom.walker.blockBoundary( + ( unit == CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS ) ? { br : 1 } : null ), + // Record the encountered 'blockBoundary' for later use. + boundaryGuard = function( node ) + { + var retval = notBlockBoundary( node ); + if ( !retval ) + blockBoundary = node; + return retval; + }, + // Record the encounted 'tailBr' for later use. + tailBrGuard = function( node ) + { + var retval = boundaryGuard( node ); + if ( !retval && node.is && node.is( 'br' ) ) + tailBr = node; + return retval; + }; + + walker.guard = boundaryGuard; + + enlargeable = walker.lastBackward(); + + // It's the body which stop the enlarging if no block boundary found. + blockBoundary = blockBoundary || body; + + // Start the range either after the end of found block (

...

[text) + // or at the start of block (

[text...), by comparing the document position + // with 'enlargeable' node. + this.setStartAt( + blockBoundary, + !blockBoundary.is( 'br' ) && + ( !enlargeable && this.checkStartOfBlock() + || enlargeable && blockBoundary.contains( enlargeable ) ) ? + CKEDITOR.POSITION_AFTER_START : + CKEDITOR.POSITION_AFTER_END ); + + // Enlarging the end boundary. + walkerRange = this.clone(); + walkerRange.collapse(); + walkerRange.setEndAt( body, CKEDITOR.POSITION_BEFORE_END ); + walker = new CKEDITOR.dom.walker( walkerRange ); + + // tailBrGuard only used for on range end. + walker.guard = ( unit == CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS ) ? + tailBrGuard : boundaryGuard; + blockBoundary = null; + // End the range right before the block boundary node. + + enlargeable = walker.lastForward(); + + // It's the body which stop the enlarging if no block boundary found. + blockBoundary = blockBoundary || body; + + // Close the range either before the found block start (text]

...

) or at the block end (...text]

) + // by comparing the document position with 'enlargeable' node. + this.setEndAt( + blockBoundary, + ( !enlargeable && this.checkEndOfBlock() + || enlargeable && blockBoundary.contains( enlargeable ) ) ? + CKEDITOR.POSITION_BEFORE_END : + CKEDITOR.POSITION_BEFORE_START ); + // We must include the
at the end of range if there's + // one and we're expanding list item contents + if ( tailBr ) + this.setEndAfter( tailBr ); + } + }, + + /** + * Descrease the range to make sure that boundaries + * always anchor beside text nodes or innermost element. + * @param {Number} mode ( CKEDITOR.SHRINK_ELEMENT | CKEDITOR.SHRINK_TEXT ) The shrinking mode. + *
+ *
CKEDITOR.SHRINK_ELEMENT
+ *
Shrink the range boundaries to the edge of the innermost element.
+ *
CKEDITOR.SHRINK_TEXT
+ *
Shrink the range boudaries to anchor by the side of enclosed text node, range remains if there's no text nodes on boundaries at all.
+ *
+ * @param {Boolean} selectContents Whether result range anchors at the inner OR outer boundary of the node. + */ + shrink : function( mode, selectContents ) + { + // Unable to shrink a collapsed range. + if ( !this.collapsed ) + { + mode = mode || CKEDITOR.SHRINK_TEXT; + + var walkerRange = this.clone(); + + var startContainer = this.startContainer, + endContainer = this.endContainer, + startOffset = this.startOffset, + endOffset = this.endOffset, + collapsed = this.collapsed; + + // Whether the start/end boundary is moveable. + var moveStart = 1, + moveEnd = 1; + + if ( startContainer && startContainer.type == CKEDITOR.NODE_TEXT ) + { + if ( !startOffset ) + walkerRange.setStartBefore( startContainer ); + else if ( startOffset >= startContainer.getLength( ) ) + walkerRange.setStartAfter( startContainer ); + else + { + // Enlarge the range properly to avoid walker making + // DOM changes caused by triming the text nodes later. + walkerRange.setStartBefore( startContainer ); + moveStart = 0; + } + } + + if ( endContainer && endContainer.type == CKEDITOR.NODE_TEXT ) + { + if ( !endOffset ) + walkerRange.setEndBefore( endContainer ); + else if ( endOffset >= endContainer.getLength( ) ) + walkerRange.setEndAfter( endContainer ); + else + { + walkerRange.setEndAfter( endContainer ); + moveEnd = 0; + } + } + + var walker = new CKEDITOR.dom.walker( walkerRange ), + isBookmark = CKEDITOR.dom.walker.bookmark(); + + walker.evaluator = function( node ) + { + return node.type == ( mode == CKEDITOR.SHRINK_ELEMENT ? + CKEDITOR.NODE_ELEMENT : CKEDITOR.NODE_TEXT ); + }; + + var currentElement; + walker.guard = function( node, movingOut ) + { + if ( isBookmark( node ) ) + return true; + + // Stop when we're shrink in element mode while encountering a text node. + if ( mode == CKEDITOR.SHRINK_ELEMENT && node.type == CKEDITOR.NODE_TEXT ) + return false; + + // Stop when we've already walked "through" an element. + if ( movingOut && node.equals( currentElement ) ) + return false; + + if ( !movingOut && node.type == CKEDITOR.NODE_ELEMENT ) + currentElement = node; + + return true; + }; + + if ( moveStart ) + { + var textStart = walker[ mode == CKEDITOR.SHRINK_ELEMENT ? 'lastForward' : 'next'](); + textStart && this.setStartAt( textStart, selectContents ? CKEDITOR.POSITION_AFTER_START : CKEDITOR.POSITION_BEFORE_START ); + } + + if ( moveEnd ) + { + walker.reset(); + var textEnd = walker[ mode == CKEDITOR.SHRINK_ELEMENT ? 'lastBackward' : 'previous'](); + textEnd && this.setEndAt( textEnd, selectContents ? CKEDITOR.POSITION_BEFORE_END : CKEDITOR.POSITION_AFTER_END ); + } + + return !!( moveStart || moveEnd ); + } + }, + + /** + * Inserts a node at the start of the range. The range will be expanded + * the contain the node. + */ + insertNode : function( node ) + { + this.optimizeBookmark(); + this.trim( false, true ); + + var startContainer = this.startContainer; + var startOffset = this.startOffset; + + var nextNode = startContainer.getChild( startOffset ); + + if ( nextNode ) + node.insertBefore( nextNode ); + else + startContainer.append( node ); + + // Check if we need to update the end boundary. + if ( node.getParent().equals( this.endContainer ) ) + this.endOffset++; + + // Expand the range to embrace the new node. + this.setStartBefore( node ); + }, + + moveToPosition : function( node, position ) + { + this.setStartAt( node, position ); + this.collapse( true ); + }, + + selectNodeContents : function( node ) + { + this.setStart( node, 0 ); + this.setEnd( node, node.type == CKEDITOR.NODE_TEXT ? node.getLength() : node.getChildCount() ); + }, + + /** + * Sets the start position of a Range. + * @param {CKEDITOR.dom.node} startNode The node to start the range. + * @param {Number} startOffset An integer greater than or equal to zero + * representing the offset for the start of the range from the start + * of startNode. + */ + setStart : function( startNode, startOffset ) + { + // W3C requires a check for the new position. If it is after the end + // boundary, the range should be collapsed to the new start. It seams + // we will not need this check for our use of this class so we can + // ignore it for now. + + // Fixing invalid range start inside dtd empty elements. + if( startNode.type == CKEDITOR.NODE_ELEMENT + && CKEDITOR.dtd.$empty[ startNode.getName() ] ) + startOffset = startNode.getIndex(), startNode = startNode.getParent(); + + this.startContainer = startNode; + this.startOffset = startOffset; + + if ( !this.endContainer ) + { + this.endContainer = startNode; + this.endOffset = startOffset; + } + + updateCollapsed( this ); + }, + + /** + * Sets the end position of a Range. + * @param {CKEDITOR.dom.node} endNode The node to end the range. + * @param {Number} endOffset An integer greater than or equal to zero + * representing the offset for the end of the range from the start + * of endNode. + */ + setEnd : function( endNode, endOffset ) + { + // W3C requires a check for the new position. If it is before the start + // boundary, the range should be collapsed to the new end. It seams we + // will not need this check for our use of this class so we can ignore + // it for now. + + // Fixing invalid range end inside dtd empty elements. + if( endNode.type == CKEDITOR.NODE_ELEMENT + && CKEDITOR.dtd.$empty[ endNode.getName() ] ) + endOffset = endNode.getIndex() + 1, endNode = endNode.getParent(); + + this.endContainer = endNode; + this.endOffset = endOffset; + + if ( !this.startContainer ) + { + this.startContainer = endNode; + this.startOffset = endOffset; + } + + updateCollapsed( this ); + }, + + setStartAfter : function( node ) + { + this.setStart( node.getParent(), node.getIndex() + 1 ); + }, + + setStartBefore : function( node ) + { + this.setStart( node.getParent(), node.getIndex() ); + }, + + setEndAfter : function( node ) + { + this.setEnd( node.getParent(), node.getIndex() + 1 ); + }, + + setEndBefore : function( node ) + { + this.setEnd( node.getParent(), node.getIndex() ); + }, + + setStartAt : function( node, position ) + { + switch( position ) + { + case CKEDITOR.POSITION_AFTER_START : + this.setStart( node, 0 ); + break; + + case CKEDITOR.POSITION_BEFORE_END : + if ( node.type == CKEDITOR.NODE_TEXT ) + this.setStart( node, node.getLength() ); + else + this.setStart( node, node.getChildCount() ); + break; + + case CKEDITOR.POSITION_BEFORE_START : + this.setStartBefore( node ); + break; + + case CKEDITOR.POSITION_AFTER_END : + this.setStartAfter( node ); + } + + updateCollapsed( this ); + }, + + setEndAt : function( node, position ) + { + switch( position ) + { + case CKEDITOR.POSITION_AFTER_START : + this.setEnd( node, 0 ); + break; + + case CKEDITOR.POSITION_BEFORE_END : + if ( node.type == CKEDITOR.NODE_TEXT ) + this.setEnd( node, node.getLength() ); + else + this.setEnd( node, node.getChildCount() ); + break; + + case CKEDITOR.POSITION_BEFORE_START : + this.setEndBefore( node ); + break; + + case CKEDITOR.POSITION_AFTER_END : + this.setEndAfter( node ); + } + + updateCollapsed( this ); + }, + + fixBlock : function( isStart, blockTag ) + { + var bookmark = this.createBookmark(), + fixedBlock = this.document.createElement( blockTag ); + + this.collapse( isStart ); + + this.enlarge( CKEDITOR.ENLARGE_BLOCK_CONTENTS ); + + this.extractContents().appendTo( fixedBlock ); + fixedBlock.trim(); + + if ( !CKEDITOR.env.ie ) + fixedBlock.appendBogus(); + + this.insertNode( fixedBlock ); + + this.moveToBookmark( bookmark ); + + return fixedBlock; + }, + + splitBlock : function( blockTag ) + { + var startPath = new CKEDITOR.dom.elementPath( this.startContainer ), + endPath = new CKEDITOR.dom.elementPath( this.endContainer ); + + var startBlockLimit = startPath.blockLimit, + endBlockLimit = endPath.blockLimit; + + var startBlock = startPath.block, + endBlock = endPath.block; + + var elementPath = null; + // Do nothing if the boundaries are in different block limits. + if ( !startBlockLimit.equals( endBlockLimit ) ) + return null; + + // Get or fix current blocks. + if ( blockTag != 'br' ) + { + if ( !startBlock ) + { + startBlock = this.fixBlock( true, blockTag ); + endBlock = new CKEDITOR.dom.elementPath( this.endContainer ).block; + } + + if ( !endBlock ) + endBlock = this.fixBlock( false, blockTag ); + } + + // Get the range position. + var isStartOfBlock = startBlock && this.checkStartOfBlock(), + isEndOfBlock = endBlock && this.checkEndOfBlock(); + + // Delete the current contents. + // TODO: Why is 2.x doing CheckIsEmpty()? + this.deleteContents(); + + if ( startBlock && startBlock.equals( endBlock ) ) + { + if ( isEndOfBlock ) + { + elementPath = new CKEDITOR.dom.elementPath( this.startContainer ); + this.moveToPosition( endBlock, CKEDITOR.POSITION_AFTER_END ); + endBlock = null; + } + else if ( isStartOfBlock ) + { + elementPath = new CKEDITOR.dom.elementPath( this.startContainer ); + this.moveToPosition( startBlock, CKEDITOR.POSITION_BEFORE_START ); + startBlock = null; + } + else + { + endBlock = this.splitElement( startBlock ); + + // In Gecko, the last child node must be a bogus
. + // Note: bogus
added under
    or
      would cause + // lists to be incorrectly rendered. + if ( !CKEDITOR.env.ie && !startBlock.is( 'ul', 'ol') ) + startBlock.appendBogus() ; + } + } + + return { + previousBlock : startBlock, + nextBlock : endBlock, + wasStartOfBlock : isStartOfBlock, + wasEndOfBlock : isEndOfBlock, + elementPath : elementPath + }; + }, + + /** + * Branch the specified element from the collapsed range position and + * place the caret between the two result branches. + * Note: The range must be collapsed and been enclosed by this element. + * @param {CKEDITOR.dom.element} element + * @return {CKEDITOR.dom.element} Root element of the new branch after the split. + */ + splitElement : function( toSplit ) + { + if ( !this.collapsed ) + return null; + + // Extract the contents of the block from the selection point to the end + // of its contents. + this.setEndAt( toSplit, CKEDITOR.POSITION_BEFORE_END ); + var documentFragment = this.extractContents(); + + // Duplicate the element after it. + var clone = toSplit.clone( false ); + + // Place the extracted contents into the duplicated element. + documentFragment.appendTo( clone ); + clone.insertAfter( toSplit ); + this.moveToPosition( toSplit, CKEDITOR.POSITION_AFTER_END ); + return clone; + }, + + /** + * Check whether a range boundary is at the inner boundary of a given + * element. + * @param {CKEDITOR.dom.element} element The target element to check. + * @param {Number} checkType The boundary to check for both the range + * and the element. It can be CKEDITOR.START or CKEDITOR.END. + * @returns {Boolean} "true" if the range boundary is at the inner + * boundary of the element. + */ + checkBoundaryOfElement : function( element, checkType ) + { + var checkStart = ( checkType == CKEDITOR.START ); + + // Create a copy of this range, so we can manipulate it for our checks. + var walkerRange = this.clone(); + + // Collapse the range at the proper size. + walkerRange.collapse( checkStart ); + + // Expand the range to element boundary. + walkerRange[ checkStart ? 'setStartAt' : 'setEndAt' ] + ( element, checkStart ? CKEDITOR.POSITION_AFTER_START : CKEDITOR.POSITION_BEFORE_END ); + + // Create the walker, which will check if we have anything useful + // in the range. + var walker = new CKEDITOR.dom.walker( walkerRange ); + walker.evaluator = elementBoundaryEval; + + return walker[ checkStart ? 'checkBackward' : 'checkForward' ](); + }, + + // Calls to this function may produce changes to the DOM. The range may + // be updated to reflect such changes. + checkStartOfBlock : function() + { + var startContainer = this.startContainer, + startOffset = this.startOffset; + + // If the starting node is a text node, and non-empty before the offset, + // then we're surely not at the start of block. + if ( startOffset && startContainer.type == CKEDITOR.NODE_TEXT ) + { + var textBefore = CKEDITOR.tools.ltrim( startContainer.substring( 0, startOffset ) ); + if ( textBefore.length ) + return false; + } + + // Antecipate the trim() call here, so the walker will not make + // changes to the DOM, which would not get reflected into this + // range otherwise. + this.trim(); + + // We need to grab the block element holding the start boundary, so + // let's use an element path for it. + var path = new CKEDITOR.dom.elementPath( this.startContainer ); + + // Creates a range starting at the block start until the range start. + var walkerRange = this.clone(); + walkerRange.collapse( true ); + walkerRange.setStartAt( path.block || path.blockLimit, CKEDITOR.POSITION_AFTER_START ); + + var walker = new CKEDITOR.dom.walker( walkerRange ); + walker.evaluator = getCheckStartEndBlockEvalFunction( true ); + + return walker.checkBackward(); + }, + + checkEndOfBlock : function() + { + var endContainer = this.endContainer, + endOffset = this.endOffset; + + // If the ending node is a text node, and non-empty after the offset, + // then we're surely not at the end of block. + if ( endContainer.type == CKEDITOR.NODE_TEXT ) + { + var textAfter = CKEDITOR.tools.rtrim( endContainer.substring( endOffset ) ); + if ( textAfter.length ) + return false; + } + + // Antecipate the trim() call here, so the walker will not make + // changes to the DOM, which would not get reflected into this + // range otherwise. + this.trim(); + + // We need to grab the block element holding the start boundary, so + // let's use an element path for it. + var path = new CKEDITOR.dom.elementPath( this.endContainer ); + + // Creates a range starting at the block start until the range start. + var walkerRange = this.clone(); + walkerRange.collapse( false ); + walkerRange.setEndAt( path.block || path.blockLimit, CKEDITOR.POSITION_BEFORE_END ); + + var walker = new CKEDITOR.dom.walker( walkerRange ); + walker.evaluator = getCheckStartEndBlockEvalFunction( false ); + + return walker.checkForward(); + }, + + /** + * Check if elements at which the range boundaries anchor are read-only, + * with respect to "contenteditable" attribute. + */ + checkReadOnly : ( function() + { + function checkNodesEditable( node, anotherEnd ) + { + while( node ) + { + if ( node.type == CKEDITOR.NODE_ELEMENT ) + { + if ( node.getAttribute( 'contentEditable' ) == 'false' + && !node.data( 'cke-editable' ) ) + { + return 0; + } + // Range enclosed entirely in an editable element. + else if ( node.is( 'html' ) + || node.getAttribute( 'contentEditable' ) == 'true' + && ( node.contains( anotherEnd ) || node.equals( anotherEnd ) ) ) + { + break; + } + } + node = node.getParent(); + } + + return 1; + } + + return function() + { + var startNode = this.startContainer, + endNode = this.endContainer; + + // Check if elements path at both boundaries are editable. + return !( checkNodesEditable( startNode, endNode ) && checkNodesEditable( endNode, startNode ) ); + }; + })(), + + /** + * Moves the range boundaries to the first/end editing point inside an + * element. For example, in an element tree like + * "<p><b><i></i></b> Text</p>", the start editing point is + * "<p><b><i>^</i></b> Text</p>" (inside <i>). + * @param {CKEDITOR.dom.element} el The element into which look for the + * editing spot. + * @param {Boolean} isMoveToEnd Whether move to the end editable position. + */ + moveToElementEditablePosition : function( el, isMoveToEnd ) + { + var isEditable; + + // Empty elements are rejected. + if ( CKEDITOR.dtd.$empty[ el.getName() ] ) + return false; + + while ( el && el.type == CKEDITOR.NODE_ELEMENT ) + { + isEditable = el.isEditable(); + + // If an editable element is found, move inside it. + if ( isEditable ) + this.moveToPosition( el, isMoveToEnd ? + CKEDITOR.POSITION_BEFORE_END : + CKEDITOR.POSITION_AFTER_START ); + // Stop immediately if we've found a non editable inline element (e.g ). + else if ( CKEDITOR.dtd.$inline[ el.getName() ] ) + { + this.moveToPosition( el, isMoveToEnd ? + CKEDITOR.POSITION_AFTER_END : + CKEDITOR.POSITION_BEFORE_START ); + return true; + } + + // Non-editable non-inline elements are to be bypassed, getting the next one. + if ( CKEDITOR.dtd.$empty[ el.getName() ] ) + el = el[ isMoveToEnd ? 'getPrevious' : 'getNext' ]( nonWhitespaceOrBookmarkEval ); + else + el = el[ isMoveToEnd ? 'getLast' : 'getFirst' ]( nonWhitespaceOrBookmarkEval ); + + // Stop immediately if we've found a text node. + if ( el && el.type == CKEDITOR.NODE_TEXT ) + { + this.moveToPosition( el, isMoveToEnd ? + CKEDITOR.POSITION_AFTER_END : + CKEDITOR.POSITION_BEFORE_START ); + return true; + } + } + + return isEditable; + }, + + /** + *@see {CKEDITOR.dom.range.moveToElementEditablePosition} + */ + moveToElementEditStart : function( target ) + { + return this.moveToElementEditablePosition( target ); + }, + + /** + *@see {CKEDITOR.dom.range.moveToElementEditablePosition} + */ + moveToElementEditEnd : function( target ) + { + return this.moveToElementEditablePosition( target, true ); + }, + + /** + * Get the single node enclosed within the range if there's one. + */ + getEnclosedNode : function() + { + var walkerRange = this.clone(); + + // Optimize and analyze the range to avoid DOM destructive nature of walker. (#5780) + walkerRange.optimize(); + if ( walkerRange.startContainer.type != CKEDITOR.NODE_ELEMENT + || walkerRange.endContainer.type != CKEDITOR.NODE_ELEMENT ) + return null; + + var walker = new CKEDITOR.dom.walker( walkerRange ), + isNotBookmarks = CKEDITOR.dom.walker.bookmark( true ), + isNotWhitespaces = CKEDITOR.dom.walker.whitespaces( true ), + evaluator = function( node ) + { + return isNotWhitespaces( node ) && isNotBookmarks( node ); + }; + walkerRange.evaluator = evaluator; + var node = walker.next(); + walker.reset(); + return node && node.equals( walker.previous() ) ? node : null; + }, + + getTouchedStartNode : function() + { + var container = this.startContainer ; + + if ( this.collapsed || container.type != CKEDITOR.NODE_ELEMENT ) + return container ; + + return container.getChild( this.startOffset ) || container ; + }, + + getTouchedEndNode : function() + { + var container = this.endContainer ; + + if ( this.collapsed || container.type != CKEDITOR.NODE_ELEMENT ) + return container ; + + return container.getChild( this.endOffset - 1 ) || container ; + } + }; +})(); + +CKEDITOR.POSITION_AFTER_START = 1; // ^contents "^text" +CKEDITOR.POSITION_BEFORE_END = 2; // contents^ "text^" +CKEDITOR.POSITION_BEFORE_START = 3; // ^contents ^"text" +CKEDITOR.POSITION_AFTER_END = 4; // contents^ "text" + +CKEDITOR.ENLARGE_ELEMENT = 1; +CKEDITOR.ENLARGE_BLOCK_CONTENTS = 2; +CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS = 3; + +// Check boundary types. +// @see CKEDITOR.dom.range.prototype.checkBoundaryOfElement +CKEDITOR.START = 1; +CKEDITOR.END = 2; +CKEDITOR.STARTEND = 3; + +// Shrink range types. +// @see CKEDITOR.dom.range.prototype.shrink +CKEDITOR.SHRINK_ELEMENT = 1; +CKEDITOR.SHRINK_TEXT = 2; diff --git a/htdocs/includes/ckeditor/_source/core/dom/rangelist.js b/htdocs/includes/ckeditor/_source/core/dom/rangelist.js new file mode 100755 index 00000000000..2685506214c --- /dev/null +++ b/htdocs/includes/ckeditor/_source/core/dom/rangelist.js @@ -0,0 +1,213 @@ +/* +Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +(function() +{ + /** + * Represents a list os CKEDITOR.dom.range objects, which can be easily + * iterated sequentially. + * @constructor + * @param {CKEDITOR.dom.range|Array} [ranges] The ranges contained on this list. + * Note that, if an array of ranges is specified, the range sequence + * should match its DOM order. This class will not help to sort them. + */ + CKEDITOR.dom.rangeList = function( ranges ) + { + if ( ranges instanceof CKEDITOR.dom.rangeList ) + return ranges; + + if ( !ranges ) + ranges = []; + else if ( ranges instanceof CKEDITOR.dom.range ) + ranges = [ ranges ]; + + return CKEDITOR.tools.extend( ranges, mixins ); + }; + + var mixins = + /** @lends CKEDITOR.dom.rangeList.prototype */ + { + /** + * Creates an instance of the rangeList iterator, it should be used + * only when the ranges processing could be DOM intrusive, which + * means it may pollute and break other ranges in this list. + * Otherwise, it's enough to just iterate over this array in a for loop. + * @returns {CKEDITOR.dom.rangeListIterator} + */ + createIterator : function() + { + var rangeList = this, + bookmark = CKEDITOR.dom.walker.bookmark(), + guard = function( node ) { return ! ( node.is && node.is( 'tr' ) ); }, + bookmarks = [], + current; + + /** + * @lends CKEDITOR.dom.rangeListIterator.prototype + */ + return { + + /** + * Retrieves the next range in the list. + * @param {Boolean} mergeConsequent Whether join two adjacent ranges into single, e.g. consequent table cells. + */ + getNextRange : function( mergeConsequent ) + { + current = current == undefined ? 0 : current + 1; + + var range = rangeList[ current ]; + + // Multiple ranges might be mangled by each other. + if ( range && rangeList.length > 1 ) + { + // Bookmarking all other ranges on the first iteration, + // the range correctness after it doesn't matter since we'll + // restore them before the next iteration. + if ( !current ) + { + // Make sure bookmark correctness by reverse processing. + for ( var i = rangeList.length - 1; i >= 0; i-- ) + bookmarks.unshift( rangeList[ i ].createBookmark( true ) ); + } + + if ( mergeConsequent ) + { + // Figure out how many ranges should be merged. + var mergeCount = 0; + while ( rangeList[ current + mergeCount + 1 ] ) + { + var doc = range.document, + found = 0, + left = doc.getById( bookmarks[ mergeCount ].endNode ), + right = doc.getById( bookmarks[ mergeCount + 1 ].startNode ), + next; + + // Check subsequent range. + while ( 1 ) + { + next = left.getNextSourceNode( false ); + if ( !right.equals( next ) ) + { + // This could be yet another bookmark or + // walking across block boundaries. + if ( bookmark( next ) || ( next.type == CKEDITOR.NODE_ELEMENT && next.isBlockBoundary() ) ) + { + left = next; + continue; + } + } + else + found = 1; + + break; + } + + if ( !found ) + break; + + mergeCount++; + } + } + + range.moveToBookmark( bookmarks.shift() ); + + // Merge ranges finally after moving to bookmarks. + while( mergeCount-- ) + { + next = rangeList[ ++current ]; + next.moveToBookmark( bookmarks.shift() ); + range.setEnd( next.endContainer, next.endOffset ); + } + } + + return range; + } + }; + }, + + createBookmarks : function( serializable ) + { + var retval = [], bookmark; + for ( var i = 0; i < this.length ; i++ ) + { + retval.push( bookmark = this[ i ].createBookmark( serializable, true) ); + + // Updating the container & offset values for ranges + // that have been touched. + for ( var j = i + 1; j < this.length; j++ ) + { + this[ j ] = updateDirtyRange( bookmark, this[ j ] ); + this[ j ] = updateDirtyRange( bookmark, this[ j ], true ); + } + } + return retval; + }, + + createBookmarks2 : function( normalized ) + { + var bookmarks = []; + + for ( var i = 0 ; i < this.length ; i++ ) + bookmarks.push( this[ i ].createBookmark2( normalized ) ); + + return bookmarks; + }, + + /** + * Move each range in the list to the position specified by a list of bookmarks. + * @param {Array} bookmarks The list of bookmarks, each one matching a range in the list. + */ + moveToBookmarks : function( bookmarks ) + { + for ( var i = 0 ; i < this.length ; i++ ) + this[ i ].moveToBookmark( bookmarks[ i ] ); + } + }; + + // Update the specified range which has been mangled by previous insertion of + // range bookmark nodes.(#3256) + function updateDirtyRange( bookmark, dirtyRange, checkEnd ) + { + var serializable = bookmark.serializable, + container = dirtyRange[ checkEnd ? 'endContainer' : 'startContainer' ], + offset = checkEnd ? 'endOffset' : 'startOffset'; + + var bookmarkStart = serializable ? + dirtyRange.document.getById( bookmark.startNode ) + : bookmark.startNode; + + var bookmarkEnd = serializable ? + dirtyRange.document.getById( bookmark.endNode ) + : bookmark.endNode; + + if ( container.equals( bookmarkStart.getPrevious() ) ) + { + dirtyRange.startOffset = dirtyRange.startOffset + - container.getLength() + - bookmarkEnd.getPrevious().getLength(); + container = bookmarkEnd.getNext(); + } + else if ( container.equals( bookmarkEnd.getPrevious() ) ) + { + dirtyRange.startOffset = dirtyRange.startOffset - container.getLength(); + container = bookmarkEnd.getNext(); + } + + container.equals( bookmarkStart.getParent() ) && dirtyRange[ offset ]++; + container.equals( bookmarkEnd.getParent() ) && dirtyRange[ offset ]++; + + // Update and return this range. + dirtyRange[ checkEnd ? 'endContainer' : 'startContainer' ] = container; + return dirtyRange; + } +})(); + +/** + * (Virtual Class) Do not call this constructor. This class is not really part + * of the API. It just describes the return type of {@link CKEDITOR.dom.rangeList#createIterator}. + * @name CKEDITOR.dom.rangeListIterator + * @constructor + * @example + */ diff --git a/htdocs/includes/ckeditor/_source/core/dom/text.js b/htdocs/includes/ckeditor/_source/core/dom/text.js new file mode 100755 index 00000000000..4abf8fc31ee --- /dev/null +++ b/htdocs/includes/ckeditor/_source/core/dom/text.js @@ -0,0 +1,128 @@ +/* +Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @fileOverview Defines the {@link CKEDITOR.dom.text} class, which represents + * a DOM text node. + */ + +/** + * Represents a DOM text node. + * @constructor + * @augments CKEDITOR.dom.node + * @param {Object|String} text A native DOM text node or a string containing + * the text to use to create a new text node. + * @param {CKEDITOR.dom.document} [ownerDocument] The document that will contain + * the node in case of new node creation. Defaults to the current document. + * @example + * var nativeNode = document.createTextNode( 'Example' ); + * var text = CKEDITOR.dom.text( nativeNode ); + * @example + * var text = CKEDITOR.dom.text( 'Example' ); + */ +CKEDITOR.dom.text = function( text, ownerDocument ) +{ + if ( typeof text == 'string' ) + text = ( ownerDocument ? ownerDocument.$ : document ).createTextNode( text ); + + // Theoretically, we should call the base constructor here + // (not CKEDITOR.dom.node though). But, IE doesn't support expando + // properties on text node, so the features provided by domObject will not + // work for text nodes (which is not a big issue for us). + // + // CKEDITOR.dom.domObject.call( this, element ); + + /** + * The native DOM text node represented by this class instance. + * @type Object + * @example + * var element = new CKEDITOR.dom.text( 'Example' ); + * alert( element.$.nodeType ); // "3" + */ + this.$ = text; +}; + +CKEDITOR.dom.text.prototype = new CKEDITOR.dom.node(); + +CKEDITOR.tools.extend( CKEDITOR.dom.text.prototype, + /** @lends CKEDITOR.dom.text.prototype */ + { + /** + * The node type. This is a constant value set to + * {@link CKEDITOR.NODE_TEXT}. + * @type Number + * @example + */ + type : CKEDITOR.NODE_TEXT, + + getLength : function() + { + return this.$.nodeValue.length; + }, + + getText : function() + { + return this.$.nodeValue; + }, + + setText : function( text ) + { + this.$.nodeValue = text; + }, + + /** + * Breaks this text node into two nodes at the specified offset, + * keeping both in the tree as siblings. This node then only contains + * all the content up to the offset point. A new text node, which is + * inserted as the next sibling of this node, contains all the content + * at and after the offset point. When the offset is equal to the + * length of this node, the new node has no data. + * @param {Number} The position at which to split, starting from zero. + * @returns {CKEDITOR.dom.text} The new text node. + */ + split : function( offset ) + { + // If the offset is after the last char, IE creates the text node + // on split, but don't include it into the DOM. So, we have to do + // that manually here. + if ( CKEDITOR.env.ie && offset == this.getLength() ) + { + var next = this.getDocument().createText( '' ); + next.insertAfter( this ); + return next; + } + + var doc = this.getDocument(); + var retval = new CKEDITOR.dom.text( this.$.splitText( offset ), doc ); + + // IE BUG: IE8 does not update the childNodes array in DOM after splitText(), + // we need to make some DOM changes to make it update. (#3436) + if ( CKEDITOR.env.ie8 ) + { + var workaround = new CKEDITOR.dom.text( '', doc ); + workaround.insertAfter( retval ); + workaround.remove(); + } + + return retval; + }, + + /** + * Extracts characters from indexA up to but not including indexB. + * @param {Number} indexA An integer between 0 and one less than the + * length of the text. + * @param {Number} [indexB] An integer between 0 and the length of the + * string. If omitted, extracts characters to the end of the text. + */ + substring : function( indexA, indexB ) + { + // We need the following check due to a Firefox bug + // https://bugzilla.mozilla.org/show_bug.cgi?id=458886 + if ( typeof indexB != 'number' ) + return this.$.nodeValue.substr( indexA ); + else + return this.$.nodeValue.substring( indexA, indexB ); + } + }); diff --git a/htdocs/includes/ckeditor/_source/core/dom/walker.js b/htdocs/includes/ckeditor/_source/core/dom/walker.js new file mode 100755 index 00000000000..7f430b8ad02 --- /dev/null +++ b/htdocs/includes/ckeditor/_source/core/dom/walker.js @@ -0,0 +1,462 @@ +/* +Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +(function() +{ + // This function is to be called under a "walker" instance scope. + function iterate( rtl, breakOnFalse ) + { + // Return null if we have reached the end. + if ( this._.end ) + return null; + + var node, + range = this.range, + guard, + userGuard = this.guard, + type = this.type, + getSourceNodeFn = ( rtl ? 'getPreviousSourceNode' : 'getNextSourceNode' ); + + // This is the first call. Initialize it. + if ( !this._.start ) + { + this._.start = 1; + + // Trim text nodes and optmize the range boundaries. DOM changes + // may happen at this point. + range.trim(); + + // A collapsed range must return null at first call. + if ( range.collapsed ) + { + this.end(); + return null; + } + } + + // Create the LTR guard function, if necessary. + if ( !rtl && !this._.guardLTR ) + { + // Gets the node that stops the walker when going LTR. + var limitLTR = range.endContainer, + blockerLTR = limitLTR.getChild( range.endOffset ); + + this._.guardLTR = function( node, movingOut ) + { + return ( ( !movingOut || !limitLTR.equals( node ) ) + && ( !blockerLTR || !node.equals( blockerLTR ) ) + && ( node.type != CKEDITOR.NODE_ELEMENT || !movingOut || node.getName() != 'body' ) ); + }; + } + + // Create the RTL guard function, if necessary. + if ( rtl && !this._.guardRTL ) + { + // Gets the node that stops the walker when going LTR. + var limitRTL = range.startContainer, + blockerRTL = ( range.startOffset > 0 ) && limitRTL.getChild( range.startOffset - 1 ); + + this._.guardRTL = function( node, movingOut ) + { + return ( ( !movingOut || !limitRTL.equals( node ) ) + && ( !blockerRTL || !node.equals( blockerRTL ) ) + && ( node.type != CKEDITOR.NODE_ELEMENT || !movingOut || node.getName() != 'body' ) ); + }; + } + + // Define which guard function to use. + var stopGuard = rtl ? this._.guardRTL : this._.guardLTR; + + // Make the user defined guard function participate in the process, + // otherwise simply use the boundary guard. + if ( userGuard ) + { + guard = function( node, movingOut ) + { + if ( stopGuard( node, movingOut ) === false ) + return false; + + return userGuard( node, movingOut ); + }; + } + else + guard = stopGuard; + + if ( this.current ) + node = this.current[ getSourceNodeFn ]( false, type, guard ); + else + { + // Get the first node to be returned. + + if ( rtl ) + { + node = range.endContainer; + + if ( range.endOffset > 0 ) + { + node = node.getChild( range.endOffset - 1 ); + if ( guard( node ) === false ) + node = null; + } + else + node = ( guard ( node, true ) === false ) ? + null : node.getPreviousSourceNode( true, type, guard ); + } + else + { + node = range.startContainer; + node = node.getChild( range.startOffset ); + + if ( node ) + { + if ( guard( node ) === false ) + node = null; + } + else + node = ( guard ( range.startContainer, true ) === false ) ? + null : range.startContainer.getNextSourceNode( true, type, guard ) ; + } + } + + while ( node && !this._.end ) + { + this.current = node; + + if ( !this.evaluator || this.evaluator( node ) !== false ) + { + if ( !breakOnFalse ) + return node; + } + else if ( breakOnFalse && this.evaluator ) + return false; + + node = node[ getSourceNodeFn ]( false, type, guard ); + } + + this.end(); + return this.current = null; + } + + function iterateToLast( rtl ) + { + var node, last = null; + + while ( ( node = iterate.call( this, rtl ) ) ) + last = node; + + return last; + } + + CKEDITOR.dom.walker = CKEDITOR.tools.createClass( + { + /** + * Utility class to "walk" the DOM inside a range boundaries. If + * necessary, partially included nodes (text nodes) are broken to + * reflect the boundaries limits, so DOM and range changes may happen. + * Outside changes to the range may break the walker. + * + * The walker may return nodes that are not totaly included into the + * range boundaires. Let's take the following range representation, + * where the square brackets indicate the boundaries: + * + * [<p>Some <b>sample] text</b> + * + * While walking forward into the above range, the following nodes are + * returned: <p>, "Some ", <b> and "sample". Going + * backwards instead we have: "sample" and "Some ". So note that the + * walker always returns nodes when "entering" them, but not when + * "leaving" them. The guard function is instead called both when + * entering and leaving nodes. + * + * @constructor + * @param {CKEDITOR.dom.range} range The range within which walk. + */ + $ : function( range ) + { + this.range = range; + + /** + * A function executed for every matched node, to check whether + * it's to be considered into the walk or not. If not provided, all + * matched nodes are considered good. + * If the function returns "false" the node is ignored. + * @name CKEDITOR.dom.walker.prototype.evaluator + * @property + * @type Function + */ + // this.evaluator = null; + + /** + * A function executed for every node the walk pass by to check + * whether the walk is to be finished. It's called when both + * entering and exiting nodes, as well as for the matched nodes. + * If this function returns "false", the walking ends and no more + * nodes are evaluated. + * @name CKEDITOR.dom.walker.prototype.guard + * @property + * @type Function + */ + // this.guard = null; + + /** @private */ + this._ = {}; + }, + +// statics : +// { +// /* Creates a CKEDITOR.dom.walker instance to walk inside DOM boundaries set by nodes. +// * @param {CKEDITOR.dom.node} startNode The node from wich the walk +// * will start. +// * @param {CKEDITOR.dom.node} [endNode] The last node to be considered +// * in the walk. No more nodes are retrieved after touching or +// * passing it. If not provided, the walker stops at the +// * <body> closing boundary. +// * @returns {CKEDITOR.dom.walker} A DOM walker for the nodes between the +// * provided nodes. +// */ +// createOnNodes : function( startNode, endNode, startInclusive, endInclusive ) +// { +// var range = new CKEDITOR.dom.range(); +// if ( startNode ) +// range.setStartAt( startNode, startInclusive ? CKEDITOR.POSITION_BEFORE_START : CKEDITOR.POSITION_AFTER_END ) ; +// else +// range.setStartAt( startNode.getDocument().getBody(), CKEDITOR.POSITION_AFTER_START ) ; +// +// if ( endNode ) +// range.setEndAt( endNode, endInclusive ? CKEDITOR.POSITION_AFTER_END : CKEDITOR.POSITION_BEFORE_START ) ; +// else +// range.setEndAt( startNode.getDocument().getBody(), CKEDITOR.POSITION_BEFORE_END ) ; +// +// return new CKEDITOR.dom.walker( range ); +// } +// }, +// + proto : + { + /** + * Stop walking. No more nodes are retrieved if this function gets + * called. + */ + end : function() + { + this._.end = 1; + }, + + /** + * Retrieves the next node (at right). + * @returns {CKEDITOR.dom.node} The next node or null if no more + * nodes are available. + */ + next : function() + { + return iterate.call( this ); + }, + + /** + * Retrieves the previous node (at left). + * @returns {CKEDITOR.dom.node} The previous node or null if no more + * nodes are available. + */ + previous : function() + { + return iterate.call( this, 1 ); + }, + + /** + * Check all nodes at right, executing the evaluation fuction. + * @returns {Boolean} "false" if the evaluator function returned + * "false" for any of the matched nodes. Otherwise "true". + */ + checkForward : function() + { + return iterate.call( this, 0, 1 ) !== false; + }, + + /** + * Check all nodes at left, executing the evaluation fuction. + * @returns {Boolean} "false" if the evaluator function returned + * "false" for any of the matched nodes. Otherwise "true". + */ + checkBackward : function() + { + return iterate.call( this, 1, 1 ) !== false; + }, + + /** + * Executes a full walk forward (to the right), until no more nodes + * are available, returning the last valid node. + * @returns {CKEDITOR.dom.node} The last node at the right or null + * if no valid nodes are available. + */ + lastForward : function() + { + return iterateToLast.call( this ); + }, + + /** + * Executes a full walk backwards (to the left), until no more nodes + * are available, returning the last valid node. + * @returns {CKEDITOR.dom.node} The last node at the left or null + * if no valid nodes are available. + */ + lastBackward : function() + { + return iterateToLast.call( this, 1 ); + }, + + reset : function() + { + delete this.current; + this._ = {}; + } + + } + }); + + /* + * Anything whose display computed style is block, list-item, table, + * table-row-group, table-header-group, table-footer-group, table-row, + * table-column-group, table-column, table-cell, table-caption, or whose node + * name is hr, br (when enterMode is br only) is a block boundary. + */ + var blockBoundaryDisplayMatch = + { + block : 1, + 'list-item' : 1, + table : 1, + 'table-row-group' : 1, + 'table-header-group' : 1, + 'table-footer-group' : 1, + 'table-row' : 1, + 'table-column-group' : 1, + 'table-column' : 1, + 'table-cell' : 1, + 'table-caption' : 1 + }; + + CKEDITOR.dom.element.prototype.isBlockBoundary = function( customNodeNames ) + { + var nodeNameMatches = customNodeNames ? + CKEDITOR.tools.extend( {}, CKEDITOR.dtd.$block, customNodeNames || {} ) : + CKEDITOR.dtd.$block; + + // Don't consider floated formatting as block boundary, fall back to dtd check in that case. (#6297) + return this.getComputedStyle( 'float' ) == 'none' && blockBoundaryDisplayMatch[ this.getComputedStyle( 'display' ) ] + || nodeNameMatches[ this.getName() ]; + }; + + CKEDITOR.dom.walker.blockBoundary = function( customNodeNames ) + { + return function( node , type ) + { + return ! ( node.type == CKEDITOR.NODE_ELEMENT + && node.isBlockBoundary( customNodeNames ) ); + }; + }; + + CKEDITOR.dom.walker.listItemBoundary = function() + { + return this.blockBoundary( { br : 1 } ); + }; + + /** + * Whether the to-be-evaluated node is a bookmark node OR bookmark node + * inner contents. + * @param {Boolean} contentOnly Whether only test againt the text content of + * bookmark node instead of the element itself(default). + * @param {Boolean} isReject Whether should return 'false' for the bookmark + * node instead of 'true'(default). + */ + CKEDITOR.dom.walker.bookmark = function( contentOnly, isReject ) + { + function isBookmarkNode( node ) + { + return ( node && node.getName + && node.getName() == 'span' + && node.data( 'cke-bookmark' ) ); + } + + return function( node ) + { + var isBookmark, parent; + // Is bookmark inner text node? + isBookmark = ( node && !node.getName && ( parent = node.getParent() ) + && isBookmarkNode( parent ) ); + // Is bookmark node? + isBookmark = contentOnly ? isBookmark : isBookmark || isBookmarkNode( node ); + return !! ( isReject ^ isBookmark ); + }; + }; + + /** + * Whether the node is a text node containing only whitespaces characters. + * @param isReject + */ + CKEDITOR.dom.walker.whitespaces = function( isReject ) + { + return function( node ) + { + var isWhitespace = node && ( node.type == CKEDITOR.NODE_TEXT ) + && !CKEDITOR.tools.trim( node.getText() ); + return !! ( isReject ^ isWhitespace ); + }; + }; + + /** + * Whether the node is invisible in wysiwyg mode. + * @param isReject + */ + CKEDITOR.dom.walker.invisible = function( isReject ) + { + var whitespace = CKEDITOR.dom.walker.whitespaces(); + return function( node ) + { + // Nodes that take no spaces in wysiwyg: + // 1. White-spaces but not including NBSP; + // 2. Empty inline elements, e.g. we're checking here + // 'offsetHeight' instead of 'offsetWidth' for properly excluding + // all sorts of empty paragraph, e.g.
      . + var isInvisible = whitespace( node ) || node.is && !node.$.offsetHeight; + return !! ( isReject ^ isInvisible ); + }; + }; + + CKEDITOR.dom.walker.nodeType = function( type, isReject ) + { + return function( node ) + { + return !! ( isReject ^ ( node.type == type ) ); + }; + }; + + var tailNbspRegex = /^[\t\r\n ]*(?: |\xa0)$/, + isWhitespaces = CKEDITOR.dom.walker.whitespaces(), + isBookmark = CKEDITOR.dom.walker.bookmark(), + toSkip = function( node ) + { + return isBookmark( node ) + || isWhitespaces( node ) + || node.type == CKEDITOR.NODE_ELEMENT + && node.getName() in CKEDITOR.dtd.$inline + && !( node.getName() in CKEDITOR.dtd.$empty ); + }; + + // Check if there's a filler node at the end of an element, and return it. + CKEDITOR.dom.element.prototype.getBogus = function() + { + // Bogus are not always at the end, e.g.

      text

      (#7070). + var tail = this; + do { tail = tail.getPreviousSourceNode(); } + while ( toSkip( tail ) ) + + if ( tail && ( !CKEDITOR.env.ie ? tail.is && tail.is( 'br' ) + : tail.getText && tailNbspRegex.test( tail.getText() ) ) ) + { + return tail; + } + return false; + }; + +})(); diff --git a/htdocs/includes/ckeditor/_source/core/dom/window.js b/htdocs/includes/ckeditor/_source/core/dom/window.js new file mode 100755 index 00000000000..c5492b6fb6a --- /dev/null +++ b/htdocs/includes/ckeditor/_source/core/dom/window.js @@ -0,0 +1,96 @@ +/* +Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @fileOverview Defines the {@link CKEDITOR.dom.document} class, which + * represents a DOM document. + */ + +/** + * Represents a DOM window. + * @constructor + * @augments CKEDITOR.dom.domObject + * @param {Object} domWindow A native DOM window. + * @example + * var document = new CKEDITOR.dom.window( window ); + */ +CKEDITOR.dom.window = function( domWindow ) +{ + CKEDITOR.dom.domObject.call( this, domWindow ); +}; + +CKEDITOR.dom.window.prototype = new CKEDITOR.dom.domObject(); + +CKEDITOR.tools.extend( CKEDITOR.dom.window.prototype, + /** @lends CKEDITOR.dom.window.prototype */ + { + /** + * Moves the selection focus to this window. + * @function + * @example + * var win = new CKEDITOR.dom.window( window ); + * win.focus(); + */ + focus : function() + { + // Webkit is sometimes failed to focus iframe, blur it first(#3835). + if ( CKEDITOR.env.webkit && this.$.parent ) + this.$.parent.focus(); + this.$.focus(); + }, + + /** + * Gets the width and height of this window's viewable area. + * @function + * @returns {Object} An object with the "width" and "height" + * properties containing the size. + * @example + * var win = new CKEDITOR.dom.window( window ); + * var size = win.getViewPaneSize(); + * alert( size.width ); + * alert( size.height ); + */ + getViewPaneSize : function() + { + var doc = this.$.document, + stdMode = doc.compatMode == 'CSS1Compat'; + return { + width : ( stdMode ? doc.documentElement.clientWidth : doc.body.clientWidth ) || 0, + height : ( stdMode ? doc.documentElement.clientHeight : doc.body.clientHeight ) || 0 + }; + }, + + /** + * Gets the current position of the window's scroll. + * @function + * @returns {Object} An object with the "x" and "y" properties + * containing the scroll position. + * @example + * var win = new CKEDITOR.dom.window( window ); + * var pos = win.getScrollPosition(); + * alert( pos.x ); + * alert( pos.y ); + */ + getScrollPosition : function() + { + var $ = this.$; + + if ( 'pageXOffset' in $ ) + { + return { + x : $.pageXOffset || 0, + y : $.pageYOffset || 0 + }; + } + else + { + var doc = $.document; + return { + x : doc.documentElement.scrollLeft || doc.body.scrollLeft || 0, + y : doc.documentElement.scrollTop || doc.body.scrollTop || 0 + }; + } + } + }); diff --git a/htdocs/includes/ckeditor/_source/core/dtd.js b/htdocs/includes/ckeditor/_source/core/dtd.js new file mode 100755 index 00000000000..51293a0f652 --- /dev/null +++ b/htdocs/includes/ckeditor/_source/core/dtd.js @@ -0,0 +1,266 @@ +/* +Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @fileOverview Defines the {@link CKEDITOR.dtd} object, which holds the DTD + * mapping for XHTML 1.0 Transitional. This file was automatically + * generated from the file: xhtml1-transitional.dtd. + */ + +/** + * @namespace Holds and object representation of the HTML DTD to be used by the + * editor in its internal operations.
      + *
      + * Each element in the DTD is represented by a property in this object. Each + * property contains the list of elements that can be contained by the element. + * Text is represented by the "#" property.
      + *
      + * Several special grouping properties are also available. Their names start + * with the "$" character. + * @example + * // Check if "div" can be contained in a "p" element. + * alert( !!CKEDITOR.dtd[ 'p' ][ 'div' ] ); "false" + * @example + * // Check if "p" can be contained in a "div" element. + * alert( !!CKEDITOR.dtd[ 'div' ][ 'p' ] ); "true" + * @example + * // Check if "p" is a block element. + * alert( !!CKEDITOR.dtd.$block[ 'p' ] ); "true" + */ +CKEDITOR.dtd = (function() +{ + var X = CKEDITOR.tools.extend, + + A = {isindex:1,fieldset:1}, + B = {input:1,button:1,select:1,textarea:1,label:1}, + C = X({a:1},B), + D = X({iframe:1},C), + E = {hr:1,ul:1,menu:1,div:1,section:1,header:1,footer:1,nav:1,article:1,aside:1,figure:1,dialog:1,hgroup:1,mark:1,time:1,meter:1,command:1,keygen:1,output:1,progress:1,audio:1,video:1,details:1,datagrid:1,datalist:1,blockquote:1,noscript:1,table:1,center:1,address:1,dir:1,pre:1,h5:1,dl:1,h4:1,noframes:1,h6:1,ol:1,h1:1,h3:1,h2:1}, + F = {ins:1,del:1,script:1,style:1}, + G = X({b:1,acronym:1,bdo:1,'var':1,'#':1,abbr:1,code:1,br:1,i:1,cite:1,kbd:1,u:1,strike:1,s:1,tt:1,strong:1,q:1,samp:1,em:1,dfn:1,span:1,wbr:1},F), + H = X({sub:1,img:1,object:1,sup:1,basefont:1,map:1,applet:1,font:1,big:1,small:1,mark:1},G), + I = X({p:1},H), + J = X({iframe:1},H,B), + K = {img:1,noscript:1,br:1,kbd:1,center:1,button:1,basefont:1,h5:1,h4:1,samp:1,h6:1,ol:1,h1:1,h3:1,h2:1,form:1,font:1,'#':1,select:1,menu:1,ins:1,abbr:1,label:1,code:1,table:1,script:1,cite:1,input:1,iframe:1,strong:1,textarea:1,noframes:1,big:1,small:1,span:1,hr:1,sub:1,bdo:1,'var':1,div:1,section:1,header:1,footer:1,nav:1,article:1,aside:1,figure:1,dialog:1,hgroup:1,mark:1,time:1,meter:1,menu:1,command:1,keygen:1,output:1,progress:1,audio:1,video:1,details:1,datagrid:1,datalist:1,object:1,sup:1,strike:1,dir:1,map:1,dl:1,applet:1,del:1,isindex:1,fieldset:1,ul:1,b:1,acronym:1,a:1,blockquote:1,i:1,u:1,s:1,tt:1,address:1,q:1,pre:1,p:1,em:1,dfn:1}, + + L = X({a:1},J), + M = {tr:1}, + N = {'#':1}, + O = X({param:1},K), + P = X({form:1},A,D,E,I), + Q = {li:1}, + R = {style:1,script:1}, + S = {base:1,link:1,meta:1,title:1}, + T = X(S,R), + U = {head:1,body:1}, + V = {html:1}; + + var block = {address:1,blockquote:1,center:1,dir:1,div:1,section:1,header:1,footer:1,nav:1,article:1,aside:1,figure:1,dialog:1,hgroup:1,time:1,meter:1,menu:1,command:1,keygen:1,output:1,progress:1,audio:1,video:1,details:1,datagrid:1,datalist:1,dl:1,fieldset:1,form:1,h1:1,h2:1,h3:1,h4:1,h5:1,h6:1,hr:1,isindex:1,noframes:1,ol:1,p:1,pre:1,table:1,ul:1}; + + return /** @lends CKEDITOR.dtd */ { + + // The "$" items have been added manually. + + // List of elements living outside body. + $nonBodyContent: X(V,U,S), + + /** + * List of block elements, like "p" or "div". + * @type Object + * @example + */ + $block : block, + + /** + * List of block limit elements. + * @type Object + * @example + */ + $blockLimit : { body:1,div:1,section:1,header:1,footer:1,nav:1,article:1,aside:1,figure:1,dialog:1,hgroup:1,time:1,meter:1,menu:1,command:1,keygen:1,output:1,progress:1,audio:1,video:1,details:1,datagrid:1,datalist:1,td:1,th:1,caption:1,form:1 }, + + /** + * List of inline (<span> like) elements. + */ + $inline : L, // Just like span. + + /** + * list of elements that can be children at <body>. + */ + $body : X({script:1,style:1}, block), + + $cdata : {script:1,style:1}, + + /** + * List of empty (self-closing) elements, like "br" or "img". + * @type Object + * @example + */ + $empty : {area:1,base:1,br:1,col:1,hr:1,img:1,input:1,link:1,meta:1,param:1,wbr:1}, + + /** + * List of list item elements, like "li" or "dd". + * @type Object + * @example + */ + $listItem : {dd:1,dt:1,li:1}, + + /** + * List of list root elements. + * @type Object + * @example + */ + $list: {ul:1,ol:1,dl:1}, + + /** + * Elements that accept text nodes, but are not possible to edit into + * the browser. + * @type Object + * @example + */ + $nonEditable : {applet:1,button:1,embed:1,iframe:1,map:1,object:1,option:1,script:1,textarea:1,param:1,audio:1,video:1}, + + /** + * List of block tags with each one a singleton element lives in the corresponding structure for description. + */ + $captionBlock : { caption:1, legend:1 }, + + /** + * List of elements that can be ignored if empty, like "b" or "span". + * @type Object + * @example + */ + $removeEmpty : {abbr:1,acronym:1,address:1,b:1,bdo:1,big:1,cite:1,code:1,del:1,dfn:1,em:1,font:1,i:1,ins:1,label:1,kbd:1,q:1,s:1,samp:1,small:1,span:1,strike:1,strong:1,sub:1,sup:1,tt:1,u:1,'var':1,mark:1}, + + /** + * List of elements that have tabindex set to zero by default. + * @type Object + * @example + */ + $tabIndex : {a:1,area:1,button:1,input:1,object:1,select:1,textarea:1}, + + /** + * List of elements used inside the "table" element, like "tbody" or "td". + * @type Object + * @example + */ + $tableContent : {caption:1,col:1,colgroup:1,tbody:1,td:1,tfoot:1,th:1,thead:1,tr:1}, + + html: U, + head: T, + style: N, + script: N, + body: P, + base: {}, + link: {}, + meta: {}, + title: N, + col : {}, + tr : {td:1,th:1}, + img : {}, + colgroup : {col:1}, + noscript : P, + td : P, + br : {}, + wbr : {}, + th : P, + center : P, + kbd : L, + button : X(I,E), + basefont : {}, + h5 : L, + h4 : L, + samp : L, + h6 : L, + ol : Q, + h1 : L, + h3 : L, + option : N, + h2 : L, + form : X(A,D,E,I), + select : {optgroup:1,option:1}, + font : L, + ins : L, + menu : Q, + abbr : L, + label : L, + table : {thead:1,col:1,tbody:1,tr:1,colgroup:1,caption:1,tfoot:1}, + code : L, + tfoot : M, + cite : L, + li : P, + input : {}, + iframe : P, + strong : L, + textarea : N, + noframes : P, + big : L, + small : L, + span : L, + hr : {}, + dt : L, + sub : L, + optgroup : {option:1}, + param : {}, + bdo : L, + 'var' : L, + div : P, + object : O, + sup : L, + dd : P, + strike : L, + area : {}, + dir : Q, + map : X({area:1,form:1,p:1},A,F,E), + applet : O, + dl : {dt:1,dd:1}, + del : L, + isindex : {}, + fieldset : X({legend:1},K), + thead : M, + ul : Q, + acronym : L, + b : L, + a : J, + blockquote : P, + caption : L, + i : L, + u : L, + tbody : M, + s : L, + address : X(D,I), + tt : L, + legend : L, + q : L, + pre : X(G,C), + p : L, + em : L, + dfn : L, + //HTML5 + section : P, + header : P, + footer : P, + nav : P, + article : P, + aside : P, + figure: P, + dialog : P, + hgroup : P, + mark : L, + time : L, + meter : L, + menu : L, + command : L, + keygen : L, + output : L, + progress : O, + audio : O, + video : O, + details : O, + datagrid : O, + datalist : O + }; +})(); + +// PACKAGER_RENAME( CKEDITOR.dtd ) diff --git a/htdocs/includes/ckeditor/_source/core/editor.js b/htdocs/includes/ckeditor/_source/core/editor.js new file mode 100755 index 00000000000..ff17efb9a96 --- /dev/null +++ b/htdocs/includes/ckeditor/_source/core/editor.js @@ -0,0 +1,1059 @@ +/* +Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @fileOverview Defines the {@link CKEDITOR.editor} class, which represents an + * editor instance. + */ + +(function() +{ + // The counter for automatic instance names. + var nameCounter = 0; + + var getNewName = function() + { + var name = 'editor' + ( ++nameCounter ); + return ( CKEDITOR.instances && CKEDITOR.instances[ name ] ) ? getNewName() : name; + }; + + // ##### START: Config Privates + + // These function loads custom configuration files and cache the + // CKEDITOR.editorConfig functions defined on them, so there is no need to + // download them more than once for several instances. + var loadConfigLoaded = {}; + var loadConfig = function( editor ) + { + var customConfig = editor.config.customConfig; + + // Check if there is a custom config to load. + if ( !customConfig ) + return false; + + customConfig = CKEDITOR.getUrl( customConfig ); + + var loadedConfig = loadConfigLoaded[ customConfig ] || ( loadConfigLoaded[ customConfig ] = {} ); + + // If the custom config has already been downloaded, reuse it. + if ( loadedConfig.fn ) + { + // Call the cached CKEDITOR.editorConfig defined in the custom + // config file for the editor instance depending on it. + loadedConfig.fn.call( editor, editor.config ); + + // If there is no other customConfig in the chain, fire the + // "configLoaded" event. + if ( CKEDITOR.getUrl( editor.config.customConfig ) == customConfig || !loadConfig( editor ) ) + editor.fireOnce( 'customConfigLoaded' ); + } + else + { + // Load the custom configuration file. + CKEDITOR.scriptLoader.load( customConfig, function() + { + // If the CKEDITOR.editorConfig function has been properly + // defined in the custom configuration file, cache it. + if ( CKEDITOR.editorConfig ) + loadedConfig.fn = CKEDITOR.editorConfig; + else + loadedConfig.fn = function(){}; + + // Call the load config again. This time the custom + // config is already cached and so it will get loaded. + loadConfig( editor ); + }); + } + + return true; + }; + + var initConfig = function( editor, instanceConfig ) + { + // Setup the lister for the "customConfigLoaded" event. + editor.on( 'customConfigLoaded', function() + { + if ( instanceConfig ) + { + // Register the events that may have been set at the instance + // configuration object. + if ( instanceConfig.on ) + { + for ( var eventName in instanceConfig.on ) + { + editor.on( eventName, instanceConfig.on[ eventName ] ); + } + } + + // Overwrite the settings from the in-page config. + CKEDITOR.tools.extend( editor.config, instanceConfig, true ); + + delete editor.config.on; + } + + onConfigLoaded( editor ); + }); + + // The instance config may override the customConfig setting to avoid + // loading the default ~/config.js file. + if ( instanceConfig && instanceConfig.customConfig != undefined ) + editor.config.customConfig = instanceConfig.customConfig; + + // Load configs from the custom configuration files. + if ( !loadConfig( editor ) ) + editor.fireOnce( 'customConfigLoaded' ); + }; + + // ##### END: Config Privates + + var onConfigLoaded = function( editor ) + { + // Set config related properties. + + var skin = editor.config.skin.split( ',' ), + skinName = skin[ 0 ], + skinPath = CKEDITOR.getUrl( skin[ 1 ] || ( + '_source/' + // @Packager.RemoveLine + 'skins/' + skinName + '/' ) ); + + /** + * The name of the skin used by this editor instance. The skin name can + * be set through the {@link CKEDITOR.config.skin} setting. + * @name CKEDITOR.editor.prototype.skinName + * @type String + * @example + * alert( editor.skinName ); // E.g. "kama" + */ + editor.skinName = skinName; + + /** + * The full URL of the skin directory. + * @name CKEDITOR.editor.prototype.skinPath + * @type String + * @example + * alert( editor.skinPath ); // E.g. "http://example.com/ckeditor/skins/kama/" + */ + editor.skinPath = skinPath; + + /** + * The CSS class name used for skin identification purposes. + * @name CKEDITOR.editor.prototype.skinClass + * @type String + * @example + * alert( editor.skinClass ); // E.g. "cke_skin_kama" + */ + editor.skinClass = 'cke_skin_' + skinName; + + /** + * The tabbing + * navigation order that has been calculated for this editor + * instance. This can be set by the {@link CKEDITOR.config.tabIndex} + * setting or taken from the tabindex attribute of the + * {@link #element} associated with the editor. + * @name CKEDITOR.editor.prototype.tabIndex + * @type Number + * @default 0 (zero) + * @example + * alert( editor.tabIndex ); // E.g. "0" + */ + editor.tabIndex = editor.config.tabIndex || editor.element.getAttribute( 'tabindex' ) || 0; + + /** + * Indicates the read-only state of this editor. This is a read-only property. + * @name CKEDITOR.editor.prototype.readOnly + * @type Boolean + * @since 3.6 + * @see CKEDITOR.editor#setReadOnly + */ + editor.readOnly = !!( editor.config.readOnly || editor.element.getAttribute( 'disabled' ) ); + + // Fire the "configLoaded" event. + editor.fireOnce( 'configLoaded' ); + + // Load language file. + loadSkin( editor ); + }; + + var loadLang = function( editor ) + { + CKEDITOR.lang.load( editor.config.language, editor.config.defaultLanguage, function( languageCode, lang ) + { + /** + * The code for the language resources that have been loaded + * for the user interface elements of this editor instance. + * @name CKEDITOR.editor.prototype.langCode + * @type String + * @example + * alert( editor.langCode ); // E.g. "en" + */ + editor.langCode = languageCode; + + /** + * An object that contains all language strings used by the editor + * interface. + * @name CKEDITOR.editor.prototype.lang + * @type CKEDITOR.lang + * @example + * alert( editor.lang.bold ); // E.g. "Negrito" (if the language is set to Portuguese) + */ + // As we'll be adding plugin specific entries that could come + // from different language code files, we need a copy of lang, + // not a direct reference to it. + editor.lang = CKEDITOR.tools.prototypedCopy( lang ); + + // We're not able to support RTL in Firefox 2 at this time. + if ( CKEDITOR.env.gecko && CKEDITOR.env.version < 10900 && editor.lang.dir == 'rtl' ) + editor.lang.dir = 'ltr'; + + editor.fire( 'langLoaded' ); + + var config = editor.config; + config.contentsLangDirection == 'ui' && ( config.contentsLangDirection = editor.lang.dir ); + + loadPlugins( editor ); + }); + }; + + var loadPlugins = function( editor ) + { + var config = editor.config, + plugins = config.plugins, + extraPlugins = config.extraPlugins, + removePlugins = config.removePlugins; + + if ( extraPlugins ) + { + // Remove them first to avoid duplications. + var removeRegex = new RegExp( '(?:^|,)(?:' + extraPlugins.replace( /\s*,\s*/g, '|' ) + ')(?=,|$)' , 'g' ); + plugins = plugins.replace( removeRegex, '' ); + + plugins += ',' + extraPlugins; + } + + if ( removePlugins ) + { + removeRegex = new RegExp( '(?:^|,)(?:' + removePlugins.replace( /\s*,\s*/g, '|' ) + ')(?=,|$)' , 'g' ); + plugins = plugins.replace( removeRegex, '' ); + } + + // Load the Adobe AIR plugin conditionally. + CKEDITOR.env.air && ( plugins += ',adobeair' ); + + // Load all plugins defined in the "plugins" setting. + CKEDITOR.plugins.load( plugins.split( ',' ), function( plugins ) + { + // The list of plugins. + var pluginsArray = []; + + // The language code to get loaded for each plugin. Null + // entries will be appended for plugins with no language files. + var languageCodes = []; + + // The list of URLs to language files. + var languageFiles = []; + + /** + * An object that contains references to all plugins used by this + * editor instance. + * @name CKEDITOR.editor.prototype.plugins + * @type Object + * @example + * alert( editor.plugins.dialog.path ); // E.g. "http://example.com/ckeditor/plugins/dialog/" + */ + editor.plugins = plugins; + + // Loop through all plugins, to build the list of language + // files to get loaded. + for ( var pluginName in plugins ) + { + var plugin = plugins[ pluginName ], + pluginLangs = plugin.lang, + pluginPath = CKEDITOR.plugins.getPath( pluginName ), + lang = null; + + // Set the plugin path in the plugin. + plugin.path = pluginPath; + + // If the plugin has "lang". + if ( pluginLangs ) + { + // Resolve the plugin language. If the current language + // is not available, get the first one (default one). + lang = ( CKEDITOR.tools.indexOf( pluginLangs, editor.langCode ) >= 0 ? editor.langCode : pluginLangs[ 0 ] ); + + if ( !plugin.langEntries || !plugin.langEntries[ lang ] ) + { + // Put the language file URL into the list of files to + // get downloaded. + languageFiles.push( CKEDITOR.getUrl( pluginPath + 'lang/' + lang + '.js' ) ); + } + else + { + CKEDITOR.tools.extend( editor.lang, plugin.langEntries[ lang ] ); + lang = null; + } + } + + // Save the language code, so we know later which + // language has been resolved to this plugin. + languageCodes.push( lang ); + + pluginsArray.push( plugin ); + } + + // Load all plugin specific language files in a row. + CKEDITOR.scriptLoader.load( languageFiles, function() + { + // Initialize all plugins that have the "beforeInit" and "init" methods defined. + var methods = [ 'beforeInit', 'init', 'afterInit' ]; + for ( var m = 0 ; m < methods.length ; m++ ) + { + for ( var i = 0 ; i < pluginsArray.length ; i++ ) + { + var plugin = pluginsArray[ i ]; + + // Uses the first loop to update the language entries also. + if ( m === 0 && languageCodes[ i ] && plugin.lang ) + CKEDITOR.tools.extend( editor.lang, plugin.langEntries[ languageCodes[ i ] ] ); + + // Call the plugin method (beforeInit and init). + if ( plugin[ methods[ m ] ] ) + plugin[ methods[ m ] ]( editor ); + } + } + + // Load the editor skin. + editor.fire( 'pluginsLoaded' ); + loadTheme( editor ); + }); + }); + }; + + var loadSkin = function( editor ) + { + CKEDITOR.skins.load( editor, 'editor', function() + { + loadLang( editor ); + }); + }; + + var loadTheme = function( editor ) + { + var theme = editor.config.theme; + CKEDITOR.themes.load( theme, function() + { + /** + * The theme used by this editor instance. + * @name CKEDITOR.editor.prototype.theme + * @type CKEDITOR.theme + * @example + * alert( editor.theme ); // E.g. "http://example.com/ckeditor/themes/default/" + */ + var editorTheme = editor.theme = CKEDITOR.themes.get( theme ); + editorTheme.path = CKEDITOR.themes.getPath( theme ); + editorTheme.build( editor ); + + if ( editor.config.autoUpdateElement ) + attachToForm( editor ); + }); + }; + + var attachToForm = function( editor ) + { + var element = editor.element; + + // If are replacing a textarea, we must + if ( editor.elementMode == CKEDITOR.ELEMENT_MODE_REPLACE && element.is( 'textarea' ) ) + { + var form = element.$.form && new CKEDITOR.dom.element( element.$.form ); + if ( form ) + { + function onSubmit() + { + editor.updateElement(); + } + form.on( 'submit',onSubmit ); + + // Setup the submit function because it doesn't fire the + // "submit" event. + if ( !form.$.submit.nodeName && !form.$.submit.length ) + { + form.$.submit = CKEDITOR.tools.override( form.$.submit, function( originalSubmit ) + { + return function() + { + editor.updateElement(); + + // For IE, the DOM submit function is not a + // function, so we need thid check. + if ( originalSubmit.apply ) + originalSubmit.apply( this, arguments ); + else + originalSubmit(); + }; + }); + } + + // Remove 'submit' events registered on form element before destroying.(#3988) + editor.on( 'destroy', function() + { + form.removeListener( 'submit', onSubmit ); + } ); + } + } + }; + + function updateCommands() + { + var command, + commands = this._.commands, + mode = this.mode; + + if ( !mode ) + return; + + for ( var name in commands ) + { + command = commands[ name ]; + command[ command.startDisabled ? 'disable' : + this.readOnly && !command.readOnly ? 'disable' : command.modes[ mode ] ? 'enable' : 'disable' ](); + } + } + + /** + * Initializes the editor instance. This function is called by the editor + * contructor (editor_basic.js). + * @private + */ + CKEDITOR.editor.prototype._init = function() + { + // Get the properties that have been saved in the editor_base + // implementation. + var element = CKEDITOR.dom.element.get( this._.element ), + instanceConfig = this._.instanceConfig; + delete this._.element; + delete this._.instanceConfig; + + this._.commands = {}; + this._.styles = []; + + /** + * The DOM element that was replaced by this editor instance. This + * element stores the editor data on load and post. + * @name CKEDITOR.editor.prototype.element + * @type CKEDITOR.dom.element + * @example + * var editor = CKEDITOR.instances.editor1; + * alert( editor.element.getName() ); // E.g. "textarea" + */ + this.element = element; + + /** + * The editor instance name. It may be the replaced element ID, name, or + * a default name using the progressive counter (editor1, + * editor2, ...). + * @name CKEDITOR.editor.prototype.name + * @type String + * @example + * var editor = CKEDITOR.instances.editor1; + * alert( editor.name ); // "editor1" + */ + this.name = ( element && ( this.elementMode == CKEDITOR.ELEMENT_MODE_REPLACE ) + && ( element.getId() || element.getNameAtt() ) ) + || getNewName(); + + if ( this.name in CKEDITOR.instances ) + throw '[CKEDITOR.editor] The instance "' + this.name + '" already exists.'; + + /** + * A unique random string assigned to each editor instance on the page. + * @name CKEDITOR.editor.prototype.id + * @type String + */ + this.id = CKEDITOR.tools.getNextId(); + + /** + * The configurations for this editor instance. It inherits all + * settings defined in (@link CKEDITOR.config}, combined with settings + * loaded from custom configuration files and those defined inline in + * the page when creating the editor. + * @name CKEDITOR.editor.prototype.config + * @type Object + * @example + * var editor = CKEDITOR.instances.editor1; + * alert( editor.config.theme ); // E.g. "default" + */ + this.config = CKEDITOR.tools.prototypedCopy( CKEDITOR.config ); + + /** + * The namespace containing UI features related to this editor instance. + * @name CKEDITOR.editor.prototype.ui + * @type CKEDITOR.ui + * @example + */ + this.ui = new CKEDITOR.ui( this ); + + /** + * Controls the focus state of this editor instance. This property + * is rarely used for normal API operations. It is mainly + * intended for developers adding UI elements to the editor interface. + * @name CKEDITOR.editor.prototype.focusManager + * @type CKEDITOR.focusManager + * @example + */ + this.focusManager = new CKEDITOR.focusManager( this ); + + CKEDITOR.fire( 'instanceCreated', null, this ); + + this.on( 'mode', updateCommands, null, null, 1 ); + this.on( 'readOnly', updateCommands, null, null, 1 ); + + initConfig( this, instanceConfig ); + }; +})(); + +CKEDITOR.tools.extend( CKEDITOR.editor.prototype, + /** @lends CKEDITOR.editor.prototype */ + { + /** + * Adds a command definition to the editor instance. Commands added with + * this function can be executed later with the {@link #execCommand} method. + * @param {String} commandName The indentifier name of the command. + * @param {CKEDITOR.commandDefinition} commandDefinition The command definition. + * @example + * editorInstance.addCommand( 'sample', + * { + * exec : function( editor ) + * { + * alert( 'Executing a command for the editor name "' + editor.name + '"!' ); + * } + * }); + */ + addCommand : function( commandName, commandDefinition ) + { + return this._.commands[ commandName ] = new CKEDITOR.command( this, commandDefinition ); + }, + + /** + * Adds a piece of CSS code to the editor which will be applied to the WYSIWYG editing document. + * This CSS would not be added to the output, and is there mainly for editor-specific editing requirements. + * Note: This function should be called before the editor is loaded to take effect. + * @param css {String} CSS text. + * @example + * editorInstance.addCss( 'body { background-color: grey; }' ); + */ + addCss : function( css ) + { + this._.styles.push( css ); + }, + + /** + * Destroys the editor instance, releasing all resources used by it. + * If the editor replaced an element, the element will be recovered. + * @param {Boolean} [noUpdate] If the instance is replacing a DOM + * element, this parameter indicates whether or not to update the + * element with the instance contents. + * @example + * alert( CKEDITOR.instances.editor1 ); // E.g "object" + * CKEDITOR.instances.editor1.destroy(); + * alert( CKEDITOR.instances.editor1 ); // "undefined" + */ + destroy : function( noUpdate ) + { + if ( !noUpdate ) + this.updateElement(); + + this.fire( 'destroy' ); + this.theme && this.theme.destroy( this ); + + CKEDITOR.remove( this ); + CKEDITOR.fire( 'instanceDestroyed', null, this ); + }, + + /** + * Executes a command associated with the editor. + * @param {String} commandName The indentifier name of the command. + * @param {Object} [data] Data to be passed to the command. + * @returns {Boolean} true if the command was executed + * successfully, otherwise false. + * @see CKEDITOR.editor.addCommand + * @example + * editorInstance.execCommand( 'bold' ); + */ + execCommand : function( commandName, data ) + { + var command = this.getCommand( commandName ); + + var eventData = + { + name: commandName, + commandData: data, + command: command + }; + + if ( command && command.state != CKEDITOR.TRISTATE_DISABLED ) + { + if ( this.fire( 'beforeCommandExec', eventData ) !== true ) + { + eventData.returnValue = command.exec( eventData.commandData ); + + // Fire the 'afterCommandExec' immediately if command is synchronous. + if ( !command.async && this.fire( 'afterCommandExec', eventData ) !== true ) + return eventData.returnValue; + } + } + + // throw 'Unknown command name "' + commandName + '"'; + return false; + }, + + /** + * Gets one of the registered commands. Note that after registering a + * command definition with {@link #addCommand}, it is + * transformed internally into an instance of + * {@link CKEDITOR.command}, which will then be returned + * by this function. + * @param {String} commandName The name of the command to be returned. + * This is the same name that is used to register the command with + * addCommand. + * @returns {CKEDITOR.command} The command object identified by the + * provided name. + */ + getCommand : function( commandName ) + { + return this._.commands[ commandName ]; + }, + + /** + * Gets the editor data. The data will be in raw format. It is the same + * data that is posted by the editor. + * @type String + * @returns (String) The editor data. + * @example + * if ( CKEDITOR.instances.editor1.getData() == '' ) + * alert( 'There is no data available' ); + */ + getData : function() + { + this.fire( 'beforeGetData' ); + + var eventData = this._.data; + + if ( typeof eventData != 'string' ) + { + var element = this.element; + if ( element && this.elementMode == CKEDITOR.ELEMENT_MODE_REPLACE ) + eventData = element.is( 'textarea' ) ? element.getValue() : element.getHtml(); + else + eventData = ''; + } + + eventData = { dataValue : eventData }; + + // Fire "getData" so data manipulation may happen. + this.fire( 'getData', eventData ); + + return eventData.dataValue; + }, + + /** + * Gets the "raw data" currently available in the editor. This is a + * fast method which returns the data as is, without processing, so it is + * not recommended to use it on resulting pages. Instead it can be used + * combined with the {@link #loadSnapshot} method in order + * to be able to automatically save the editor data from time to time + * while the user is using the editor, to avoid data loss, without risking + * performance issues. + * @see CKEDITOR.editor.getData + * @example + * alert( editor.getSnapshot() ); + */ + getSnapshot : function() + { + var data = this.fire( 'getSnapshot' ); + + if ( typeof data != 'string' ) + { + var element = this.element; + if ( element && this.elementMode == CKEDITOR.ELEMENT_MODE_REPLACE ) + data = element.is( 'textarea' ) ? element.getValue() : element.getHtml(); + } + + return data; + }, + + /** + * Loads "raw data" into the editor. The data is loaded with processing + * straight to the editing area. It should not be used as a way to load + * any kind of data, but instead in combination with + * {@link #getSnapshot} produced data. + * @see CKEDITOR.editor.setData + * @example + * var data = editor.getSnapshot(); + * editor.loadSnapshot( data ); + */ + loadSnapshot : function( snapshot ) + { + this.fire( 'loadSnapshot', snapshot ); + }, + + /** + * Sets the editor data. The data must be provided in the raw format (HTML).
      + *
      + * Note that this method is asynchronous. The callback parameter must + * be used if interaction with the editor is needed after setting the data. + * @param {String} data HTML code to replace the curent content in the + * editor. + * @param {Function} callback Function to be called after the setData + * is completed. + *@param {Boolean} internal Whether to suppress any event firing when copying data + * internally inside the editor. + * @example + * CKEDITOR.instances.editor1.setData( '<p>This is the editor data.</p>' ); + * @example + * CKEDITOR.instances.editor1.setData( '<p>Some other editor data.</p>', function() + * { + * this.checkDirty(); // true + * }); + */ + setData : function( data , callback, internal ) + { + if( callback ) + { + this.on( 'dataReady', function( evt ) + { + evt.removeListener(); + callback.call( evt.editor ); + } ); + } + + // Fire "setData" so data manipulation may happen. + var eventData = { dataValue : data }; + !internal && this.fire( 'setData', eventData ); + + this._.data = eventData.dataValue; + + !internal && this.fire( 'afterSetData', eventData ); + }, + + /** + * Puts or restores the editor into read-only state. When in read-only, + * the user is not able to change the editor contents, but can still use + * some editor features. This function sets the {@link CKEDITOR.config.readOnly} + * property of the editor, firing the {@link CKEDITOR.editor#readOnly} event.

      + * Note: the current editing area will be reloaded. + * @param {Boolean} [isReadOnly] Indicates that the editor must go + * read-only (true, default) or be restored and made editable + * (false). + * @since 3.6 + */ + setReadOnly : function( isReadOnly ) + { + isReadOnly = ( isReadOnly == undefined ) || isReadOnly; + + if ( this.readOnly != isReadOnly ) + { + this.readOnly = isReadOnly; + + // Fire the readOnly event so the editor features can update + // their state accordingly. + this.fire( 'readOnly' ); + } + }, + + /** + * Inserts HTML code into the currently selected position in the editor in WYSIWYG mode. + * @param {String} data HTML code to be inserted into the editor. + * @example + * CKEDITOR.instances.editor1.insertHtml( '<p>This is a new paragraph.</p>' ); + */ + insertHtml : function( data ) + { + this.fire( 'insertHtml', data ); + }, + + /** + * Insert text content into the currently selected position in the + * editor in WYSIWYG mode. The styles of the selected element will be applied to the inserted text. + * Spaces around the text will be leaving untouched. + * Note: two subsequent line-breaks will introduce one paragraph. This depends on {@link CKEDITOR.config.enterMode}; + * A single line-break will be instead translated into one <br />. + * @since 3.5 + * @param {String} text Text to be inserted into the editor. + * @example + * CKEDITOR.instances.editor1.insertText( ' line1 \n\n line2' ); + */ + insertText : function( text ) + { + this.fire( 'insertText', text ); + }, + + /** + * Inserts an element into the currently selected position in the + * editor in WYSIWYG mode. + * @param {CKEDITOR.dom.element} element The element to be inserted + * into the editor. + * @example + * var element = CKEDITOR.dom.element.createFromHtml( '<img src="hello.png" border="0" title="Hello" />' ); + * CKEDITOR.instances.editor1.insertElement( element ); + */ + insertElement : function( element ) + { + this.fire( 'insertElement', element ); + }, + + /** + * Checks whether the current editor contents contain changes when + * compared to the contents loaded into the editor at startup, or to + * the contents available in the editor when {@link #resetDirty} + * was called. + * @returns {Boolean} "true" is the contents contain changes. + * @example + * function beforeUnload( e ) + * { + * if ( CKEDITOR.instances.editor1.checkDirty() ) + * return e.returnValue = "You will lose the changes made in the editor."; + * } + * + * if ( window.addEventListener ) + * window.addEventListener( 'beforeunload', beforeUnload, false ); + * else + * window.attachEvent( 'onbeforeunload', beforeUnload ); + */ + checkDirty : function() + { + return ( this.mayBeDirty && this._.previousValue !== this.getSnapshot() ); + }, + + /** + * Resets the "dirty state" of the editor so subsequent calls to + * {@link #checkDirty} will return false if the user will not + * have made further changes to the contents. + * @example + * alert( editor.checkDirty() ); // E.g. "true" + * editor.resetDirty(); + * alert( editor.checkDirty() ); // "false" + */ + resetDirty : function() + { + if ( this.mayBeDirty ) + this._.previousValue = this.getSnapshot(); + }, + + /** + * Updates the <textarea> element that was replaced by the editor with + * the current data available in the editor. + * @see CKEDITOR.editor.element + * @example + * CKEDITOR.instances.editor1.updateElement(); + * alert( document.getElementById( 'editor1' ).value ); // The current editor data. + */ + updateElement : function() + { + var element = this.element; + if ( element && this.elementMode == CKEDITOR.ELEMENT_MODE_REPLACE ) + { + var data = this.getData(); + + if ( this.config.htmlEncodeOutput ) + data = CKEDITOR.tools.htmlEncode( data ); + + if ( element.is( 'textarea' ) ) + element.setValue( data ); + else + element.setHtml( data ); + } + } + }); + +CKEDITOR.on( 'loaded', function() + { + // Run the full initialization for pending editors. + var pending = CKEDITOR.editor._pending; + if ( pending ) + { + delete CKEDITOR.editor._pending; + + for ( var i = 0 ; i < pending.length ; i++ ) + pending[ i ]._init(); + } + }); + +/** + * Whether to escape HTML when the editor updates the original input element. + * @name CKEDITOR.config.htmlEncodeOutput + * @since 3.1 + * @type Boolean + * @default false + * @example + * config.htmlEncodeOutput = true; + */ + +/** + * If true, makes the editor start in read-only state. Otherwise, it will check + * if the linked <textarea> element has the disabled attribute. + * @name CKEDITOR.config.readOnly + * @see CKEDITOR.editor#setReadOnly + * @type Boolean + * @default false + * @since 3.6 + * @example + * config.readOnly = true; + */ + +/** + * Fired when a CKEDITOR instance is created, but still before initializing it. + * To interact with a fully initialized instance, use the + * {@link CKEDITOR#instanceReady} event instead. + * @name CKEDITOR#instanceCreated + * @event + * @param {CKEDITOR.editor} editor The editor instance that has been created. + */ + +/** + * Fired when a CKEDITOR instance is destroyed. + * @name CKEDITOR#instanceDestroyed + * @event + * @param {CKEDITOR.editor} editor The editor instance that has been destroyed. + */ + +/** + * Fired when the language is loaded into the editor instance. + * @name CKEDITOR.editor#langLoaded + * @event + * @since 3.6.1 + * @param {CKEDITOR.editor} editor This editor instance. + */ + +/** + * Fired when all plugins are loaded and initialized into the editor instance. + * @name CKEDITOR.editor#pluginsLoaded + * @event + * @param {CKEDITOR.editor} editor This editor instance. + */ + +/** + * Fired before the command execution when {@link #execCommand} is called. + * @name CKEDITOR.editor#beforeCommandExec + * @event + * @param {CKEDITOR.editor} editor This editor instance. + * @param {String} data.name The command name. + * @param {Object} data.commandData The data to be sent to the command. This + * can be manipulated by the event listener. + * @param {CKEDITOR.command} data.command The command itself. + */ + +/** + * Fired after the command execution when {@link #execCommand} is called. + * @name CKEDITOR.editor#afterCommandExec + * @event + * @param {CKEDITOR.editor} editor This editor instance. + * @param {String} data.name The command name. + * @param {Object} data.commandData The data sent to the command. + * @param {CKEDITOR.command} data.command The command itself. + * @param {Object} data.returnValue The value returned by the command execution. + */ + +/** + * Fired when the custom configuration file is loaded, before the final + * configurations initialization.
      + *
      + * Custom configuration files can be loaded thorugh the + * {@link CKEDITOR.config.customConfig} setting. Several files can be loaded + * by changing this setting. + * @name CKEDITOR.editor#customConfigLoaded + * @event + * @param {CKEDITOR.editor} editor This editor instance. + */ + +/** + * Fired once the editor configuration is ready (loaded and processed). + * @name CKEDITOR.editor#configLoaded + * @event + * @param {CKEDITOR.editor} editor This editor instance. + */ + +/** + * Fired when this editor instance is destroyed. The editor at this + * point is not usable and this event should be used to perform the clean-up + * in any plugin. + * @name CKEDITOR.editor#destroy + * @event + */ + +/** + * Internal event to get the current data. + * @name CKEDITOR.editor#beforeGetData + * @event + */ + +/** + * Internal event to perform the #getSnapshot call. + * @name CKEDITOR.editor#getSnapshot + * @event + */ + +/** + * Internal event to perform the #loadSnapshot call. + * @name CKEDITOR.editor#loadSnapshot + * @event + */ + +/** + * Event fired before the #getData call returns allowing additional manipulation. + * @name CKEDITOR.editor#getData + * @event + * @param {CKEDITOR.editor} editor This editor instance. + * @param {String} data.dataValue The data that will be returned. + */ + +/** + * Event fired before the #setData call is executed allowing additional manipulation. + * @name CKEDITOR.editor#setData + * @event + * @param {CKEDITOR.editor} editor This editor instance. + * @param {String} data.dataValue The data that will be used. + */ + +/** + * Event fired at the end of the #setData call execution. Usually it is better to use the + * {@link CKEDITOR.editor.prototype.dataReady} event. + * @name CKEDITOR.editor#afterSetData + * @event + * @param {CKEDITOR.editor} editor This editor instance. + * @param {String} data.dataValue The data that has been set. + */ + +/** + * Internal event to perform the #insertHtml call + * @name CKEDITOR.editor#insertHtml + * @event + * @param {CKEDITOR.editor} editor This editor instance. + * @param {String} data The HTML to insert. + */ + +/** + * Internal event to perform the #insertText call + * @name CKEDITOR.editor#insertText + * @event + * @param {CKEDITOR.editor} editor This editor instance. + * @param {String} text The text to insert. + */ + +/** + * Internal event to perform the #insertElement call + * @name CKEDITOR.editor#insertElement + * @event + * @param {CKEDITOR.editor} editor This editor instance. + * @param {Object} element The element to insert. + */ + +/** + * Event fired after the {@link CKEDITOR.editor#readOnly} property changes. + * @name CKEDITOR.editor#readOnly + * @event + * @since 3.6 + * @param {CKEDITOR.editor} editor This editor instance. + */ diff --git a/htdocs/includes/ckeditor/_source/core/editor_basic.js b/htdocs/includes/ckeditor/_source/core/editor_basic.js new file mode 100755 index 00000000000..8d109c82de9 --- /dev/null +++ b/htdocs/includes/ckeditor/_source/core/editor_basic.js @@ -0,0 +1,186 @@ +/* +Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +if ( !CKEDITOR.editor ) +{ + /** + * No element is linked to the editor instance. + * @constant + * @example + */ + CKEDITOR.ELEMENT_MODE_NONE = 0; + + /** + * The element is to be replaced by the editor instance. + * @constant + * @example + */ + CKEDITOR.ELEMENT_MODE_REPLACE = 1; + + /** + * The editor is to be created inside the element. + * @constant + * @example + */ + CKEDITOR.ELEMENT_MODE_APPENDTO = 2; + + /** + * Creates an editor class instance. This constructor should be rarely + * used, in favor of the {@link CKEDITOR} editor creation functions. + * @ class Represents an editor instance. + * @param {Object} instanceConfig Configuration values for this specific + * instance. + * @param {CKEDITOR.dom.element} [element] The element linked to this + * instance. + * @param {Number} [mode] The mode in which the element is linked to this + * instance. See {@link #elementMode}. + * @param {String} [data] Since 3.3. Initial value for the instance. + * @augments CKEDITOR.event + * @example + */ + CKEDITOR.editor = function( instanceConfig, element, mode, data ) + { + this._ = + { + // Save the config to be processed later by the full core code. + instanceConfig : instanceConfig, + element : element, + data : data + }; + + /** + * The mode in which the {@link #element} is linked to this editor + * instance. It can be any of the following values: + *
        + *
      • {@link CKEDITOR.ELEMENT_MODE_NONE}: No element is linked to the + * editor instance.
      • + *
      • {@link CKEDITOR.ELEMENT_MODE_REPLACE}: The element is to be + * replaced by the editor instance.
      • + *
      • {@link CKEDITOR.ELEMENT_MODE_APPENDTO}: The editor is to be + * created inside the element.
      • + *
      + * @name CKEDITOR.editor.prototype.elementMode + * @type Number + * @example + * var editor = CKEDITOR.replace( 'editor1' ); + * alert( editor.elementMode ); "1" + */ + this.elementMode = mode || CKEDITOR.ELEMENT_MODE_NONE; + + // Call the CKEDITOR.event constructor to initialize this instance. + CKEDITOR.event.call( this ); + + this._init(); + }; + + /** + * Replaces a <textarea> or a DOM element (DIV) with a CKEditor + * instance. For textareas, the initial value in the editor will be the + * textarea value. For DOM elements, their innerHTML will be used + * instead. We recommend using TEXTAREA and DIV elements only. Do not use + * this function directly. Use {@link CKEDITOR.replace} instead. + * @param {Object|String} elementOrIdOrName The DOM element (textarea), its + * ID or name. + * @param {Object} [config] The specific configurations to apply to this + * editor instance. Configurations set here will override global CKEditor + * settings. + * @returns {CKEDITOR.editor} The editor instance created. + * @example + */ + CKEDITOR.editor.replace = function( elementOrIdOrName, config ) + { + var element = elementOrIdOrName; + + if ( typeof element != 'object' ) + { + // Look for the element by id. We accept any kind of element here. + element = document.getElementById( elementOrIdOrName ); + + // Elements that should go into head are unacceptable (#6791). + if ( element && element.tagName.toLowerCase() in {style:1,script:1,base:1,link:1,meta:1,title:1} ) + element = null; + + // If not found, look for elements by name. In this case we accept only + // textareas. + if ( !element ) + { + var i = 0, + textareasByName = document.getElementsByName( elementOrIdOrName ); + + while ( ( element = textareasByName[ i++ ] ) && element.tagName.toLowerCase() != 'textarea' ) + { /*jsl:pass*/ } + } + + if ( !element ) + throw '[CKEDITOR.editor.replace] The element with id or name "' + elementOrIdOrName + '" was not found.'; + } + + // Do not replace the textarea right now, just hide it. The effective + // replacement will be done by the _init function. + element.style.visibility = 'hidden'; + + // Create the editor instance. + return new CKEDITOR.editor( config, element, CKEDITOR.ELEMENT_MODE_REPLACE ); + }; + + /** + * Creates a new editor instance inside a specific DOM element. Do not use + * this function directly. Use {@link CKEDITOR.appendTo} instead. + * @param {Object|String} elementOrId The DOM element or its ID. + * @param {Object} [config] The specific configurations to apply to this + * editor instance. Configurations set here will override global CKEditor + * settings. + * @param {String} [data] Since 3.3. Initial value for the instance. + * @returns {CKEDITOR.editor} The editor instance created. + * @example + */ + CKEDITOR.editor.appendTo = function( elementOrId, config, data ) + { + var element = elementOrId; + if ( typeof element != 'object' ) + { + element = document.getElementById( elementOrId ); + + if ( !element ) + throw '[CKEDITOR.editor.appendTo] The element with id "' + elementOrId + '" was not found.'; + } + + // Create the editor instance. + return new CKEDITOR.editor( config, element, CKEDITOR.ELEMENT_MODE_APPENDTO, data ); + }; + + CKEDITOR.editor.prototype = + { + /** + * Initializes the editor instance. This function will be overriden by the + * full CKEDITOR.editor implementation (editor.js). + * @private + */ + _init : function() + { + var pending = CKEDITOR.editor._pending || ( CKEDITOR.editor._pending = [] ); + pending.push( this ); + }, + + // Both fire and fireOnce will always pass this editor instance as the + // "editor" param in CKEDITOR.event.fire. So, we override it to do that + // automaticaly. + + /** @ignore */ + fire : function( eventName, data ) + { + return CKEDITOR.event.prototype.fire.call( this, eventName, data, this ); + }, + + /** @ignore */ + fireOnce : function( eventName, data ) + { + return CKEDITOR.event.prototype.fireOnce.call( this, eventName, data, this ); + } + }; + + // "Inherit" (copy actually) from CKEDITOR.event. + CKEDITOR.event.implementOn( CKEDITOR.editor.prototype, true ); +} diff --git a/htdocs/includes/ckeditor/_source/core/env.js b/htdocs/includes/ckeditor/_source/core/env.js new file mode 100755 index 00000000000..9b73aead513 --- /dev/null +++ b/htdocs/includes/ckeditor/_source/core/env.js @@ -0,0 +1,291 @@ +/* +Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @fileOverview Defines the {@link CKEDITOR.env} object, which constains + * environment and browser information. + */ + +if ( !CKEDITOR.env ) +{ + /** + * @namespace Environment and browser information. + */ + CKEDITOR.env = (function() + { + var agent = navigator.userAgent.toLowerCase(); + var opera = window.opera; + + var env = + /** @lends CKEDITOR.env */ + { + /** + * Indicates that CKEditor is running on Internet Explorer. + * @type Boolean + * @example + * if ( CKEDITOR.env.ie ) + * alert( "I'm on IE!" ); + */ + ie : /*@cc_on!@*/false, + + /** + * Indicates that CKEditor is running on Opera. + * @type Boolean + * @example + * if ( CKEDITOR.env.opera ) + * alert( "I'm on Opera!" ); + */ + opera : ( !!opera && opera.version ), + + /** + * Indicates that CKEditor is running on a WebKit based browser, like + * Safari. + * @type Boolean + * @example + * if ( CKEDITOR.env.webkit ) + * alert( "I'm on WebKit!" ); + */ + webkit : ( agent.indexOf( ' applewebkit/' ) > -1 ), + + /** + * Indicates that CKEditor is running on Adobe AIR. + * @type Boolean + * @example + * if ( CKEDITOR.env.air ) + * alert( "I'm on AIR!" ); + */ + air : ( agent.indexOf( ' adobeair/' ) > -1 ), + + /** + * Indicates that CKEditor is running on Macintosh. + * @type Boolean + * @example + * if ( CKEDITOR.env.mac ) + * alert( "I love apples!" ); + */ + mac : ( agent.indexOf( 'macintosh' ) > -1 ), + + /** + * Indicates that CKEditor is running on a quirks mode environemnt. + * @type Boolean + * @example + * if ( CKEDITOR.env.quirks ) + * alert( "Nooooo!" ); + */ + quirks : ( document.compatMode == 'BackCompat' ), + + /** + * Indicates that CKEditor is running on a mobile like environemnt. + * @type Boolean + * @example + * if ( CKEDITOR.env.mobile ) + * alert( "I'm running with CKEditor today!" ); + */ + mobile : ( agent.indexOf( 'mobile' ) > -1 ), + + /** + * Indicates that the browser has a custom domain enabled. This has + * been set with "document.domain". + * @returns {Boolean} "true" if a custom domain is enabled. + * @example + * if ( CKEDITOR.env.isCustomDomain() ) + * alert( "I'm in a custom domain!" ); + */ + isCustomDomain : function() + { + if ( !this.ie ) + return false; + + var domain = document.domain, + hostname = window.location.hostname; + + return domain != hostname && + domain != ( '[' + hostname + ']' ); // IPv6 IP support (#5434) + }, + + /** + * Indicates that page is running under an encrypted connection. + * @returns {Boolean} "true" if the page has an encrypted connection. + * @example + * if ( CKEDITOR.env.secure ) + * alert( "I'm in SSL!" ); + */ + secure : location.protocol == 'https:' + }; + + /** + * Indicates that CKEditor is running on a Gecko based browser, like + * Firefox. + * @name CKEDITOR.env.gecko + * @type Boolean + * @example + * if ( CKEDITOR.env.gecko ) + * alert( "I'm riding a gecko!" ); + */ + env.gecko = ( navigator.product == 'Gecko' && !env.webkit && !env.opera ); + + var version = 0; + + // Internet Explorer 6.0+ + if ( env.ie ) + { + version = parseFloat( agent.match( /msie (\d+)/ )[1] ); + + /** + * Indicates that CKEditor is running on Internet Explorer 8. + * @name CKEDITOR.env.ie8 + * @type Boolean + * @example + * if ( CKEDITOR.env.ie8 ) + * alert( "I'm on IE8!" ); + */ + env.ie8 = !!document.documentMode; + + /** + * Indicates that CKEditor is running on Internet Explorer 8 on + * standards mode. + * @name CKEDITOR.env.ie8Compat + * @type Boolean + * @example + * if ( CKEDITOR.env.ie8Compat ) + * alert( "Now I'm on IE8, for real!" ); + */ + env.ie8Compat = document.documentMode == 8; + + /** + * Indicates that CKEditor is running on Internet Explorer 9's standards mode. + * @name CKEDITOR.env.ie9Compat + * @type Boolean + * @example + * if ( CKEDITOR.env.ie9Compat ) + * alert( "IE9, the beauty of the web!" ); + */ + env.ie9Compat = document.documentMode == 9; + + /** + * Indicates that CKEditor is running on an IE7-like environment, which + * includes IE7 itself and IE8's IE7 document mode. + * @name CKEDITOR.env.ie7Compat + * @type Boolean + * @example + * if ( CKEDITOR.env.ie8Compat ) + * alert( "I'm on IE7 or on an IE7 like IE8!" ); + */ + env.ie7Compat = ( ( version == 7 && !document.documentMode ) + || document.documentMode == 7 ); + + /** + * Indicates that CKEditor is running on an IE6-like environment, which + * includes IE6 itself and IE7 and IE8 quirks mode. + * @name CKEDITOR.env.ie6Compat + * @type Boolean + * @example + * if ( CKEDITOR.env.ie6Compat ) + * alert( "I'm on IE6 or quirks mode!" ); + */ + env.ie6Compat = ( version < 7 || env.quirks ); + } + + // Gecko. + if ( env.gecko ) + { + var geckoRelease = agent.match( /rv:([\d\.]+)/ ); + if ( geckoRelease ) + { + geckoRelease = geckoRelease[1].split( '.' ); + version = geckoRelease[0] * 10000 + ( geckoRelease[1] || 0 ) * 100 + ( geckoRelease[2] || 0 ) * 1; + } + } + + // Opera 9.50+ + if ( env.opera ) + version = parseFloat( opera.version() ); + + // Adobe AIR 1.0+ + // Checked before Safari because AIR have the WebKit rich text editor + // features from Safari 3.0.4, but the version reported is 420. + if ( env.air ) + version = parseFloat( agent.match( / adobeair\/(\d+)/ )[1] ); + + // WebKit 522+ (Safari 3+) + if ( env.webkit ) + version = parseFloat( agent.match( / applewebkit\/(\d+)/ )[1] ); + + /** + * Contains the browser version.
      + *
      + * For gecko based browsers (like Firefox) it contains the revision + * number with first three parts concatenated with a padding zero + * (e.g. for revision 1.9.0.2 we have 10900).
      + *
      + * For webkit based browser (like Safari and Chrome) it contains the + * WebKit build version (e.g. 522). + * @name CKEDITOR.env.version + * @type Boolean + * @example + * if ( CKEDITOR.env.ie && CKEDITOR.env.version <= 6 ) + * alert( "Ouch!" ); + */ + env.version = version; + + /** + * Indicates that CKEditor is running on a compatible browser. + * @name CKEDITOR.env.isCompatible + * @type Boolean + * @example + * if ( CKEDITOR.env.isCompatible ) + * alert( "Your browser is pretty cool!" ); + */ + env.isCompatible = + !env.mobile && ( + ( env.ie && version >= 6 ) || + ( env.gecko && version >= 10801 ) || + ( env.opera && version >= 9.5 ) || + ( env.air && version >= 1 ) || + ( env.webkit && version >= 522 ) || + false ); + + /** + * The CSS class to be appended on the main UI containers, making it + * easy to apply browser specific styles to it. + * @name CKEDITOR.env.cssClass + * @type String + * @example + * myDiv.className = CKEDITOR.env.cssClass; + */ + env.cssClass = + 'cke_browser_' + ( + env.ie ? 'ie' : + env.gecko ? 'gecko' : + env.opera ? 'opera' : + env.webkit ? 'webkit' : + 'unknown' ); + + if ( env.quirks ) + env.cssClass += ' cke_browser_quirks'; + + if ( env.ie ) + { + env.cssClass += ' cke_browser_ie' + ( + env.version < 7 ? '6' : + env.version >= 8 ? document.documentMode: + '7' ); + + if ( env.quirks ) + env.cssClass += ' cke_browser_iequirks'; + } + + if ( env.gecko && version < 10900 ) + env.cssClass += ' cke_browser_gecko18'; + + if ( env.air ) + env.cssClass += ' cke_browser_air'; + + return env; + })(); +} + +// PACKAGER_RENAME( CKEDITOR.env ) +// PACKAGER_RENAME( CKEDITOR.env.ie ) diff --git a/htdocs/includes/ckeditor/_source/core/event.js b/htdocs/includes/ckeditor/_source/core/event.js new file mode 100755 index 00000000000..fa190603762 --- /dev/null +++ b/htdocs/includes/ckeditor/_source/core/event.js @@ -0,0 +1,342 @@ +/* +Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @fileOverview Defines the {@link CKEDITOR.event} class, which serves as the + * base for classes and objects that require event handling features. + */ + +if ( !CKEDITOR.event ) +{ + /** + * Creates an event class instance. This constructor is rearely used, being + * the {@link #.implementOn} function used in class prototypes directly + * instead. + * @class This is a base class for classes and objects that require event + * handling features.
      + *
      + * Do not confuse this class with {@link CKEDITOR.dom.event} which is + * instead used for DOM events. The CKEDITOR.event class implements the + * internal event system used by the CKEditor to fire API related events. + * @example + */ + CKEDITOR.event = function() + {}; + + /** + * Implements the {@link CKEDITOR.event} features in an object. + * @param {Object} targetObject The object into which implement the features. + * @example + * var myObject = { message : 'Example' }; + * CKEDITOR.event.implementOn( myObject }; + * myObject.on( 'testEvent', function() + * { + * alert( this.message ); // "Example" + * }); + * myObject.fire( 'testEvent' ); + */ + CKEDITOR.event.implementOn = function( targetObject ) + { + var eventProto = CKEDITOR.event.prototype; + + for ( var prop in eventProto ) + { + if ( targetObject[ prop ] == undefined ) + targetObject[ prop ] = eventProto[ prop ]; + } + }; + + CKEDITOR.event.prototype = (function() + { + // Returns the private events object for a given object. + var getPrivate = function( obj ) + { + var _ = ( obj.getPrivate && obj.getPrivate() ) || obj._ || ( obj._ = {} ); + return _.events || ( _.events = {} ); + }; + + var eventEntry = function( eventName ) + { + this.name = eventName; + this.listeners = []; + }; + + eventEntry.prototype = + { + // Get the listener index for a specified function. + // Returns -1 if not found. + getListenerIndex : function( listenerFunction ) + { + for ( var i = 0, listeners = this.listeners ; i < listeners.length ; i++ ) + { + if ( listeners[i].fn == listenerFunction ) + return i; + } + return -1; + } + }; + + return /** @lends CKEDITOR.event.prototype */ { + /** + * Registers a listener to a specific event in the current object. + * @param {String} eventName The event name to which listen. + * @param {Function} listenerFunction The function listening to the + * event. A single {@link CKEDITOR.eventInfo} object instanced + * is passed to this function containing all the event data. + * @param {Object} [scopeObj] The object used to scope the listener + * call (the this object. If omitted, the current object is used. + * @param {Object} [listenerData] Data to be sent as the + * {@link CKEDITOR.eventInfo#listenerData} when calling the + * listener. + * @param {Number} [priority] The listener priority. Lower priority + * listeners are called first. Listeners with the same priority + * value are called in registration order. Defaults to 10. + * @example + * someObject.on( 'someEvent', function() + * { + * alert( this == someObject ); // "true" + * }); + * @example + * someObject.on( 'someEvent', function() + * { + * alert( this == anotherObject ); // "true" + * } + * , anotherObject ); + * @example + * someObject.on( 'someEvent', function( event ) + * { + * alert( event.listenerData ); // "Example" + * } + * , null, 'Example' ); + * @example + * someObject.on( 'someEvent', function() { ... } ); // 2nd called + * someObject.on( 'someEvent', function() { ... }, null, null, 100 ); // 3rd called + * someObject.on( 'someEvent', function() { ... }, null, null, 1 ); // 1st called + */ + on : function( eventName, listenerFunction, scopeObj, listenerData, priority ) + { + // Get the event entry (create it if needed). + var events = getPrivate( this ), + event = events[ eventName ] || ( events[ eventName ] = new eventEntry( eventName ) ); + + if ( event.getListenerIndex( listenerFunction ) < 0 ) + { + // Get the listeners. + var listeners = event.listeners; + + // Fill the scope. + if ( !scopeObj ) + scopeObj = this; + + // Default the priority, if needed. + if ( isNaN( priority ) ) + priority = 10; + + var me = this; + + // Create the function to be fired for this listener. + var listenerFirer = function( editor, publisherData, stopFn, cancelFn ) + { + var ev = + { + name : eventName, + sender : this, + editor : editor, + data : publisherData, + listenerData : listenerData, + stop : stopFn, + cancel : cancelFn, + removeListener : function() + { + me.removeListener( eventName, listenerFunction ); + } + }; + + listenerFunction.call( scopeObj, ev ); + + return ev.data; + }; + listenerFirer.fn = listenerFunction; + listenerFirer.priority = priority; + + // Search for the right position for this new listener, based on its + // priority. + for ( var i = listeners.length - 1 ; i >= 0 ; i-- ) + { + // Find the item which should be before the new one. + if ( listeners[ i ].priority <= priority ) + { + // Insert the listener in the array. + listeners.splice( i + 1, 0, listenerFirer ); + return; + } + } + + // If no position has been found (or zero length), put it in + // the front of list. + listeners.unshift( listenerFirer ); + } + }, + + /** + * Fires an specific event in the object. All registered listeners are + * called at this point. + * @function + * @param {String} eventName The event name to fire. + * @param {Object} [data] Data to be sent as the + * {@link CKEDITOR.eventInfo#data} when calling the + * listeners. + * @param {CKEDITOR.editor} [editor] The editor instance to send as the + * {@link CKEDITOR.eventInfo#editor} when calling the + * listener. + * @returns {Boolean|Object} A booloan indicating that the event is to be + * canceled, or data returned by one of the listeners. + * @example + * someObject.on( 'someEvent', function() { ... } ); + * someObject.on( 'someEvent', function() { ... } ); + * someObject.fire( 'someEvent' ); // both listeners are called + * @example + * someObject.on( 'someEvent', function( event ) + * { + * alert( event.data ); // "Example" + * }); + * someObject.fire( 'someEvent', 'Example' ); + */ + fire : (function() + { + // Create the function that marks the event as stopped. + var stopped = false; + var stopEvent = function() + { + stopped = true; + }; + + // Create the function that marks the event as canceled. + var canceled = false; + var cancelEvent = function() + { + canceled = true; + }; + + return function( eventName, data, editor ) + { + // Get the event entry. + var event = getPrivate( this )[ eventName ]; + + // Save the previous stopped and cancelled states. We may + // be nesting fire() calls. + var previousStopped = stopped, + previousCancelled = canceled; + + // Reset the stopped and canceled flags. + stopped = canceled = false; + + if ( event ) + { + var listeners = event.listeners; + + if ( listeners.length ) + { + // As some listeners may remove themselves from the + // event, the original array length is dinamic. So, + // let's make a copy of all listeners, so we are + // sure we'll call all of them. + listeners = listeners.slice( 0 ); + + // Loop through all listeners. + for ( var i = 0 ; i < listeners.length ; i++ ) + { + // Call the listener, passing the event data. + var retData = listeners[i].call( this, editor, data, stopEvent, cancelEvent ); + + if ( typeof retData != 'undefined' ) + data = retData; + + // No further calls is stopped or canceled. + if ( stopped || canceled ) + break; + } + } + } + + var ret = canceled || ( typeof data == 'undefined' ? false : data ); + + // Restore the previous stopped and canceled states. + stopped = previousStopped; + canceled = previousCancelled; + + return ret; + }; + })(), + + /** + * Fires an specific event in the object, releasing all listeners + * registered to that event. The same listeners are not called again on + * successive calls of it or of {@link #fire}. + * @param {String} eventName The event name to fire. + * @param {Object} [data] Data to be sent as the + * {@link CKEDITOR.eventInfo#data} when calling the + * listeners. + * @param {CKEDITOR.editor} [editor] The editor instance to send as the + * {@link CKEDITOR.eventInfo#editor} when calling the + * listener. + * @returns {Boolean|Object} A booloan indicating that the event is to be + * canceled, or data returned by one of the listeners. + * @example + * someObject.on( 'someEvent', function() { ... } ); + * someObject.fire( 'someEvent' ); // above listener called + * someObject.fireOnce( 'someEvent' ); // above listener called + * someObject.fire( 'someEvent' ); // no listeners called + */ + fireOnce : function( eventName, data, editor ) + { + var ret = this.fire( eventName, data, editor ); + delete getPrivate( this )[ eventName ]; + return ret; + }, + + /** + * Unregisters a listener function from being called at the specified + * event. No errors are thrown if the listener has not been + * registered previously. + * @param {String} eventName The event name. + * @param {Function} listenerFunction The listener function to unregister. + * @example + * var myListener = function() { ... }; + * someObject.on( 'someEvent', myListener ); + * someObject.fire( 'someEvent' ); // myListener called + * someObject.removeListener( 'someEvent', myListener ); + * someObject.fire( 'someEvent' ); // myListener not called + */ + removeListener : function( eventName, listenerFunction ) + { + // Get the event entry. + var event = getPrivate( this )[ eventName ]; + + if ( event ) + { + var index = event.getListenerIndex( listenerFunction ); + if ( index >= 0 ) + event.listeners.splice( index, 1 ); + } + }, + + /** + * Checks if there is any listener registered to a given event. + * @param {String} eventName The event name. + * @example + * var myListener = function() { ... }; + * someObject.on( 'someEvent', myListener ); + * alert( someObject.hasListeners( 'someEvent' ) ); // "true" + * alert( someObject.hasListeners( 'noEvent' ) ); // "false" + */ + hasListeners : function( eventName ) + { + var event = getPrivate( this )[ eventName ]; + return ( event && event.listeners.length > 0 ) ; + } + }; + })(); +} diff --git a/htdocs/includes/ckeditor/_source/core/eventInfo.js b/htdocs/includes/ckeditor/_source/core/eventInfo.js new file mode 100755 index 00000000000..0f313e86ec5 --- /dev/null +++ b/htdocs/includes/ckeditor/_source/core/eventInfo.js @@ -0,0 +1,120 @@ +/* +Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @fileOverview Defines the "virtual" {@link CKEDITOR.eventInfo} class, which + * contains the defintions of the event object passed to event listeners. + * This file is for documentation purposes only. + */ + +/** + * (Virtual Class) Do not call this constructor. This class is not really part + * of the API. + * @class Virtual class that illustrates the features of the event object to be + * passed to event listeners by a {@link CKEDITOR.event} based object. + * @name CKEDITOR.eventInfo + * @example + * // Do not do this. + * var myEvent = new CKEDITOR.eventInfo(); // Error: CKEDITOR.eventInfo is undefined + */ + +/** + * The event name. + * @name CKEDITOR.eventInfo.prototype.name + * @field + * @type String + * @example + * someObject.on( 'someEvent', function( event ) + * { + * alert( event.name ); // "someEvent" + * }); + * someObject.fire( 'someEvent' ); + */ + +/** + * The object that publishes (sends) the event. + * @name CKEDITOR.eventInfo.prototype.sender + * @field + * @type Object + * @example + * someObject.on( 'someEvent', function( event ) + * { + * alert( event.sender == someObject ); // "true" + * }); + * someObject.fire( 'someEvent' ); + */ + +/** + * The editor instance that holds the sender. May be the same as sender. May be + * null if the sender is not part of an editor instance, like a component + * running in standalone mode. + * @name CKEDITOR.eventInfo.prototype.editor + * @field + * @type CKEDITOR.editor + * @example + * myButton.on( 'someEvent', function( event ) + * { + * alert( event.editor == myEditor ); // "true" + * }); + * myButton.fire( 'someEvent', null, myEditor ); + */ + +/** + * Any kind of additional data. Its format and usage is event dependent. + * @name CKEDITOR.eventInfo.prototype.data + * @field + * @type Object + * @example + * someObject.on( 'someEvent', function( event ) + * { + * alert( event.data ); // "Example" + * }); + * someObject.fire( 'someEvent', 'Example' ); + */ + +/** + * Any extra data appended during the listener registration. + * @name CKEDITOR.eventInfo.prototype.listenerData + * @field + * @type Object + * @example + * someObject.on( 'someEvent', function( event ) + * { + * alert( event.listenerData ); // "Example" + * } + * , null, 'Example' ); + */ + +/** + * Indicates that no further listeners are to be called. + * @name CKEDITOR.eventInfo.prototype.stop + * @function + * @example + * someObject.on( 'someEvent', function( event ) + * { + * event.stop(); + * }); + * someObject.on( 'someEvent', function( event ) + * { + * // This one will not be called. + * }); + * alert( someObject.fire( 'someEvent' ) ); // "false" + */ + +/** + * Indicates that the event is to be cancelled (if cancelable). + * @name CKEDITOR.eventInfo.prototype.cancel + * @function + * @example + * someObject.on( 'someEvent', function( event ) + * { + * event.cancel(); + * }); + * someObject.on( 'someEvent', function( event ) + * { + * // This one will not be called. + * }); + * alert( someObject.fire( 'someEvent' ) ); // "true" + */ diff --git a/htdocs/includes/ckeditor/_source/core/focusmanager.js b/htdocs/includes/ckeditor/_source/core/focusmanager.js new file mode 100755 index 00000000000..e59900ded0c --- /dev/null +++ b/htdocs/includes/ckeditor/_source/core/focusmanager.js @@ -0,0 +1,152 @@ +/* +Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @fileOverview Defines the {@link CKEDITOR.focusManager} class, which is used + * to handle the focus on editor instances.. + */ + +/** + * Creates a focusManager class instance. + * @class Manages the focus activity in an editor instance. This class is to be + * used mainly by UI elements coders when adding interface elements that need + * to set the focus state of the editor. + * @param {CKEDITOR.editor} editor The editor instance. + * @example + * var focusManager = new CKEDITOR.focusManager( editor ); + * focusManager.focus(); + */ +CKEDITOR.focusManager = function( editor ) +{ + if ( editor.focusManager ) + return editor.focusManager; + + /** + * Indicates that the editor instance has focus. + * @type Boolean + * @example + * alert( CKEDITOR.instances.editor1.focusManager.hasFocus ); // e.g "true" + */ + this.hasFocus = false; + + /** + * Object used to hold private stuff. + * @private + */ + this._ = + { + editor : editor + }; + + return this; +}; + +CKEDITOR.focusManager.prototype = +{ + /** + * Used to indicate that the editor instance has the focus.
      + *
      + * Note that this function will not explicitelly set the focus in the + * editor (for example, making the caret blinking on it). Use + * {@link CKEDITOR.editor#focus} for it instead. + * @example + * var editor = CKEDITOR.instances.editor1; + * editor.focusManager.focus(); + */ + focus : function() + { + if ( this._.timer ) + clearTimeout( this._.timer ); + + if ( !this.hasFocus ) + { + // If another editor has the current focus, we first "blur" it. In + // this way the events happen in a more logical sequence, like: + // "focus 1" > "blur 1" > "focus 2" + // ... instead of: + // "focus 1" > "focus 2" > "blur 1" + if ( CKEDITOR.currentInstance ) + CKEDITOR.currentInstance.focusManager.forceBlur(); + + var editor = this._.editor; + + editor.container.getChild( 1 ).addClass( 'cke_focus' ); + + this.hasFocus = true; + editor.fire( 'focus' ); + } + }, + + /** + * Used to indicate that the editor instance has lost the focus.
      + *
      + * Note that this functions acts asynchronously with a delay of 100ms to + * avoid subsequent blur/focus effects. If you want the "blur" to happen + * immediately, use the {@link #forceBlur} function instead. + * @example + * var editor = CKEDITOR.instances.editor1; + * editor.focusManager.blur(); + */ + blur : function() + { + var focusManager = this; + + if ( focusManager._.timer ) + clearTimeout( focusManager._.timer ); + + focusManager._.timer = setTimeout( + function() + { + delete focusManager._.timer; + focusManager.forceBlur(); + } + , 100 ); + }, + + /** + * Used to indicate that the editor instance has lost the focus. Unlike + * {@link #blur}, this function is synchronous, marking the instance as + * "blured" immediately. + * @example + * var editor = CKEDITOR.instances.editor1; + * editor.focusManager.forceBlur(); + */ + forceBlur : function() + { + if ( this.hasFocus ) + { + var editor = this._.editor; + + editor.container.getChild( 1 ).removeClass( 'cke_focus' ); + + this.hasFocus = false; + editor.fire( 'blur' ); + } + } +}; + +/** + * Fired when the editor instance receives the input focus. + * @name CKEDITOR.editor#focus + * @event + * @param {CKEDITOR.editor} editor The editor instance. + * @example + * editor.on( 'focus', function( e ) + * { + * alert( 'The editor named ' + e.editor.name + ' is now focused' ); + * }); + */ + +/** + * Fired when the editor instance loses the input focus. + * @name CKEDITOR.editor#blur + * @event + * @param {CKEDITOR.editor} editor The editor instance. + * @example + * editor.on( 'blur', function( e ) + * { + * alert( 'The editor named ' + e.editor.name + ' lost the focus' ); + * }); + */ diff --git a/htdocs/includes/ckeditor/_source/core/htmlparser.js b/htdocs/includes/ckeditor/_source/core/htmlparser.js new file mode 100755 index 00000000000..45808e398bb --- /dev/null +++ b/htdocs/includes/ckeditor/_source/core/htmlparser.js @@ -0,0 +1,224 @@ +/* +Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * Creates a {@link CKEDITOR.htmlParser} class instance. + * @class Provides an "event like" system to parse strings of HTML data. + * @example + * var parser = new CKEDITOR.htmlParser(); + * parser.onTagOpen = function( tagName, attributes, selfClosing ) + * { + * alert( tagName ); + * }; + * parser.parse( '<p>Some <b>text</b>.</p>' ); + */ +CKEDITOR.htmlParser = function() +{ + this._ = + { + htmlPartsRegex : new RegExp( '<(?:(?:\\/([^>]+)>)|(?:!--([\\S|\\s]*?)-->)|(?:([^\\s>]+)\\s*((?:(?:"[^"]*")|(?:\'[^\']*\')|[^"\'>])*)\\/?>))', 'g' ) + }; +}; + +(function() +{ + var attribsRegex = /([\w\-:.]+)(?:(?:\s*=\s*(?:(?:"([^"]*)")|(?:'([^']*)')|([^\s>]+)))|(?=\s|$))/g, + emptyAttribs = {checked:1,compact:1,declare:1,defer:1,disabled:1,ismap:1,multiple:1,nohref:1,noresize:1,noshade:1,nowrap:1,readonly:1,selected:1}; + + CKEDITOR.htmlParser.prototype = + { + /** + * Function to be fired when a tag opener is found. This function + * should be overriden when using this class. + * @param {String} tagName The tag name. The name is guarantted to be + * lowercased. + * @param {Object} attributes An object containing all tag attributes. Each + * property in this object represent and attribute name and its + * value is the attribute value. + * @param {Boolean} selfClosing true if the tag closes itself, false if the + * tag doesn't. + * @example + * var parser = new CKEDITOR.htmlParser(); + * parser.onTagOpen = function( tagName, attributes, selfClosing ) + * { + * alert( tagName ); // e.g. "b" + * }); + * parser.parse( "<!-- Example --><b>Hello</b>" ); + */ + onTagOpen : function() {}, + + /** + * Function to be fired when a tag closer is found. This function + * should be overriden when using this class. + * @param {String} tagName The tag name. The name is guarantted to be + * lowercased. + * @example + * var parser = new CKEDITOR.htmlParser(); + * parser.onTagClose = function( tagName ) + * { + * alert( tagName ); // e.g. "b" + * }); + * parser.parse( "<!-- Example --><b>Hello</b>" ); + */ + onTagClose : function() {}, + + /** + * Function to be fired when text is found. This function + * should be overriden when using this class. + * @param {String} text The text found. + * @example + * var parser = new CKEDITOR.htmlParser(); + * parser.onText = function( text ) + * { + * alert( text ); // e.g. "Hello" + * }); + * parser.parse( "<!-- Example --><b>Hello</b>" ); + */ + onText : function() {}, + + /** + * Function to be fired when CDATA section is found. This function + * should be overriden when using this class. + * @param {String} cdata The CDATA been found. + * @example + * var parser = new CKEDITOR.htmlParser(); + * parser.onCDATA = function( cdata ) + * { + * alert( cdata ); // e.g. "var hello;" + * }); + * parser.parse( "<script>var hello;</script>" ); + */ + onCDATA : function() {}, + + /** + * Function to be fired when a commend is found. This function + * should be overriden when using this class. + * @param {String} comment The comment text. + * @example + * var parser = new CKEDITOR.htmlParser(); + * parser.onComment = function( comment ) + * { + * alert( comment ); // e.g. " Example " + * }); + * parser.parse( "<!-- Example --><b>Hello</b>" ); + */ + onComment : function() {}, + + /** + * Parses text, looking for HTML tokens, like tag openers or closers, + * or comments. This function fires the onTagOpen, onTagClose, onText + * and onComment function during its execution. + * @param {String} html The HTML to be parsed. + * @example + * var parser = new CKEDITOR.htmlParser(); + * // The onTagOpen, onTagClose, onText and onComment should be overriden + * // at this point. + * parser.parse( "<!-- Example --><b>Hello</b>" ); + */ + parse : function( html ) + { + var parts, + tagName, + nextIndex = 0, + cdata; // The collected data inside a CDATA section. + + while ( ( parts = this._.htmlPartsRegex.exec( html ) ) ) + { + var tagIndex = parts.index; + if ( tagIndex > nextIndex ) + { + var text = html.substring( nextIndex, tagIndex ); + + if ( cdata ) + cdata.push( text ); + else + this.onText( text ); + } + + nextIndex = this._.htmlPartsRegex.lastIndex; + + /* + "parts" is an array with the following items: + 0 : The entire match for opening/closing tags and comments. + 1 : Group filled with the tag name for closing tags. + 2 : Group filled with the comment text. + 3 : Group filled with the tag name for opening tags. + 4 : Group filled with the attributes part of opening tags. + */ + + // Closing tag + if ( ( tagName = parts[ 1 ] ) ) + { + tagName = tagName.toLowerCase(); + + if ( cdata && CKEDITOR.dtd.$cdata[ tagName ] ) + { + // Send the CDATA data. + this.onCDATA( cdata.join('') ); + cdata = null; + } + + if ( !cdata ) + { + this.onTagClose( tagName ); + continue; + } + } + + // If CDATA is enabled, just save the raw match. + if ( cdata ) + { + cdata.push( parts[ 0 ] ); + continue; + } + + // Opening tag + if ( ( tagName = parts[ 3 ] ) ) + { + tagName = tagName.toLowerCase(); + + // There are some tag names that can break things, so let's + // simply ignore them when parsing. (#5224) + if ( /="/.test( tagName ) ) + continue; + + var attribs = {}, + attribMatch, + attribsPart = parts[ 4 ], + selfClosing = !!( attribsPart && attribsPart.charAt( attribsPart.length - 1 ) == '/' ); + + if ( attribsPart ) + { + while ( ( attribMatch = attribsRegex.exec( attribsPart ) ) ) + { + var attName = attribMatch[1].toLowerCase(), + attValue = attribMatch[2] || attribMatch[3] || attribMatch[4] || ''; + + if ( !attValue && emptyAttribs[ attName ] ) + attribs[ attName ] = attName; + else + attribs[ attName ] = attValue; + } + } + + this.onTagOpen( tagName, attribs, selfClosing ); + + // Open CDATA mode when finding the appropriate tags. + if ( !cdata && CKEDITOR.dtd.$cdata[ tagName ] ) + cdata = []; + + continue; + } + + // Comment + if ( ( tagName = parts[ 2 ] ) ) + this.onComment( tagName ); + } + + if ( html.length > nextIndex ) + this.onText( html.substring( nextIndex, html.length ) ); + } + }; +})(); diff --git a/htdocs/includes/ckeditor/_source/core/htmlparser/basicwriter.js b/htdocs/includes/ckeditor/_source/core/htmlparser/basicwriter.js new file mode 100755 index 00000000000..010ae74e6b7 --- /dev/null +++ b/htdocs/includes/ckeditor/_source/core/htmlparser/basicwriter.js @@ -0,0 +1,145 @@ +/* +Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +CKEDITOR.htmlParser.basicWriter = CKEDITOR.tools.createClass( +{ + $ : function() + { + this._ = + { + output : [] + }; + }, + + proto : + { + /** + * Writes the tag opening part for a opener tag. + * @param {String} tagName The element name for this tag. + * @param {Object} attributes The attributes defined for this tag. The + * attributes could be used to inspect the tag. + * @example + * // Writes "<p". + * writer.openTag( 'p', { class : 'MyClass', id : 'MyId' } ); + */ + openTag : function( tagName, attributes ) + { + this._.output.push( '<', tagName ); + }, + + /** + * Writes the tag closing part for a opener tag. + * @param {String} tagName The element name for this tag. + * @param {Boolean} isSelfClose Indicates that this is a self-closing tag, + * like "br" or "img". + * @example + * // Writes ">". + * writer.openTagClose( 'p', false ); + * @example + * // Writes " />". + * writer.openTagClose( 'br', true ); + */ + openTagClose : function( tagName, isSelfClose ) + { + if ( isSelfClose ) + this._.output.push( ' />' ); + else + this._.output.push( '>' ); + }, + + /** + * Writes an attribute. This function should be called after opening the + * tag with {@link #openTagClose}. + * @param {String} attName The attribute name. + * @param {String} attValue The attribute value. + * @example + * // Writes ' class="MyClass"'. + * writer.attribute( 'class', 'MyClass' ); + */ + attribute : function( attName, attValue ) + { + // Browsers don't always escape special character in attribute values. (#4683, #4719). + if ( typeof attValue == 'string' ) + attValue = CKEDITOR.tools.htmlEncodeAttr( attValue ); + + this._.output.push( ' ', attName, '="', attValue, '"' ); + }, + + /** + * Writes a closer tag. + * @param {String} tagName The element name for this tag. + * @example + * // Writes "</p>". + * writer.closeTag( 'p' ); + */ + closeTag : function( tagName ) + { + this._.output.push( '' ); + }, + + /** + * Writes text. + * @param {String} text The text value + * @example + * // Writes "Hello Word". + * writer.text( 'Hello Word' ); + */ + text : function( text ) + { + this._.output.push( text ); + }, + + /** + * Writes a comment. + * @param {String} comment The comment text. + * @example + * // Writes "<!-- My comment -->". + * writer.comment( ' My comment ' ); + */ + comment : function( comment ) + { + this._.output.push( '' ); + }, + + /** + * Writes any kind of data to the ouput. + * @example + * writer.write( 'This is an <b>example</b>.' ); + */ + write : function( data ) + { + this._.output.push( data ); + }, + + /** + * Empties the current output buffer. + * @example + * writer.reset(); + */ + reset : function() + { + this._.output = []; + this._.indent = false; + }, + + /** + * Empties the current output buffer. + * @param {Boolean} reset Indicates that the {@link reset} function is to + * be automatically called after retrieving the HTML. + * @returns {String} The HTML written to the writer so far. + * @example + * var html = writer.getHtml(); + */ + getHtml : function( reset ) + { + var html = this._.output.join( '' ); + + if ( reset ) + this.reset(); + + return html; + } + } +}); diff --git a/htdocs/includes/ckeditor/_source/core/htmlparser/cdata.js b/htdocs/includes/ckeditor/_source/core/htmlparser/cdata.js new file mode 100755 index 00000000000..422f603eb30 --- /dev/null +++ b/htdocs/includes/ckeditor/_source/core/htmlparser/cdata.js @@ -0,0 +1,43 @@ +/* +Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +(function() +{ + + /** + * A lightweight representation of HTML text. + * @constructor + * @example + */ + CKEDITOR.htmlParser.cdata = function( value ) + { + /** + * The CDATA value. + * @type String + * @example + */ + this.value = value; + }; + + CKEDITOR.htmlParser.cdata.prototype = + { + /** + * CDATA has the same type as {@link CKEDITOR.htmlParser.text} This is + * a constant value set to {@link CKEDITOR.NODE_TEXT}. + * @type Number + * @example + */ + type : CKEDITOR.NODE_TEXT, + + /** + * Writes write the CDATA with no special manipulations. + * @param {CKEDITOR.htmlWriter} writer The writer to which write the HTML. + */ + writeHtml : function( writer ) + { + writer.write( this.value ); + } + }; +})(); diff --git a/htdocs/includes/ckeditor/_source/core/htmlparser/comment.js b/htdocs/includes/ckeditor/_source/core/htmlparser/comment.js new file mode 100755 index 00000000000..a55b489c8ba --- /dev/null +++ b/htdocs/includes/ckeditor/_source/core/htmlparser/comment.js @@ -0,0 +1,60 @@ +/* +Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * A lightweight representation of an HTML comment. + * @constructor + * @example + */ +CKEDITOR.htmlParser.comment = function( value ) +{ + /** + * The comment text. + * @type String + * @example + */ + this.value = value; + + /** @private */ + this._ = + { + isBlockLike : false + }; +}; + +CKEDITOR.htmlParser.comment.prototype = +{ + /** + * The node type. This is a constant value set to {@link CKEDITOR.NODE_COMMENT}. + * @type Number + * @example + */ + type : CKEDITOR.NODE_COMMENT, + + /** + * Writes the HTML representation of this comment to a CKEDITOR.htmlWriter. + * @param {CKEDITOR.htmlWriter} writer The writer to which write the HTML. + * @example + */ + writeHtml : function( writer, filter ) + { + var comment = this.value; + + if ( filter ) + { + if ( !( comment = filter.onComment( comment, this ) ) ) + return; + + if ( typeof comment != 'string' ) + { + comment.parent = this.parent; + comment.writeHtml( writer, filter ); + return; + } + } + + writer.comment( comment ); + } +}; diff --git a/htdocs/includes/ckeditor/_source/core/htmlparser/element.js b/htdocs/includes/ckeditor/_source/core/htmlparser/element.js new file mode 100755 index 00000000000..b1d55475058 --- /dev/null +++ b/htdocs/includes/ckeditor/_source/core/htmlparser/element.js @@ -0,0 +1,308 @@ +/* +Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * A lightweight representation of an HTML element. + * @param {String} name The element name. + * @param {Object} attributes And object holding all attributes defined for + * this element. + * @constructor + * @example + */ +CKEDITOR.htmlParser.element = function( name, attributes ) +{ + /** + * The element name. + * @type String + * @example + */ + this.name = name; + + /** + * Holds the attributes defined for this element. + * @type Object + * @example + */ + this.attributes = attributes || ( attributes = {} ); + + /** + * The nodes that are direct children of this element. + * @type Array + * @example + */ + this.children = []; + + var tagName = attributes[ 'data-cke-real-element-type' ] || name || ''; + + // Reveal the real semantic of our internal custom tag name (#6639). + var internalTag = tagName.match( /^cke:(.*)/ ); + internalTag && ( tagName = internalTag[ 1 ] ); + + var dtd = CKEDITOR.dtd, + isBlockLike = !!( dtd.$nonBodyContent[ tagName ] + || dtd.$block[ tagName ] + || dtd.$listItem[ tagName ] + || dtd.$tableContent[ tagName ] + || dtd.$nonEditable[ tagName ] + || tagName == 'br' ), + isEmpty = !!dtd.$empty[ name ]; + + this.isEmpty = isEmpty; + this.isUnknown = !dtd[ name ]; + + /** @private */ + this._ = + { + isBlockLike : isBlockLike, + hasInlineStarted : isEmpty || !isBlockLike + }; +}; + +/** + * Object presentation of CSS style declaration text. + * @param {CKEDITOR.htmlParser.element|String} elementOrStyleText A html parser element or the inline style text. + */ +CKEDITOR.htmlParser.cssStyle = function() +{ + var styleText, + arg = arguments[ 0 ], + rules = {}; + + styleText = arg instanceof CKEDITOR.htmlParser.element ? arg.attributes.style : arg; + + // html-encoded quote might be introduced by 'font-family' + // from MS-Word which confused the following regexp. e.g. + //'font-family: "Lucida, Console"' + ( styleText || '' ) + .replace( /"/g, '"' ) + .replace( /\s*([^ :;]+)\s*:\s*([^;]+)\s*(?=;|$)/g, + function( match, name, value ) + { + name == 'font-family' && ( value = value.replace( /["']/g, '' ) ); + rules[ name.toLowerCase() ] = value; + }); + + return { + + rules : rules, + + /** + * Apply the styles onto the specified element or object. + * @param {CKEDITOR.htmlParser.element|CKEDITOR.dom.element|Object} obj + */ + populate : function( obj ) + { + var style = this.toString(); + if ( style ) + { + obj instanceof CKEDITOR.dom.element ? + obj.setAttribute( 'style', style ) : + obj instanceof CKEDITOR.htmlParser.element ? + obj.attributes.style = style : + obj.style = style; + } + }, + + toString : function() + { + var output = []; + for ( var i in rules ) + rules[ i ] && output.push( i, ':', rules[ i ], ';' ); + return output.join( '' ); + } + }; +}; + +(function() +{ + // Used to sort attribute entries in an array, where the first element of + // each object is the attribute name. + var sortAttribs = function( a, b ) + { + a = a[0]; + b = b[0]; + return a < b ? -1 : a > b ? 1 : 0; + }; + + CKEDITOR.htmlParser.element.prototype = + { + /** + * The node type. This is a constant value set to {@link CKEDITOR.NODE_ELEMENT}. + * @type Number + * @example + */ + type : CKEDITOR.NODE_ELEMENT, + + /** + * Adds a node to the element children list. + * @param {Object} node The node to be added. It can be any of of the + * following types: {@link CKEDITOR.htmlParser.element}, + * {@link CKEDITOR.htmlParser.text} and + * {@link CKEDITOR.htmlParser.comment}. + * @function + * @example + */ + add : CKEDITOR.htmlParser.fragment.prototype.add, + + /** + * Clone this element. + * @returns {CKEDITOR.htmlParser.element} The element clone. + * @example + */ + clone : function() + { + return new CKEDITOR.htmlParser.element( this.name, this.attributes ); + }, + + /** + * Writes the element HTML to a CKEDITOR.htmlWriter. + * @param {CKEDITOR.htmlWriter} writer The writer to which write the HTML. + * @example + */ + writeHtml : function( writer, filter ) + { + var attributes = this.attributes; + + // Ignore cke: prefixes when writing HTML. + var element = this, + writeName = element.name, + a, newAttrName, value; + + var isChildrenFiltered; + + /** + * Providing an option for bottom-up filtering order ( element + * children to be pre-filtered before the element itself ). + */ + element.filterChildren = function() + { + if ( !isChildrenFiltered ) + { + var writer = new CKEDITOR.htmlParser.basicWriter(); + CKEDITOR.htmlParser.fragment.prototype.writeChildrenHtml.call( element, writer, filter ); + element.children = new CKEDITOR.htmlParser.fragment.fromHtml( writer.getHtml(), 0, element.clone() ).children; + isChildrenFiltered = 1; + } + }; + + if ( filter ) + { + while ( true ) + { + if ( !( writeName = filter.onElementName( writeName ) ) ) + return; + + element.name = writeName; + + if ( !( element = filter.onElement( element ) ) ) + return; + + element.parent = this.parent; + + if ( element.name == writeName ) + break; + + // If the element has been replaced with something of a + // different type, then make the replacement write itself. + if ( element.type != CKEDITOR.NODE_ELEMENT ) + { + element.writeHtml( writer, filter ); + return; + } + + writeName = element.name; + + // This indicate that the element has been dropped by + // filter but not the children. + if ( !writeName ) + { + // Fix broken parent refs. + for ( var c = 0, length = this.children.length ; c < length ; c++ ) + this.children[ c ].parent = element.parent; + + this.writeChildrenHtml.call( element, writer, isChildrenFiltered ? null : filter ); + return; + } + } + + // The element may have been changed, so update the local + // references. + attributes = element.attributes; + } + + // Open element tag. + writer.openTag( writeName, attributes ); + + // Copy all attributes to an array. + var attribsArray = []; + // Iterate over the attributes twice since filters may alter + // other attributes. + for ( var i = 0 ; i < 2; i++ ) + { + for ( a in attributes ) + { + newAttrName = a; + value = attributes[ a ]; + if ( i == 1 ) + attribsArray.push( [ a, value ] ); + else if ( filter ) + { + while ( true ) + { + if ( !( newAttrName = filter.onAttributeName( a ) ) ) + { + delete attributes[ a ]; + break; + } + else if ( newAttrName != a ) + { + delete attributes[ a ]; + a = newAttrName; + continue; + } + else + break; + } + if ( newAttrName ) + { + if ( ( value = filter.onAttribute( element, newAttrName, value ) ) === false ) + delete attributes[ newAttrName ]; + else + attributes [ newAttrName ] = value; + } + } + } + } + // Sort the attributes by name. + if ( writer.sortAttributes ) + attribsArray.sort( sortAttribs ); + + // Send the attributes. + var len = attribsArray.length; + for ( i = 0 ; i < len ; i++ ) + { + var attrib = attribsArray[ i ]; + writer.attribute( attrib[0], attrib[1] ); + } + + // Close the tag. + writer.openTagClose( writeName, element.isEmpty ); + + if ( !element.isEmpty ) + { + this.writeChildrenHtml.call( element, writer, isChildrenFiltered ? null : filter ); + // Close the element. + writer.closeTag( writeName ); + } + }, + + writeChildrenHtml : function( writer, filter ) + { + // Send children. + CKEDITOR.htmlParser.fragment.prototype.writeChildrenHtml.apply( this, arguments ); + + } + }; +})(); diff --git a/htdocs/includes/ckeditor/_source/core/htmlparser/filter.js b/htdocs/includes/ckeditor/_source/core/htmlparser/filter.js new file mode 100755 index 00000000000..a1f16e2a9e3 --- /dev/null +++ b/htdocs/includes/ckeditor/_source/core/htmlparser/filter.js @@ -0,0 +1,288 @@ +/* +Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +(function() +{ + CKEDITOR.htmlParser.filter = CKEDITOR.tools.createClass( + { + $ : function( rules ) + { + this._ = + { + elementNames : [], + attributeNames : [], + elements : { $length : 0 }, + attributes : { $length : 0 } + }; + + if ( rules ) + this.addRules( rules, 10 ); + }, + + proto : + { + addRules : function( rules, priority ) + { + if ( typeof priority != 'number' ) + priority = 10; + + // Add the elementNames. + addItemsToList( this._.elementNames, rules.elementNames, priority ); + + // Add the attributeNames. + addItemsToList( this._.attributeNames, rules.attributeNames, priority ); + + // Add the elements. + addNamedItems( this._.elements, rules.elements, priority ); + + // Add the attributes. + addNamedItems( this._.attributes, rules.attributes, priority ); + + // Add the text. + this._.text = transformNamedItem( this._.text, rules.text, priority ) || this._.text; + + // Add the comment. + this._.comment = transformNamedItem( this._.comment, rules.comment, priority ) || this._.comment; + + // Add root fragment. + this._.root = transformNamedItem( this._.root, rules.root, priority ) || this._.root; + }, + + onElementName : function( name ) + { + return filterName( name, this._.elementNames ); + }, + + onAttributeName : function( name ) + { + return filterName( name, this._.attributeNames ); + }, + + onText : function( text ) + { + var textFilter = this._.text; + return textFilter ? textFilter.filter( text ) : text; + }, + + onComment : function( commentText, comment ) + { + var textFilter = this._.comment; + return textFilter ? textFilter.filter( commentText, comment ) : commentText; + }, + + onFragment : function( element ) + { + var rootFilter = this._.root; + return rootFilter ? rootFilter.filter( element ) : element; + }, + + onElement : function( element ) + { + // We must apply filters set to the specific element name as + // well as those set to the generic $ name. So, add both to an + // array and process them in a small loop. + var filters = [ this._.elements[ '^' ], this._.elements[ element.name ], this._.elements.$ ], + filter, ret; + + for ( var i = 0 ; i < 3 ; i++ ) + { + filter = filters[ i ]; + if ( filter ) + { + ret = filter.filter( element, this ); + + if ( ret === false ) + return null; + + if ( ret && ret != element ) + return this.onNode( ret ); + + // The non-root element has been dismissed by one of the filters. + if ( element.parent && !element.name ) + break; + } + } + + return element; + }, + + onNode : function( node ) + { + var type = node.type; + + return type == CKEDITOR.NODE_ELEMENT ? this.onElement( node ) : + type == CKEDITOR.NODE_TEXT ? new CKEDITOR.htmlParser.text( this.onText( node.value ) ) : + type == CKEDITOR.NODE_COMMENT ? new CKEDITOR.htmlParser.comment( this.onComment( node.value ) ): + null; + }, + + onAttribute : function( element, name, value ) + { + var filter = this._.attributes[ name ]; + + if ( filter ) + { + var ret = filter.filter( value, element, this ); + + if ( ret === false ) + return false; + + if ( typeof ret != 'undefined' ) + return ret; + } + + return value; + } + } + }); + + function filterName( name, filters ) + { + for ( var i = 0 ; name && i < filters.length ; i++ ) + { + var filter = filters[ i ]; + name = name.replace( filter[ 0 ], filter[ 1 ] ); + } + return name; + } + + function addItemsToList( list, items, priority ) + { + if ( typeof items == 'function' ) + items = [ items ]; + + var i, j, + listLength = list.length, + itemsLength = items && items.length; + + if ( itemsLength ) + { + // Find the index to insert the items at. + for ( i = 0 ; i < listLength && list[ i ].pri < priority ; i++ ) + { /*jsl:pass*/ } + + // Add all new items to the list at the specific index. + for ( j = itemsLength - 1 ; j >= 0 ; j-- ) + { + var item = items[ j ]; + if ( item ) + { + item.pri = priority; + list.splice( i, 0, item ); + } + } + } + } + + function addNamedItems( hashTable, items, priority ) + { + if ( items ) + { + for ( var name in items ) + { + var current = hashTable[ name ]; + + hashTable[ name ] = + transformNamedItem( + current, + items[ name ], + priority ); + + if ( !current ) + hashTable.$length++; + } + } + } + + function transformNamedItem( current, item, priority ) + { + if ( item ) + { + item.pri = priority; + + if ( current ) + { + // If the current item is not an Array, transform it. + if ( !current.splice ) + { + if ( current.pri > priority ) + current = [ item, current ]; + else + current = [ current, item ]; + + current.filter = callItems; + } + else + addItemsToList( current, item, priority ); + + return current; + } + else + { + item.filter = item; + return item; + } + } + } + + // Invoke filters sequentially on the array, break the iteration + // when it doesn't make sense to continue anymore. + function callItems( currentEntry ) + { + var isNode = currentEntry.type + || currentEntry instanceof CKEDITOR.htmlParser.fragment; + + for ( var i = 0 ; i < this.length ; i++ ) + { + // Backup the node info before filtering. + if ( isNode ) + { + var orgType = currentEntry.type, + orgName = currentEntry.name; + } + + var item = this[ i ], + ret = item.apply( window, arguments ); + + if ( ret === false ) + return ret; + + // We're filtering node (element/fragment). + if ( isNode ) + { + // No further filtering if it's not anymore + // fitable for the subsequent filters. + if ( ret && ( ret.name != orgName + || ret.type != orgType ) ) + { + return ret; + } + } + // Filtering value (nodeName/textValue/attrValue). + else + { + // No further filtering if it's not + // any more values. + if ( typeof ret != 'string' ) + return ret; + } + + ret != undefined && ( currentEntry = ret ); + } + + return currentEntry; + } +})(); + +// "entities" plugin +/* +{ + text : function( text ) + { + // TODO : Process entities. + return text.toUpperCase(); + } +}; +*/ diff --git a/htdocs/includes/ckeditor/_source/core/htmlparser/fragment.js b/htdocs/includes/ckeditor/_source/core/htmlparser/fragment.js new file mode 100755 index 00000000000..bfa5cc57925 --- /dev/null +++ b/htdocs/includes/ckeditor/_source/core/htmlparser/fragment.js @@ -0,0 +1,497 @@ +/* +Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * A lightweight representation of an HTML DOM structure. + * @constructor + * @example + */ +CKEDITOR.htmlParser.fragment = function() +{ + /** + * The nodes contained in the root of this fragment. + * @type Array + * @example + * var fragment = CKEDITOR.htmlParser.fragment.fromHtml( 'Sample Text' ); + * alert( fragment.children.length ); "2" + */ + this.children = []; + + /** + * Get the fragment parent. Should always be null. + * @type Object + * @default null + * @example + */ + this.parent = null; + + /** @private */ + this._ = + { + isBlockLike : true, + hasInlineStarted : false + }; +}; + +(function() +{ + // Block-level elements whose internal structure should be respected during + // parser fixing. + var nonBreakingBlocks = CKEDITOR.tools.extend( { table:1,ul:1,ol:1,dl:1 }, CKEDITOR.dtd.table, CKEDITOR.dtd.ul, CKEDITOR.dtd.ol, CKEDITOR.dtd.dl ); + + // IE < 8 don't output the close tag on definition list items. (#6975) + var optionalCloseTags = CKEDITOR.env.ie && CKEDITOR.env.version < 8 ? { dd : 1, dt :1 } : {}; + + var listBlocks = { ol:1, ul:1 }; + + // Dtd of the fragment element, basically it accept anything except for intermediate structure, e.g. orphan
    1. . + var rootDtd = CKEDITOR.tools.extend( {}, { html: 1 }, CKEDITOR.dtd.html, CKEDITOR.dtd.body, CKEDITOR.dtd.head, { style:1,script:1 } ); + + /** + * Creates a {@link CKEDITOR.htmlParser.fragment} from an HTML string. + * @param {String} fragmentHtml The HTML to be parsed, filling the fragment. + * @param {Number} [fixForBody=false] Wrap body with specified element if needed. + * @param {CKEDITOR.htmlParser.element} contextNode Parse the html as the content of this element. + * @returns CKEDITOR.htmlParser.fragment The fragment created. + * @example + * var fragment = CKEDITOR.htmlParser.fragment.fromHtml( 'Sample Text' ); + * alert( fragment.children[0].name ); "b" + * alert( fragment.children[1].value ); " Text" + */ + CKEDITOR.htmlParser.fragment.fromHtml = function( fragmentHtml, fixForBody, contextNode ) + { + var parser = new CKEDITOR.htmlParser(), + fragment = contextNode || new CKEDITOR.htmlParser.fragment(), + pendingInline = [], + pendingBRs = [], + currentNode = fragment, + // Indicate we're inside a
       element, spaces should be touched differently.
      +			inPre = false;
      +
      +		function checkPending( newTagName )
      +		{
      +			var pendingBRsSent;
      +
      +			if ( pendingInline.length > 0 )
      +			{
      +				for ( var i = 0 ; i < pendingInline.length ; i++ )
      +				{
      +					var pendingElement = pendingInline[ i ],
      +						pendingName = pendingElement.name,
      +						pendingDtd = CKEDITOR.dtd[ pendingName ],
      +						currentDtd = currentNode.name && CKEDITOR.dtd[ currentNode.name ];
      +
      +					if ( ( !currentDtd || currentDtd[ pendingName ] ) && ( !newTagName || !pendingDtd || pendingDtd[ newTagName ] || !CKEDITOR.dtd[ newTagName ] ) )
      +					{
      +						if ( !pendingBRsSent )
      +						{
      +							sendPendingBRs();
      +							pendingBRsSent = 1;
      +						}
      +
      +						// Get a clone for the pending element.
      +						pendingElement = pendingElement.clone();
      +
      +						// Add it to the current node and make it the current,
      +						// so the new element will be added inside of it.
      +						pendingElement.parent = currentNode;
      +						currentNode = pendingElement;
      +
      +						// Remove the pending element (back the index by one
      +						// to properly process the next entry).
      +						pendingInline.splice( i, 1 );
      +						i--;
      +					}
      +				}
      +			}
      +		}
      +
      +		function sendPendingBRs()
      +		{
      +			while ( pendingBRs.length )
      +				currentNode.add( pendingBRs.shift() );
      +		}
      +
      +		/*
      +		* Beside of simply append specified element to target, this function also takes
      +		* care of other dirty lifts like forcing block in body, trimming spaces at
      +		* the block boundaries etc.
      +		*
      +		* @param {Element} element  The element to be added as the last child of {@link target}.
      +		* @param {Element} target The parent element to relieve the new node.
      +		* @param {Boolean} [moveCurrent=false] Don't change the "currentNode" global unless
      +		* there's a return point node specified on the element, otherwise move current onto {@link target} node.
      +		 */
      +		function addElement( element, target, moveCurrent )
      +		{
      +			// Ignore any element that has already been added.
      +			if ( element.previous !== undefined )
      +				return;
      +
      +			target = target || currentNode || fragment;
      +
      +			// Current element might be mangled by fix body below,
      +			// save it for restore later.
      +			var savedCurrent = currentNode;
      +
      +			// If the target is the fragment and this inline element can't go inside
      +			// body (if fixForBody).
      +			if ( fixForBody && ( !target.type || target.name == 'body' ) )
      +			{
      +				var elementName, realElementName;
      +				if ( element.attributes
      +					 && ( realElementName =
      +						  element.attributes[ 'data-cke-real-element-type' ] ) )
      +					elementName = realElementName;
      +				else
      +					elementName =  element.name;
      +
      +				if ( elementName && !( elementName in CKEDITOR.dtd.$body || elementName == 'body' || element.isOrphan ) )
      +				{
      +					// Create a 

      in the fragment. + currentNode = target; + parser.onTagOpen( fixForBody, {} ); + + // The new target now is the

      . + element.returnPoint = target = currentNode; + } + } + + // Rtrim empty spaces on block end boundary. (#3585) + if ( element._.isBlockLike + && element.name != 'pre' ) + { + + var length = element.children.length, + lastChild = element.children[ length - 1 ], + text; + if ( lastChild && lastChild.type == CKEDITOR.NODE_TEXT ) + { + if ( !( text = CKEDITOR.tools.rtrim( lastChild.value ) ) ) + element.children.length = length -1; + else + lastChild.value = text; + } + } + + target.add( element ); + + if ( element.returnPoint ) + { + currentNode = element.returnPoint; + delete element.returnPoint; + } + else + currentNode = moveCurrent ? target : savedCurrent; + } + + parser.onTagOpen = function( tagName, attributes, selfClosing, optionalClose ) + { + var element = new CKEDITOR.htmlParser.element( tagName, attributes ); + + // "isEmpty" will be always "false" for unknown elements, so we + // must force it if the parser has identified it as a selfClosing tag. + if ( element.isUnknown && selfClosing ) + element.isEmpty = true; + + // Check for optional closed elements, including browser quirks and manually opened blocks. + element.isOptionalClose = tagName in optionalCloseTags || optionalClose; + + // This is a tag to be removed if empty, so do not add it immediately. + if ( CKEDITOR.dtd.$removeEmpty[ tagName ] ) + { + pendingInline.push( element ); + return; + } + else if ( tagName == 'pre' ) + inPre = true; + else if ( tagName == 'br' && inPre ) + { + currentNode.add( new CKEDITOR.htmlParser.text( '\n' ) ); + return; + } + + if ( tagName == 'br' ) + { + pendingBRs.push( element ); + return; + } + + while( 1 ) + { + var currentName = currentNode.name; + + var currentDtd = currentName ? ( CKEDITOR.dtd[ currentName ] + || ( currentNode._.isBlockLike ? CKEDITOR.dtd.div : CKEDITOR.dtd.span ) ) + : rootDtd; + + // If the element cannot be child of the current element. + if ( !element.isUnknown && !currentNode.isUnknown && !currentDtd[ tagName ] ) + { + // Current node doesn't have a close tag, time for a close + // as this element isn't fit in. (#7497) + if ( currentNode.isOptionalClose ) + parser.onTagClose( currentName ); + // Fixing malformed nested lists by moving it into a previous list item. (#3828) + else if ( tagName in listBlocks + && currentName in listBlocks ) + { + var children = currentNode.children, + lastChild = children[ children.length - 1 ]; + + // Establish the list item if it's not existed. + if ( !( lastChild && lastChild.name == 'li' ) ) + addElement( ( lastChild = new CKEDITOR.htmlParser.element( 'li' ) ), currentNode ); + + !element.returnPoint && ( element.returnPoint = currentNode ); + currentNode = lastChild; + } + // Establish new list root for orphan list items. + else if ( tagName in CKEDITOR.dtd.$listItem && currentName != tagName ) + parser.onTagOpen( tagName == 'li' ? 'ul' : 'dl', {}, 0, 1 ); + // We're inside a structural block like table and list, AND the incoming element + // is not of the same type (e.g. td1td2), we simply add this new one before it, + // and most importantly, return back to here once this element is added, + // e.g.

      p1

      td1td2
      + else if ( currentName in nonBreakingBlocks && currentName != tagName ) + { + !element.returnPoint && ( element.returnPoint = currentNode ); + currentNode = currentNode.parent; + } + else + { + // The current element is an inline element, which + // need to be continued even after the close, so put + // it in the pending list. + if ( currentName in CKEDITOR.dtd.$inline ) + pendingInline.unshift( currentNode ); + + // The most common case where we just need to close the + // current one and append the new one to the parent. + if ( currentNode.parent ) + addElement( currentNode, currentNode.parent, 1 ); + // We've tried our best to fix the embarrassment here, while + // this element still doesn't find it's parent, mark it as + // orphan and show our tolerance to it. + else + { + element.isOrphan = 1; + break; + } + } + } + else + break; + } + + checkPending( tagName ); + sendPendingBRs(); + + element.parent = currentNode; + + if ( element.isEmpty ) + addElement( element ); + else + currentNode = element; + }; + + parser.onTagClose = function( tagName ) + { + // Check if there is any pending tag to be closed. + for ( var i = pendingInline.length - 1 ; i >= 0 ; i-- ) + { + // If found, just remove it from the list. + if ( tagName == pendingInline[ i ].name ) + { + pendingInline.splice( i, 1 ); + return; + } + } + + var pendingAdd = [], + newPendingInline = [], + candidate = currentNode; + + while ( candidate != fragment && candidate.name != tagName ) + { + // If this is an inline element, add it to the pending list, if we're + // really closing one of the parents element later, they will continue + // after it. + if ( !candidate._.isBlockLike ) + newPendingInline.unshift( candidate ); + + // This node should be added to it's parent at this point. But, + // it should happen only if the closing tag is really closing + // one of the nodes. So, for now, we just cache it. + pendingAdd.push( candidate ); + + // Make sure return point is properly restored. + candidate = candidate.returnPoint || candidate.parent; + } + + if ( candidate != fragment ) + { + // Add all elements that have been found in the above loop. + for ( i = 0 ; i < pendingAdd.length ; i++ ) + { + var node = pendingAdd[ i ]; + addElement( node, node.parent ); + } + + currentNode = candidate; + + if ( currentNode.name == 'pre' ) + inPre = false; + + if ( candidate._.isBlockLike ) + sendPendingBRs(); + + addElement( candidate, candidate.parent ); + + // The parent should start receiving new nodes now, except if + // addElement changed the currentNode. + if ( candidate == currentNode ) + currentNode = currentNode.parent; + + pendingInline = pendingInline.concat( newPendingInline ); + } + + if ( tagName == 'body' ) + fixForBody = false; + }; + + parser.onText = function( text ) + { + // Trim empty spaces at beginning of text contents except

      .
      +			if ( ( !currentNode._.hasInlineStarted || pendingBRs.length ) && !inPre )
      +			{
      +				text = CKEDITOR.tools.ltrim( text );
      +
      +				if ( text.length === 0 )
      +					return;
      +			}
      +
      +			sendPendingBRs();
      +			checkPending();
      +
      +			if ( fixForBody
      +				 && ( !currentNode.type || currentNode.name == 'body' )
      +				 && CKEDITOR.tools.trim( text ) )
      +			{
      +				this.onTagOpen( fixForBody, {}, 0, 1 );
      +			}
      +
      +			// Shrinking consequential spaces into one single for all elements
      +			// text contents.
      +			if ( !inPre )
      +				text = text.replace( /[\t\r\n ]{2,}|[\t\r\n]/g, ' ' );
      +
      +			currentNode.add( new CKEDITOR.htmlParser.text( text ) );
      +		};
      +
      +		parser.onCDATA = function( cdata )
      +		{
      +			currentNode.add( new CKEDITOR.htmlParser.cdata( cdata ) );
      +		};
      +
      +		parser.onComment = function( comment )
      +		{
      +			sendPendingBRs();
      +			checkPending();
      +			currentNode.add( new CKEDITOR.htmlParser.comment( comment ) );
      +		};
      +
      +		// Parse it.
      +		parser.parse( fragmentHtml );
      +
      +		// Send all pending BRs except one, which we consider a unwanted bogus. (#5293)
      +		sendPendingBRs( !CKEDITOR.env.ie && 1 );
      +
      +		// Close all pending nodes, make sure return point is properly restored.
      +		while ( currentNode != fragment )
      +			addElement( currentNode, currentNode.parent, 1 );
      +
      +		return fragment;
      +	};
      +
      +	CKEDITOR.htmlParser.fragment.prototype =
      +	{
      +		/**
      +		 * Adds a node to this fragment.
      +		 * @param {Object} node The node to be added. It can be any of of the
      +		 *		following types: {@link CKEDITOR.htmlParser.element},
      +		 *		{@link CKEDITOR.htmlParser.text} and
      +		 *		{@link CKEDITOR.htmlParser.comment}.
      +		 *	@param {Number} [index] From where the insertion happens.
      +		 * @example
      +		 */
      +		add : function( node, index )
      +		{
      +			isNaN( index ) && ( index = this.children.length );
      +
      +			var previous = index > 0 ? this.children[ index - 1 ] : null;
      +			if ( previous )
      +			{
      +				// If the block to be appended is following text, trim spaces at
      +				// the right of it.
      +				if ( node._.isBlockLike && previous.type == CKEDITOR.NODE_TEXT )
      +				{
      +					previous.value = CKEDITOR.tools.rtrim( previous.value );
      +
      +					// If we have completely cleared the previous node.
      +					if ( previous.value.length === 0 )
      +					{
      +						// Remove it from the list and add the node again.
      +						this.children.pop();
      +						this.add( node );
      +						return;
      +					}
      +				}
      +
      +				previous.next = node;
      +			}
      +
      +			node.previous = previous;
      +			node.parent = this;
      +
      +			this.children.splice( index, 0, node );
      +
      +			this._.hasInlineStarted = node.type == CKEDITOR.NODE_TEXT || ( node.type == CKEDITOR.NODE_ELEMENT && !node._.isBlockLike );
      +		},
      +
      +		/**
      +		 * Writes the fragment HTML to a CKEDITOR.htmlWriter.
      +		 * @param {CKEDITOR.htmlWriter} writer The writer to which write the HTML.
      +		 * @example
      +		 * var writer = new CKEDITOR.htmlWriter();
      +		 * var fragment = CKEDITOR.htmlParser.fragment.fromHtml( '<P><B>Example' );
      +		 * fragment.writeHtml( writer )
      +		 * alert( writer.getHtml() );  "<p><b>Example</b></p>"
      +		 */
      +		writeHtml : function( writer, filter )
      +		{
      +			var isChildrenFiltered;
      +			this.filterChildren = function()
      +			{
      +				var writer = new CKEDITOR.htmlParser.basicWriter();
      +				this.writeChildrenHtml.call( this, writer, filter, true );
      +				var html = writer.getHtml();
      +				this.children = new CKEDITOR.htmlParser.fragment.fromHtml( html ).children;
      +				isChildrenFiltered = 1;
      +			};
      +
      +			// Filtering the root fragment before anything else.
      +			!this.name && filter && filter.onFragment( this );
      +
      +			this.writeChildrenHtml( writer, isChildrenFiltered ? null : filter );
      +		},
      +
      +		writeChildrenHtml : function( writer, filter )
      +		{
      +			for ( var i = 0 ; i < this.children.length ; i++ )
      +				this.children[i].writeHtml( writer, filter );
      +		}
      +	};
      +})();
      diff --git a/htdocs/includes/ckeditor/_source/core/htmlparser/text.js b/htdocs/includes/ckeditor/_source/core/htmlparser/text.js
      new file mode 100755
      index 00000000000..02f893586bd
      --- /dev/null
      +++ b/htdocs/includes/ckeditor/_source/core/htmlparser/text.js
      @@ -0,0 +1,55 @@
      +/*
      +Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved.
      +For licensing, see LICENSE.html or http://ckeditor.com/license
      +*/
      +
      +(function()
      +{
      +	var spacesRegex = /[\t\r\n ]{2,}|[\t\r\n]/g;
      +
      +	/**
      +	 * A lightweight representation of HTML text.
      +	 * @constructor
      +	 * @example
      +	 */
      + 	CKEDITOR.htmlParser.text = function( value )
      +	{
      +		/**
      +		 * The text value.
      +		 * @type String
      +		 * @example
      +		 */
      +		this.value = value;
      +
      +		/** @private */
      +		this._ =
      +		{
      +			isBlockLike : false
      +		};
      +	};
      +
      +	CKEDITOR.htmlParser.text.prototype =
      +	{
      +		/**
      +		 * The node type. This is a constant value set to {@link CKEDITOR.NODE_TEXT}.
      +		 * @type Number
      +		 * @example
      +		 */
      +		type : CKEDITOR.NODE_TEXT,
      +
      +		/**
      +		 * Writes the HTML representation of this text to a CKEDITOR.htmlWriter.
      +		 * @param {CKEDITOR.htmlWriter} writer The writer to which write the HTML.
      +		 * @example
      +		 */
      +		writeHtml : function( writer, filter )
      +		{
      +			var text = this.value;
      +
      +			if ( filter && !( text = filter.onText( text, this ) ) )
      +				return;
      +
      +			writer.text( text );
      +		}
      +	};
      +})();
      diff --git a/htdocs/includes/ckeditor/_source/core/lang.js b/htdocs/includes/ckeditor/_source/core/lang.js
      new file mode 100755
      index 00000000000..19b903d6c42
      --- /dev/null
      +++ b/htdocs/includes/ckeditor/_source/core/lang.js
      @@ -0,0 +1,157 @@
      +/*
      +Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved.
      +For licensing, see LICENSE.html or http://ckeditor.com/license
      +*/
      +
      +(function()
      +{
      +	var loadedLangs = {};
      +
      +	/**
      +	 * @namespace Holds language related functions.
      +	 */
      +	CKEDITOR.lang =
      +	{
      +		/**
      +		 * The list of languages available in the editor core.
      +		 * @type Object
      +		 * @example
      +		 * alert( CKEDITOR.lang.en );  // "true"
      +		 */
      +		languages :
      +		{
      +			'af'	: 1,
      +			'ar'	: 1,
      +			'bg'	: 1,
      +			'bn'	: 1,
      +			'bs'	: 1,
      +			'ca'	: 1,
      +			'cs'	: 1,
      +			'cy'	: 1,
      +			'da'	: 1,
      +			'de'	: 1,
      +			'el'	: 1,
      +			'en-au'	: 1,
      +			'en-ca'	: 1,
      +			'en-gb'	: 1,
      +			'en'	: 1,
      +			'eo'	: 1,
      +			'es'	: 1,
      +			'et'	: 1,
      +			'eu'	: 1,
      +			'fa'	: 1,
      +			'fi'	: 1,
      +			'fo'	: 1,
      +			'fr-ca'	: 1,
      +			'fr'	: 1,
      +			'gl'	: 1,
      +			'gu'	: 1,
      +			'he'	: 1,
      +			'hi'	: 1,
      +			'hr'	: 1,
      +			'hu'	: 1,
      +			'is'	: 1,
      +			'it'	: 1,
      +			'ja'	: 1,
      +			'ka'	: 1,
      +			'km'	: 1,
      +			'ko'	: 1,
      +			'lt'	: 1,
      +			'lv'	: 1,
      +			'mn'	: 1,
      +			'ms'	: 1,
      +			'nb'	: 1,
      +			'nl'	: 1,
      +			'no'	: 1,
      +			'pl'	: 1,
      +			'pt-br'	: 1,
      +			'pt'	: 1,
      +			'ro'	: 1,
      +			'ru'	: 1,
      +			'sk'	: 1,
      +			'sl'	: 1,
      +			'sr-latn'	: 1,
      +			'sr'	: 1,
      +			'sv'	: 1,
      +			'th'	: 1,
      +			'tr'	: 1,
      +			'uk'	: 1,
      +			'vi'	: 1,
      +			'zh-cn'	: 1,
      +			'zh'	: 1
      +		},
      +
      +		/**
      +		 * Loads a specific language file, or auto detect it. A callback is
      +		 * then called when the file gets loaded.
      +		 * @param {String} languageCode The code of the language file to be
      +		 *		loaded. If null or empty, autodetection will be performed. The
      +		 *		same happens if the language is not supported.
      +		 * @param {String} defaultLanguage The language to be used if
      +		 *		languageCode is not supported or if the autodetection fails.
      +		 * @param {Function} callback A function to be called once the
      +		 *		language file is loaded. Two parameters are passed to this
      +		 *		function: the language code and the loaded language entries.
      +		 * @example
      +		 */
      +		load : function( languageCode, defaultLanguage, callback )
      +		{
      +			// If no languageCode - fallback to browser or default.
      +			// If languageCode - fallback to no-localized version or default.
      +			if ( !languageCode || !CKEDITOR.lang.languages[ languageCode ] )
      +				languageCode = this.detect( defaultLanguage, languageCode );
      +
      +			if ( !this[ languageCode ] )
      +			{
      +				CKEDITOR.scriptLoader.load( CKEDITOR.getUrl(
      +					'_source/' +	// @Packager.RemoveLine
      +					'lang/' + languageCode + '.js' ),
      +					function()
      +						{
      +							callback( languageCode, this[ languageCode ] );
      +						}
      +						, this );
      +			}
      +			else
      +				callback( languageCode, this[ languageCode ] );
      +		},
      +
      +		/**
      +		 * Returns the language that best fit the user language. For example,
      +		 * suppose that the user language is "pt-br". If this language is
      +		 * supported by the editor, it is returned. Otherwise, if only "pt" is
      +		 * supported, it is returned instead. If none of the previous are
      +		 * supported, a default language is then returned.
      +		 * @param {String} defaultLanguage The default language to be returned
      +		 *		if the user language is not supported.
      +		 * @param {String} [probeLanguage] A language code to try to use,
      +		 *		instead of the browser based autodetection.
      +		 * @returns {String} The detected language code.
      +		 * @example
      +		 * alert( CKEDITOR.lang.detect( 'en' ) );  // e.g., in a German browser: "de"
      +		 */
      +		detect : function( defaultLanguage, probeLanguage )
      +		{
      +			var languages = this.languages;
      +			probeLanguage = probeLanguage || navigator.userLanguage || navigator.language;
      +
      +			var parts = probeLanguage
      +					.toLowerCase()
      +					.match( /([a-z]+)(?:-([a-z]+))?/ ),
      +				lang = parts[1],
      +				locale = parts[2];
      +
      +			if ( languages[ lang + '-' + locale ] )
      +				lang = lang + '-' + locale;
      +			else if ( !languages[ lang ] )
      +				lang = null;
      +
      +			CKEDITOR.lang.detect = lang ?
      +				function() { return lang; } :
      +				function( defaultLanguage ) { return defaultLanguage; };
      +
      +			return lang || defaultLanguage;
      +		}
      +	};
      +
      +})();
      diff --git a/htdocs/includes/ckeditor/_source/core/loader.js b/htdocs/includes/ckeditor/_source/core/loader.js
      new file mode 100755
      index 00000000000..d8a8302590c
      --- /dev/null
      +++ b/htdocs/includes/ckeditor/_source/core/loader.js
      @@ -0,0 +1,240 @@
      +/*
      +Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved.
      +For licensing, see LICENSE.html or http://ckeditor.com/license
      +*/
      +
      +/**
      + * @fileOverview Defines the {@link CKEDITOR.loader} objects, which is used to
      + *		load core scripts and their dependencies from _source.
      + */
      +
      +if ( typeof CKEDITOR == 'undefined' )
      +	CKEDITOR = {};
      +
      +if ( !CKEDITOR.loader )
      +{
      +	/**
      +	 * Load core scripts and their dependencies from _source.
      +	 * @namespace
      +	 * @example
      +	 */
      +	CKEDITOR.loader = (function()
      +	{
      +		// Table of script names and their dependencies.
      +		var scripts =
      +		{
      +			'core/_bootstrap'		: [ 'core/config', 'core/ckeditor', 'core/plugins', 'core/scriptloader', 'core/tools', /* The following are entries that we want to force loading at the end to avoid dependence recursion */ 'core/dom/comment', 'core/dom/elementpath', 'core/dom/text', 'core/dom/rangelist' ],
      +			'core/ckeditor'			: [ 'core/ckeditor_basic', 'core/dom', 'core/dtd', 'core/dom/document', 'core/dom/element', 'core/editor', 'core/event', 'core/htmlparser', 'core/htmlparser/element', 'core/htmlparser/fragment', 'core/htmlparser/filter', 'core/htmlparser/basicwriter', 'core/tools' ],
      +			'core/ckeditor_base'	: [],
      +			'core/ckeditor_basic'	: [ 'core/editor_basic', 'core/env', 'core/event' ],
      +			'core/command'			: [],
      +			'core/config'			: [ 'core/ckeditor_base' ],
      +			'core/dom'				: [],
      +			'core/dom/comment'		: [ 'core/dom/node' ],
      +			'core/dom/document'		: [ 'core/dom', 'core/dom/domobject', 'core/dom/window' ],
      +			'core/dom/documentfragment'	: [ 'core/dom/element' ],
      +			'core/dom/element'		: [ 'core/dom', 'core/dom/document', 'core/dom/domobject', 'core/dom/node', 'core/dom/nodelist', 'core/tools' ],
      +			'core/dom/elementpath'	: [ 'core/dom/element' ],
      +			'core/dom/event'		: [],
      +			'core/dom/node'			: [ 'core/dom/domobject', 'core/tools' ],
      +			'core/dom/nodelist'		: [ 'core/dom/node' ],
      +			'core/dom/domobject'	: [ 'core/dom/event' ],
      +			'core/dom/range'		: [ 'core/dom/document', 'core/dom/documentfragment', 'core/dom/element', 'core/dom/walker' ],
      +			'core/dom/rangelist'    : [ 'core/dom/range' ],
      +			'core/dom/text'			: [ 'core/dom/node', 'core/dom/domobject' ],
      +			'core/dom/walker'		: [ 'core/dom/node' ],
      +			'core/dom/window'		: [ 'core/dom/domobject' ],
      +			'core/dtd'				: [ 'core/tools' ],
      +			'core/editor'			: [ 'core/command', 'core/config', 'core/editor_basic', 'core/focusmanager', 'core/lang', 'core/plugins', 'core/skins', 'core/themes', 'core/tools', 'core/ui' ],
      +			'core/editor_basic'		: [ 'core/event' ],
      +			'core/env'				: [],
      +			'core/event'			: [],
      +			'core/focusmanager'		: [],
      +			'core/htmlparser'		: [],
      +			'core/htmlparser/comment'	: [ 'core/htmlparser' ],
      +			'core/htmlparser/element'	: [ 'core/htmlparser', 'core/htmlparser/fragment' ],
      +			'core/htmlparser/fragment'	: [ 'core/htmlparser', 'core/htmlparser/comment', 'core/htmlparser/text', 'core/htmlparser/cdata' ],
      +			'core/htmlparser/text'		: [ 'core/htmlparser' ],
      +			'core/htmlparser/cdata'		: [ 'core/htmlparser' ],
      +			'core/htmlparser/filter'	: [ 'core/htmlparser' ],
      +			'core/htmlparser/basicwriter': [ 'core/htmlparser' ],
      +			'core/lang'				: [],
      +			'core/plugins'			: [ 'core/resourcemanager' ],
      +			'core/resourcemanager'	: [ 'core/scriptloader', 'core/tools' ],
      +			'core/scriptloader'		: [ 'core/dom/element', 'core/env' ],
      +			'core/skins'			: [ 'core/scriptloader' ],
      +			'core/themes'			: [ 'core/resourcemanager' ],
      +			'core/tools'			: [ 'core/env' ],
      +			'core/ui'				: []
      +		};
      +
      +		var basePath = (function()
      +		{
      +			// This is a copy of CKEDITOR.basePath, but requires the script having
      +			// "_source/core/loader.js".
      +			if ( CKEDITOR && CKEDITOR.basePath )
      +				return CKEDITOR.basePath;
      +
      +			// Find out the editor directory path, based on its ' +
      +								'';
      +
      +							var src =
      +								CKEDITOR.env.air ?
      +									'javascript:void(0)' :
      +								isCustomDomain ?
      +									'javascript:void((function(){' +
      +										'document.open();' +
      +										'document.domain=\'' + document.domain + '\';' +
      +										'document.close();' +
      +									'})())"'
      +								:
      +									'';
      +
      +							var iframe = CKEDITOR.dom.element.createFromHtml(
      +								'' );
      +
      +							iframe.on( 'load', function( e )
      +							{
      +								e.removeListener();
      +
      +								var doc = iframe.getFrameDocument();
      +								doc.write( htmlToLoad );
      +
      +								if ( CKEDITOR.env.air )
      +									onPasteFrameLoad.call( this, doc.getWindow().$ );
      +							}, dialog );
      +
      +							iframe.setCustomData( 'dialog', dialog );
      +
      +							var container = this.getElement();
      +							container.setHtml( '' );
      +							container.append( iframe );
      +
      +							// IE need a redirect on focus to make
      +							// the cursor blinking inside iframe. (#5461)
      +							if ( CKEDITOR.env.ie )
      +							{
      +								var focusGrabber = CKEDITOR.dom.element.createFromHtml( '' );
      +								focusGrabber.on( 'focus', function()
      +								{
      +									iframe.$.contentWindow.focus();
      +								});
      +								container.append( focusGrabber );
      +
      +								// Override focus handler on field.
      +								this.focus = function()
      +								{
      +									focusGrabber.focus();
      +									this.fire( 'focus' );
      +								};
      +							}
      +
      +							this.getInputElement = function(){ return iframe; };
      +
      +							// Force container to scale in IE.
      +							if ( CKEDITOR.env.ie )
      +							{
      +								container.setStyle( 'display', 'block' );
      +								container.setStyle( 'height', ( iframe.$.offsetHeight + 2 ) + 'px' );
      +							}
      +						},
      +						commit : function( data )
      +						{
      +							var container = this.getElement(),
      +								editor = this.getDialog().getParentEditor(),
      +								body = this.getInputElement().getFrameDocument().getBody(),
      +								bogus = body.getBogus(),
      +								html;
      +							bogus && bogus.remove();
      +
      +							// Saving the contents so changes until paste is complete will not take place (#7500)
      +							html = body.getHtml();
      +
      +							setTimeout( function(){
      +								editor.fire( 'paste', { 'html' : html } );
      +							}, 0 );
      +						}
      +					}
      +				]
      +			}
      +		]
      +	};
      +});
      diff --git a/htdocs/includes/ckeditor/_source/plugins/clipboard/plugin.js b/htdocs/includes/ckeditor/_source/plugins/clipboard/plugin.js
      new file mode 100755
      index 00000000000..3c53ab1007f
      --- /dev/null
      +++ b/htdocs/includes/ckeditor/_source/plugins/clipboard/plugin.js
      @@ -0,0 +1,453 @@
      +/*
      +Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved.
      +For licensing, see LICENSE.html or http://ckeditor.com/license
      +*/
      +
      +/**
      + * @file Clipboard support
      + */
      +
      +(function()
      +{
      +	// Tries to execute any of the paste, cut or copy commands in IE. Returns a
      +	// boolean indicating that the operation succeeded.
      +	var execIECommand = function( editor, command )
      +	{
      +		var doc = editor.document,
      +			body = doc.getBody();
      +
      +		var enabled = 0;
      +		var onExec = function()
      +		{
      +			enabled = 1;
      +		};
      +
      +		// The following seems to be the only reliable way to detect that
      +		// clipboard commands are enabled in IE. It will fire the
      +		// onpaste/oncut/oncopy events only if the security settings allowed
      +		// the command to execute.
      +		body.on( command, onExec );
      +
      +		// IE6/7: document.execCommand has problem to paste into positioned element.
      +		( CKEDITOR.env.version > 7 ? doc.$ : doc.$.selection.createRange() ) [ 'execCommand' ]( command );
      +
      +		body.removeListener( command, onExec );
      +
      +		return enabled;
      +	};
      +
      +	// Attempts to execute the Cut and Copy operations.
      +	var tryToCutCopy =
      +		CKEDITOR.env.ie ?
      +			function( editor, type )
      +			{
      +				return execIECommand( editor, type );
      +			}
      +		:		// !IE.
      +			function( editor, type )
      +			{
      +				try
      +				{
      +					// Other browsers throw an error if the command is disabled.
      +					return editor.document.$.execCommand( type, false, null );
      +				}
      +				catch( e )
      +				{
      +					return false;
      +				}
      +			};
      +
      +	// A class that represents one of the cut or copy commands.
      +	var cutCopyCmd = function( type )
      +	{
      +		this.type = type;
      +		this.canUndo = this.type == 'cut';		// We can't undo copy to clipboard.
      +		this.startDisabled = true;
      +	};
      +
      +	cutCopyCmd.prototype =
      +	{
      +		exec : function( editor, data )
      +		{
      +			this.type == 'cut' && fixCut( editor );
      +
      +			var success = tryToCutCopy( editor, this.type );
      +
      +			if ( !success )
      +				alert( editor.lang.clipboard[ this.type + 'Error' ] );		// Show cutError or copyError.
      +
      +			return success;
      +		}
      +	};
      +
      +	// Paste command.
      +	var pasteCmd =
      +	{
      +		canUndo : false,
      +
      +		exec :
      +			CKEDITOR.env.ie ?
      +				function( editor )
      +				{
      +					// Prevent IE from pasting at the begining of the document.
      +					editor.focus();
      +
      +					if ( !editor.document.getBody().fire( 'beforepaste' )
      +						 && !execIECommand( editor, 'paste' ) )
      +					{
      +						editor.fire( 'pasteDialog' );
      +						return false;
      +					}
      +				}
      +			:
      +				function( editor )
      +				{
      +					try
      +					{
      +						if ( !editor.document.getBody().fire( 'beforepaste' )
      +							 && !editor.document.$.execCommand( 'Paste', false, null ) )
      +						{
      +							throw 0;
      +						}
      +					}
      +					catch ( e )
      +					{
      +						setTimeout( function()
      +							{
      +								editor.fire( 'pasteDialog' );
      +							}, 0 );
      +						return false;
      +					}
      +				}
      +	};
      +
      +	// Listens for some clipboard related keystrokes, so they get customized.
      +	var onKey = function( event )
      +	{
      +		if ( this.mode != 'wysiwyg' )
      +			return;
      +
      +		switch ( event.data.keyCode )
      +		{
      +			// Paste
      +			case CKEDITOR.CTRL + 86 :		// CTRL+V
      +			case CKEDITOR.SHIFT + 45 :		// SHIFT+INS
      +
      +				var body = this.document.getBody();
      +
      +				// Simulate 'beforepaste' event for all none-IEs.
      +				if ( !CKEDITOR.env.ie && body.fire( 'beforepaste' ) )
      +					event.cancel();
      +				// Simulate 'paste' event for Opera/Firefox2.
      +				else if ( CKEDITOR.env.opera
      +						 || CKEDITOR.env.gecko && CKEDITOR.env.version < 10900 )
      +					body.fire( 'paste' );
      +				return;
      +
      +			// Cut
      +			case CKEDITOR.CTRL + 88 :		// CTRL+X
      +			case CKEDITOR.SHIFT + 46 :		// SHIFT+DEL
      +
      +				// Save Undo snapshot.
      +				var editor = this;
      +				this.fire( 'saveSnapshot' );		// Save before paste
      +				setTimeout( function()
      +					{
      +						editor.fire( 'saveSnapshot' );		// Save after paste
      +					}, 0 );
      +		}
      +	};
      +
      +	function cancel( evt ) { evt.cancel(); }
      +
      +	// Allow to peek clipboard content by redirecting the
      +	// pasting content into a temporary bin and grab the content of it.
      +	function getClipboardData( evt, mode, callback )
      +	{
      +		var doc = this.document;
      +
      +		// Avoid recursions on 'paste' event or consequent paste too fast. (#5730)
      +		if ( doc.getById( 'cke_pastebin' ) )
      +			return;
      +
      +		// If the browser supports it, get the data directly
      +		if ( mode == 'text' && evt.data && evt.data.$.clipboardData )
      +		{
      +			// evt.data.$.clipboardData.types contains all the flavours in Mac's Safari, but not on windows.
      +			var plain = evt.data.$.clipboardData.getData( 'text/plain' );
      +			if ( plain )
      +			{
      +				evt.data.preventDefault();
      +				callback( plain );
      +				return;
      +			}
      +		}
      +
      +		var sel = this.getSelection(),
      +			range = new CKEDITOR.dom.range( doc );
      +
      +		// Create container to paste into
      +		var pastebin = new CKEDITOR.dom.element( mode == 'text' ? 'textarea' : CKEDITOR.env.webkit ? 'body' : 'div', doc );
      +		pastebin.setAttribute( 'id', 'cke_pastebin' );
      +		// Safari requires a filler node inside the div to have the content pasted into it. (#4882)
      +		CKEDITOR.env.webkit && pastebin.append( doc.createText( '\xa0' ) );
      +		doc.getBody().append( pastebin );
      +
      +		pastebin.setStyles(
      +			{
      +				position : 'absolute',
      +				// Position the bin exactly at the position of the selected element
      +				// to avoid any subsequent document scroll.
      +				top : sel.getStartElement().getDocumentPosition().y + 'px',
      +				width : '1px',
      +				height : '1px',
      +				overflow : 'hidden'
      +			});
      +
      +		// It's definitely a better user experience if we make the paste-bin pretty unnoticed
      +		// by pulling it off the screen.
      +		pastebin.setStyle( this.config.contentsLangDirection == 'ltr' ? 'left' : 'right', '-1000px' );
      +
      +		var bms = sel.createBookmarks();
      +
      +		this.on( 'selectionChange', cancel, null, null, 0 );
      +
      +		// Turn off design mode temporarily before give focus to the paste bin.
      +		if ( mode == 'text' )
      +			pastebin.$.focus();
      +		else
      +		{
      +			range.setStartAt( pastebin, CKEDITOR.POSITION_AFTER_START );
      +			range.setEndAt( pastebin, CKEDITOR.POSITION_BEFORE_END );
      +			range.select( true );
      +		}
      +
      +		var editor  = this;
      +		// Wait a while and grab the pasted contents
      +		window.setTimeout( function()
      +		{
      +			mode == 'text' && CKEDITOR.env.gecko && editor.focusGrabber.focus();
      +			pastebin.remove();
      +			editor.removeListener( 'selectionChange', cancel );
      +
      +			// Grab the HTML contents.
      +			// We need to look for a apple style wrapper on webkit it also adds
      +			// a div wrapper if you copy/paste the body of the editor.
      +			// Remove hidden div and restore selection.
      +			var bogusSpan;
      +			pastebin = ( CKEDITOR.env.webkit
      +						 && ( bogusSpan = pastebin.getFirst() )
      +						 && ( bogusSpan.is && bogusSpan.hasClass( 'Apple-style-span' ) ) ?
      +							bogusSpan : pastebin );
      +
      +			sel.selectBookmarks( bms );
      +			callback( pastebin[ 'get' + ( mode == 'text' ? 'Value' : 'Html' ) ]() );
      +		}, 0 );
      +	}
      +
      +	// Cutting off control type element in IE standards breaks the selection entirely. (#4881)
      +	function fixCut( editor )
      +	{
      +		if ( !CKEDITOR.env.ie || CKEDITOR.env.quirks )
      +			return;
      +
      +		var sel = editor.getSelection();
      +		var control;
      +		if( ( sel.getType() == CKEDITOR.SELECTION_ELEMENT ) && ( control = sel.getSelectedElement() ) )
      +		{
      +			var range = sel.getRanges()[ 0 ];
      +			var dummy = editor.document.createText( '' );
      +			dummy.insertBefore( control );
      +			range.setStartBefore( dummy );
      +			range.setEndAfter( control );
      +			sel.selectRanges( [ range ] );
      +
      +			// Clear up the fix if the paste wasn't succeeded.
      +			setTimeout( function()
      +			{
      +				// Element still online?
      +				if ( control.getParent() )
      +				{
      +					dummy.remove();
      +					sel.selectElement( control );
      +				}
      +			}, 0 );
      +		}
      +	}
      +
      +	var depressBeforeEvent;
      +	function stateFromNamedCommand( command, editor )
      +	{
      +		// IE Bug: queryCommandEnabled('paste') fires also 'beforepaste(copy/cut)',
      +		// guard to distinguish from the ordinary sources( either
      +		// keyboard paste or execCommand ) (#4874).
      +		CKEDITOR.env.ie && ( depressBeforeEvent = 1 );
      +
      +		var retval = CKEDITOR.TRISTATE_OFF;
      +		try { retval = editor.document.$.queryCommandEnabled( command ) ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED; }catch( er ){}
      +
      +		depressBeforeEvent = 0;
      +		return retval;
      +	}
      +
      +	var inReadOnly;
      +	function setToolbarStates()
      +	{
      +		if ( this.mode != 'wysiwyg' )
      +			return;
      +
      +		this.getCommand( 'cut' ).setState( inReadOnly ? CKEDITOR.TRISTATE_DISABLED : stateFromNamedCommand( 'Cut', this ) );
      +		this.getCommand( 'copy' ).setState( stateFromNamedCommand( 'Copy', this ) );
      +		var pasteState = inReadOnly ? CKEDITOR.TRISTATE_DISABLED :
      +						CKEDITOR.env.webkit ? CKEDITOR.TRISTATE_OFF : stateFromNamedCommand( 'Paste', this );
      +		this.fire( 'pasteState', pasteState );
      +	}
      +
      +	// Register the plugin.
      +	CKEDITOR.plugins.add( 'clipboard',
      +		{
      +			requires : [ 'dialog', 'htmldataprocessor' ],
      +			init : function( editor )
      +			{
      +				// Inserts processed data into the editor at the end of the
      +				// events chain.
      +				editor.on( 'paste', function( evt )
      +					{
      +						var data = evt.data;
      +						if ( data[ 'html' ] )
      +							editor.insertHtml( data[ 'html' ] );
      +						else if ( data[ 'text' ] )
      +							editor.insertText( data[ 'text' ] );
      +
      +						setTimeout( function () { editor.fire( 'afterPaste' ); }, 0 );
      +
      +					}, null, null, 1000 );
      +
      +				editor.on( 'pasteDialog', function( evt )
      +					{
      +						setTimeout( function()
      +						{
      +							// Open default paste dialog.
      +							editor.openDialog( 'paste' );
      +						}, 0 );
      +					});
      +
      +				editor.on( 'pasteState', function( evt )
      +					{
      +						editor.getCommand( 'paste' ).setState( evt.data );
      +					});
      +
      +				function addButtonCommand( buttonName, commandName, command, ctxMenuOrder )
      +				{
      +					var lang = editor.lang[ commandName ];
      +
      +					editor.addCommand( commandName, command );
      +					editor.ui.addButton( buttonName,
      +						{
      +							label : lang,
      +							command : commandName
      +						});
      +
      +					// If the "menu" plugin is loaded, register the menu item.
      +					if ( editor.addMenuItems )
      +					{
      +						editor.addMenuItem( commandName,
      +							{
      +								label : lang,
      +								command : commandName,
      +								group : 'clipboard',
      +								order : ctxMenuOrder
      +							});
      +					}
      +				}
      +
      +				addButtonCommand( 'Cut', 'cut', new cutCopyCmd( 'cut' ), 1 );
      +				addButtonCommand( 'Copy', 'copy', new cutCopyCmd( 'copy' ), 4 );
      +				addButtonCommand( 'Paste', 'paste', pasteCmd, 8 );
      +
      +				CKEDITOR.dialog.add( 'paste', CKEDITOR.getUrl( this.path + 'dialogs/paste.js' ) );
      +
      +				editor.on( 'key', onKey, editor );
      +
      +				// We'll be catching all pasted content in one line, regardless of whether the
      +				// it's introduced by a document command execution (e.g. toolbar buttons) or
      +				// user paste behaviors. (e.g. Ctrl-V)
      +				editor.on( 'contentDom', function()
      +				{
      +					var body = editor.document.getBody();
      +					body.on( CKEDITOR.env.webkit ? 'paste' : 'beforepaste', function( evt )
      +						{
      +							if ( depressBeforeEvent )
      +								return;
      +
      +							// Fire 'beforePaste' event so clipboard flavor get customized
      +							// by other plugins.
      +							var eventData =  { mode : 'html' };
      +							editor.fire( 'beforePaste', eventData );
      +
      +							getClipboardData.call( editor, evt, eventData.mode, function ( data )
      +							{
      +								// The very last guard to make sure the
      +								// paste has successfully happened.
      +								if ( !( data = CKEDITOR.tools.trim( data.replace( /]+data-cke-bookmark[^<]*?<\/span>/ig,'' ) ) ) )
      +									return;
      +
      +								var dataTransfer = {};
      +								dataTransfer[ eventData.mode ] = data;
      +								editor.fire( 'paste', dataTransfer );
      +							} );
      +						});
      +
      +					// Dismiss the (wrong) 'beforepaste' event fired on context menu open. (#7953)
      +					body.on( 'contextmenu', function()
      +					{
      +						depressBeforeEvent = 1;
      +						setTimeout( function() { depressBeforeEvent = 0; }, 10 );
      +					});
      +
      +					body.on( 'beforecut', function() { !depressBeforeEvent && fixCut( editor ); } );
      +
      +					body.on( 'mouseup', function(){ setTimeout( function(){ setToolbarStates.call( editor ); }, 0 ); }, editor );
      +					body.on( 'keyup', setToolbarStates, editor );
      +				});
      +
      +				// For improved performance, we're checking the readOnly state on selectionChange instead of hooking a key event for that.
      +				editor.on( 'selectionChange', function( evt )
      +				{
      +					inReadOnly = evt.data.selection.getRanges()[ 0 ].checkReadOnly();
      +					setToolbarStates.call( editor );
      +				});
      +
      +				// If the "contextmenu" plugin is loaded, register the listeners.
      +				if ( editor.contextMenu )
      +				{
      +					editor.contextMenu.addListener( function( element, selection )
      +						{
      +							var readOnly = selection.getRanges()[ 0 ].checkReadOnly();
      +							return {
      +								cut : !readOnly && stateFromNamedCommand( 'Cut', editor ),
      +								copy : stateFromNamedCommand( 'Copy', editor ),
      +								paste : !readOnly && ( CKEDITOR.env.webkit ? CKEDITOR.TRISTATE_OFF : stateFromNamedCommand( 'Paste', editor ) )
      +							};
      +						});
      +				}
      +			}
      +		});
      +})();
      +
      +/**
      + * Fired when a clipboard operation is about to be taken into the editor.
      + * Listeners can manipulate the data to be pasted before having it effectively
      + * inserted into the document.
      + * @name CKEDITOR.editor#paste
      + * @since 3.1
      + * @event
      + * @param {String} [data.html] The HTML data to be pasted. If not available, e.data.text will be defined.
      + * @param {String} [data.text] The plain text data to be pasted, available when plain text operations are to used. If not available, e.data.html will be defined.
      + */
      +
      +/**
      + * Internal event to open the Paste dialog
      + * @name CKEDITOR.editor#pasteDialog
      + * @event
      + */
      diff --git a/htdocs/includes/ckeditor/_source/plugins/colorbutton/plugin.js b/htdocs/includes/ckeditor/_source/plugins/colorbutton/plugin.js
      new file mode 100755
      index 00000000000..b3e5d9cc171
      --- /dev/null
      +++ b/htdocs/includes/ckeditor/_source/plugins/colorbutton/plugin.js
      @@ -0,0 +1,294 @@
      +/*
      +Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved.
      +For licensing, see LICENSE.html or http://ckeditor.com/license
      +*/
      +
      +CKEDITOR.plugins.add( 'colorbutton',
      +{
      +	requires : [ 'panelbutton', 'floatpanel', 'styles' ],
      +
      +	init : function( editor )
      +	{
      +		var config = editor.config,
      +			lang = editor.lang.colorButton;
      +
      +		var clickFn;
      +
      +		if ( !CKEDITOR.env.hc )
      +		{
      +			addButton( 'TextColor', 'fore', lang.textColorTitle );
      +			addButton( 'BGColor', 'back', lang.bgColorTitle );
      +		}
      +
      +		function addButton( name, type, title )
      +		{
      +			var colorBoxId = CKEDITOR.tools.getNextId() + '_colorBox';
      +			editor.ui.add( name, CKEDITOR.UI_PANELBUTTON,
      +				{
      +					label : title,
      +					title : title,
      +					className : 'cke_button_' + name.toLowerCase(),
      +					modes : { wysiwyg : 1 },
      +
      +					panel :
      +					{
      +						css : editor.skin.editor.css,
      +						attributes : { role : 'listbox', 'aria-label' : lang.panelTitle }
      +					},
      +
      +					onBlock : function( panel, block )
      +					{
      +						block.autoSize = true;
      +						block.element.addClass( 'cke_colorblock' );
      +						block.element.setHtml( renderColors( panel, type, colorBoxId ) );
      +						// The block should not have scrollbars (#5933, #6056)
      +						block.element.getDocument().getBody().setStyle( 'overflow', 'hidden' );
      +
      +						CKEDITOR.ui.fire( 'ready', this );
      +
      +						var keys = block.keys;
      +						var rtl = editor.lang.dir == 'rtl';
      +						keys[ rtl ? 37 : 39 ]	= 'next';					// ARROW-RIGHT
      +						keys[ 40 ]	= 'next';					// ARROW-DOWN
      +						keys[ 9 ]	= 'next';					// TAB
      +						keys[ rtl ? 39 : 37 ]	= 'prev';					// ARROW-LEFT
      +						keys[ 38 ]	= 'prev';					// ARROW-UP
      +						keys[ CKEDITOR.SHIFT + 9 ]	= 'prev';	// SHIFT + TAB
      +						keys[ 32 ]	= 'click';					// SPACE
      +					},
      +
      +					// The automatic colorbox should represent the real color (#6010)
      +					onOpen : function()
      +					{
      +						var selection = editor.getSelection(),
      +							block = selection && selection.getStartElement(),
      +							path = new CKEDITOR.dom.elementPath( block ),
      +							color;
      +
      +						// Find the closest block element.
      +						block = path.block || path.blockLimit || editor.document.getBody();
      +
      +						// The background color might be transparent. In that case, look up the color in the DOM tree.
      +						do
      +						{
      +							color = block && block.getComputedStyle( type == 'back' ? 'background-color' : 'color' ) || 'transparent';
      +						}
      +						while ( type == 'back' && color == 'transparent' && block && ( block = block.getParent() ) );
      +
      +						// The box should never be transparent.
      +						if ( !color || color == 'transparent' )
      +							color = '#ffffff';
      +
      +						this._.panel._.iframe.getFrameDocument().getById( colorBoxId ).setStyle( 'background-color', color );
      +					}
      +				});
      +		}
      +
      +
      +		function renderColors( panel, type, colorBoxId )
      +		{
      +			var output = [],
      +				colors = config.colorButton_colors.split( ',' ),
      +				total = colors.length + ( config.colorButton_enableMore ? 2 : 1 );
      +
      +			var clickFn = CKEDITOR.tools.addFunction( function( color, type )
      +				{
      +					if ( color == '?' )
      +					{
      +						var applyColorStyle = arguments.callee;
      +						function onColorDialogClose( evt )
      +						{
      +							this.removeListener( 'ok', onColorDialogClose );
      +							this.removeListener( 'cancel', onColorDialogClose );
      +
      +							evt.name == 'ok' && applyColorStyle( this.getContentElement( 'picker', 'selectedColor' ).getValue(), type );
      +						}
      +
      +						editor.openDialog( 'colordialog', function()
      +						{
      +							this.on( 'ok', onColorDialogClose );
      +							this.on( 'cancel', onColorDialogClose );
      +						} );
      +
      +						return;
      +					}
      +
      +					editor.focus();
      +
      +					panel.hide( false );
      +
      +					editor.fire( 'saveSnapshot' );
      +
      +					// Clean up any conflicting style within the range.
      +					new CKEDITOR.style( config['colorButton_' + type + 'Style'], { color : 'inherit' } ).remove( editor.document );
      +
      +					if ( color )
      +					{
      +						var colorStyle = config['colorButton_' + type + 'Style'];
      +
      +						colorStyle.childRule = type == 'back' ?
      +							function( element )
      +							{
      +								// It's better to apply background color as the innermost style. (#3599)
      +								// Except for "unstylable elements". (#6103)
      +								return isUnstylable( element );
      +							}
      +							:
      +							function( element )
      +							{
      +								// Fore color style must be applied inside links instead of around it.
      +								return element.getName() != 'a' || isUnstylable( element );
      +							};
      +
      +						new CKEDITOR.style( colorStyle, { color : color } ).apply( editor.document );
      +					}
      +
      +					editor.fire( 'saveSnapshot' );
      +				});
      +
      +			// Render the "Automatic" button.
      +			output.push(
      +				'' +
      +					'' +
      +						'' +
      +							'' +
      +							'' +
      +						'' +
      +					'
      ' + + '' + + '', + lang.auto, + '
      ' + + '
      ' + + '' ); + + // Render the color boxes. + for ( var i = 0 ; i < colors.length ; i++ ) + { + if ( ( i % 8 ) === 0 ) + output.push( '' ); + + var parts = colors[ i ].split( '/' ), + colorName = parts[ 0 ], + colorCode = parts[ 1 ] || colorName; + + // The data can be only a color code (without #) or colorName + color code + // If only a color code is provided, then the colorName is the color with the hash + // Convert the color from RGB to RRGGBB for better compatibility with IE and . See #5676 + if (!parts[1]) + colorName = '#' + colorName.replace( /^(.)(.)(.)$/, '$1$1$2$2$3$3' ); + + var colorLabel = editor.lang.colors[ colorCode ] || colorCode; + output.push( + '' ); + } + + // Render the "More Colors" button. + if ( config.colorButton_enableMore === undefined || config.colorButton_enableMore ) + { + output.push( + '' + + '' + + '' ); // tr is later in the code. + } + + output.push( '
      ' + + '' + + '' + + '' + + '
      ' + + '', + lang.more, + '' + + '
      ' ); + + return output.join( '' ); + } + + function isUnstylable( ele ) + { + return ( ele.getAttribute( 'contentEditable' ) == 'false' ) || ele.getAttribute( 'data-nostyle' ); + } + } +}); + +/** + * Whether to enable the "More Colors..." button in the color selectors. + * @name CKEDITOR.config.colorButton_enableMore + * @default true + * @type Boolean + * @example + * config.colorButton_enableMore = false; + */ + +/** + * Defines the colors to be displayed in the color selectors. It's a string + * containing the hexadecimal notation for HTML colors, without the "#" prefix. + * + * Since 3.3: A name may be optionally defined by prefixing the entries with the + * name and the slash character. For example, "FontColor1/FF9900" will be + * displayed as the color #FF9900 in the selector, but will be outputted as "FontColor1". + * @name CKEDITOR.config.colorButton_colors + * @type String + * @default '000,800000,8B4513,2F4F4F,008080,000080,4B0082,696969,B22222,A52A2A,DAA520,006400,40E0D0,0000CD,800080,808080,F00,FF8C00,FFD700,008000,0FF,00F,EE82EE,A9A9A9,FFA07A,FFA500,FFFF00,00FF00,AFEEEE,ADD8E6,DDA0DD,D3D3D3,FFF0F5,FAEBD7,FFFFE0,F0FFF0,F0FFFF,F0F8FF,E6E6FA,FFF' + * @example + * // Brazil colors only. + * config.colorButton_colors = '00923E,F8C100,28166F'; + * @example + * config.colorButton_colors = 'FontColor1/FF9900,FontColor2/0066CC,FontColor3/F00' + */ +CKEDITOR.config.colorButton_colors = + '000,800000,8B4513,2F4F4F,008080,000080,4B0082,696969,' + + 'B22222,A52A2A,DAA520,006400,40E0D0,0000CD,800080,808080,' + + 'F00,FF8C00,FFD700,008000,0FF,00F,EE82EE,A9A9A9,' + + 'FFA07A,FFA500,FFFF00,00FF00,AFEEEE,ADD8E6,DDA0DD,D3D3D3,' + + 'FFF0F5,FAEBD7,FFFFE0,F0FFF0,F0FFFF,F0F8FF,E6E6FA,FFF'; + +/** + * Holds the style definition to be used to apply the text foreground color. + * @name CKEDITOR.config.colorButton_foreStyle + * @type Object + * @example + * // This is basically the default setting value. + * config.colorButton_foreStyle = + * { + * element : 'span', + * styles : { 'color' : '#(color)' } + * }; + */ +CKEDITOR.config.colorButton_foreStyle = + { + element : 'span', + styles : { 'color' : '#(color)' }, + overrides : [ { element : 'font', attributes : { 'color' : null } } ] + }; + +/** + * Holds the style definition to be used to apply the text background color. + * @name CKEDITOR.config.colorButton_backStyle + * @type Object + * @example + * // This is basically the default setting value. + * config.colorButton_backStyle = + * { + * element : 'span', + * styles : { 'background-color' : '#(color)' } + * }; + */ +CKEDITOR.config.colorButton_backStyle = + { + element : 'span', + styles : { 'background-color' : '#(color)' } + }; diff --git a/htdocs/includes/ckeditor/_source/plugins/colordialog/dialogs/colordialog.js b/htdocs/includes/ckeditor/_source/plugins/colordialog/dialogs/colordialog.js new file mode 100755 index 00000000000..61d411be401 --- /dev/null +++ b/htdocs/includes/ckeditor/_source/plugins/colordialog/dialogs/colordialog.js @@ -0,0 +1,340 @@ +/* +Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +CKEDITOR.dialog.add( 'colordialog', function( editor ) + { + // Define some shorthands. + var $el = CKEDITOR.dom.element, + $doc = CKEDITOR.document, + $tools = CKEDITOR.tools, + lang = editor.lang.colordialog; + + // Reference the dialog. + var dialog; + + var spacer = + { + type : 'html', + html : ' ' + }; + + function clearSelected() + { + $doc.getById( selHiColorId ).removeStyle( 'background-color' ); + dialog.getContentElement( 'picker', 'selectedColor' ).setValue( '' ); + } + + function updateSelected( evt ) + { + if ( ! ( evt instanceof CKEDITOR.dom.event ) ) + evt = new CKEDITOR.dom.event( evt ); + + var target = evt.getTarget(), + color; + + if ( target.getName() == 'a' && ( color = target.getChild( 0 ).getHtml() ) ) + dialog.getContentElement( 'picker', 'selectedColor' ).setValue( color ); + } + + function updateHighlight( event ) + { + if ( ! ( event instanceof CKEDITOR.dom.event ) ) + event = event.data; + + var target = event.getTarget(), + color; + + if ( target.getName() == 'a' && ( color = target.getChild( 0 ).getHtml() ) ) + { + $doc.getById( hicolorId ).setStyle( 'background-color', color ); + $doc.getById( hicolorTextId ).setHtml( color ); + } + } + + function clearHighlight() + { + $doc.getById( hicolorId ).removeStyle( 'background-color' ); + $doc.getById( hicolorTextId ).setHtml( ' ' ); + } + + var onMouseout = $tools.addFunction( clearHighlight ), + onClick = updateSelected, + onClickHandler = CKEDITOR.tools.addFunction( onClick ), + onFocus = updateHighlight, + onBlur = clearHighlight; + + var onKeydownHandler = CKEDITOR.tools.addFunction( function( ev ) + { + ev = new CKEDITOR.dom.event( ev ); + var element = ev.getTarget(); + var relative, nodeToMove; + var keystroke = ev.getKeystroke(), + rtl = editor.lang.dir == 'rtl'; + + switch ( keystroke ) + { + // UP-ARROW + case 38 : + // relative is TR + if ( ( relative = element.getParent().getParent().getPrevious() ) ) + { + nodeToMove = relative.getChild( [element.getParent().getIndex(), 0] ); + nodeToMove.focus(); + onBlur( ev, element ); + onFocus( ev, nodeToMove ); + } + ev.preventDefault(); + break; + // DOWN-ARROW + case 40 : + // relative is TR + if ( ( relative = element.getParent().getParent().getNext() ) ) + { + nodeToMove = relative.getChild( [ element.getParent().getIndex(), 0 ] ); + if ( nodeToMove && nodeToMove.type == 1 ) + { + nodeToMove.focus(); + onBlur( ev, element ); + onFocus( ev, nodeToMove ); + } + } + ev.preventDefault(); + break; + // SPACE + // ENTER is already handled as onClick + case 32 : + onClick( ev ); + ev.preventDefault(); + break; + + // RIGHT-ARROW + case rtl ? 37 : 39 : + // relative is TD + if ( ( relative = element.getParent().getNext() ) ) + { + nodeToMove = relative.getChild( 0 ); + if ( nodeToMove.type == 1 ) + { + nodeToMove.focus(); + onBlur( ev, element ); + onFocus( ev, nodeToMove ); + ev.preventDefault( true ); + } + else + onBlur( null, element ); + } + // relative is TR + else if ( ( relative = element.getParent().getParent().getNext() ) ) + { + nodeToMove = relative.getChild( [ 0, 0 ] ); + if ( nodeToMove && nodeToMove.type == 1 ) + { + nodeToMove.focus(); + onBlur( ev, element ); + onFocus( ev, nodeToMove ); + ev.preventDefault( true ); + } + else + onBlur( null, element ); + } + break; + + // LEFT-ARROW + case rtl ? 39 : 37 : + // relative is TD + if ( ( relative = element.getParent().getPrevious() ) ) + { + nodeToMove = relative.getChild( 0 ); + nodeToMove.focus(); + onBlur( ev, element ); + onFocus( ev, nodeToMove ); + ev.preventDefault( true ); + } + // relative is TR + else if ( ( relative = element.getParent().getParent().getPrevious() ) ) + { + nodeToMove = relative.getLast().getChild( 0 ); + nodeToMove.focus(); + onBlur( ev, element ); + onFocus( ev, nodeToMove ); + ev.preventDefault( true ); + } + else + onBlur( null, element ); + break; + default : + // Do not stop not handled events. + return; + } + }); + + function createColorTable() + { + // Create the base colors array. + var aColors = [ '00', '33', '66', '99', 'cc', 'ff' ]; + + // This function combines two ranges of three values from the color array into a row. + function appendColorRow( rangeA, rangeB ) + { + for ( var i = rangeA ; i < rangeA + 3 ; i++ ) + { + var row = table.$.insertRow( -1 ); + + for ( var j = rangeB ; j < rangeB + 3 ; j++ ) + { + for ( var n = 0 ; n < 6 ; n++ ) + { + appendColorCell( row, '#' + aColors[j] + aColors[n] + aColors[i] ); + } + } + } + } + + // This function create a single color cell in the color table. + function appendColorCell( targetRow, color ) + { + var cell = new $el( targetRow.insertCell( -1 ) ); + cell.setAttribute( 'class', 'ColorCell' ); + cell.setStyle( 'background-color', color ); + + cell.setStyle( 'width', '15px' ); + cell.setStyle( 'height', '15px' ); + + var index = cell.$.cellIndex + 1 + 18 * targetRow.rowIndex; + cell.append( CKEDITOR.dom.element.createFromHtml( + '' + color + ' ', CKEDITOR.document ) ); + } + + appendColorRow( 0, 0 ); + appendColorRow( 3, 0 ); + appendColorRow( 0, 3 ); + appendColorRow( 3, 3 ); + + // Create the last row. + var oRow = table.$.insertRow(-1) ; + + // Create the gray scale colors cells. + for ( var n = 0 ; n < 6 ; n++ ) + { + appendColorCell( oRow, '#' + aColors[n] + aColors[n] + aColors[n] ) ; + } + + // Fill the row with black cells. + for ( var i = 0 ; i < 12 ; i++ ) + { + appendColorCell( oRow, '#000000' ) ; + } + } + + var table = new $el( 'table' ); + createColorTable(); + var html = table.getHtml(); + + var numbering = function( id ) + { + return CKEDITOR.tools.getNextId() + '_' + id; + }, + hicolorId = numbering( 'hicolor' ), + hicolorTextId = numbering( 'hicolortext' ), + selHiColorId = numbering( 'selhicolor' ), + tableLabelId = numbering( 'color_table_label' ); + + return { + title : lang.title, + minWidth : 360, + minHeight : 220, + onLoad : function() + { + // Update reference. + dialog = this; + }, + contents : [ + { + id : 'picker', + label : lang.title, + accessKey : 'I', + elements : + [ + { + type : 'hbox', + padding : 0, + widths : [ '70%', '10%', '30%' ], + children : + [ + { + type : 'html', + html : '' + + ( !CKEDITOR.env.webkit ? html : '' ) + + '
      ' + lang.options +'', + onLoad : function() + { + var table = CKEDITOR.document.getById( this.domId ); + table.on( 'mouseover', updateHighlight ); + // In WebKit, the table content must be inserted after this event call (#6150) + CKEDITOR.env.webkit && table.setHtml( html ); + }, + focus: function() + { + var firstColor = this.getElement().getElementsByTag( 'a' ).getItem( 0 ); + firstColor.focus(); + } + }, + spacer, + { + type : 'vbox', + padding : 0, + widths : [ '70%', '5%', '25%' ], + children : + [ + { + type : 'html', + html : '' + lang.highlight +'\ +
      \ +
       
      ' + lang.selected + '\ +
      ' + }, + { + type : 'text', + label : lang.selected, + labelStyle: 'display:none', + id : 'selectedColor', + style : 'width: 74px', + onChange : function() + { + // Try to update color preview with new value. If fails, then set it no none. + try + { + $doc.getById( selHiColorId ).setStyle( 'background-color', this.getValue() ); + } + catch ( e ) + { + clearSelected(); + } + } + }, + spacer, + { + type : 'button', + id : 'clear', + style : 'margin-top: 5px', + label : lang.clear, + onClick : clearSelected + } + ] + } + ] + } + ] + } + ] + }; + } + ); diff --git a/htdocs/includes/ckeditor/_source/plugins/colordialog/plugin.js b/htdocs/includes/ckeditor/_source/plugins/colordialog/plugin.js new file mode 100755 index 00000000000..ca7f44ebe6e --- /dev/null +++ b/htdocs/includes/ckeditor/_source/plugins/colordialog/plugin.js @@ -0,0 +1,15 @@ +/* +Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +CKEDITOR.plugins.colordialog = +{ + init : function( editor ) + { + editor.addCommand( 'colordialog', new CKEDITOR.dialogCommand( 'colordialog' ) ); + CKEDITOR.dialog.add( 'colordialog', this.path + 'dialogs/colordialog.js' ); + } +}; + +CKEDITOR.plugins.add( 'colordialog', CKEDITOR.plugins.colordialog ); diff --git a/htdocs/includes/ckeditor/_source/plugins/contextmenu/plugin.js b/htdocs/includes/ckeditor/_source/plugins/contextmenu/plugin.js new file mode 100755 index 00000000000..31bc859c2a1 --- /dev/null +++ b/htdocs/includes/ckeditor/_source/plugins/contextmenu/plugin.js @@ -0,0 +1,177 @@ +/* +Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +CKEDITOR.plugins.add( 'contextmenu', +{ + requires : [ 'menu' ], + + // Make sure the base class (CKEDITOR.menu) is loaded before it (#3318). + onLoad : function() + { + CKEDITOR.plugins.contextMenu = CKEDITOR.tools.createClass( + { + base : CKEDITOR.menu, + + $ : function( editor ) + { + this.base.call( this, editor, + { + panel: + { + className : editor.skinClass + ' cke_contextmenu', + attributes : + { + 'aria-label' : editor.lang.contextmenu.options + } + } + }); + }, + + proto : + { + addTarget : function( element, nativeContextMenuOnCtrl ) + { + // Opera doesn't support 'contextmenu' event, we have duo approaches employed here: + // 1. Inherit the 'button override' hack we introduced in v2 (#4530), while this require the Opera browser + // option 'Allow script to detect context menu/right click events' to be always turned on. + // 2. Considering the fact that ctrl/meta key is not been occupied + // for multiple range selecting (like Gecko), we use this key + // combination as a fallback for triggering context-menu. (#4530) + if ( CKEDITOR.env.opera && !( 'oncontextmenu' in document.body )) + { + var contextMenuOverrideButton; + element.on( 'mousedown', function( evt ) + { + evt = evt.data; + if ( evt.$.button != 2 ) + { + if ( evt.getKeystroke() == CKEDITOR.CTRL + 1 ) + element.fire( 'contextmenu', evt ); + return; + } + + if ( nativeContextMenuOnCtrl + && ( CKEDITOR.env.mac ? evt.$.metaKey : evt.$.ctrlKey ) ) + return; + + var target = evt.getTarget(); + + if ( !contextMenuOverrideButton ) + { + var ownerDoc = target.getDocument(); + contextMenuOverrideButton = ownerDoc.createElement( 'input' ) ; + contextMenuOverrideButton.$.type = 'button' ; + ownerDoc.getBody().append( contextMenuOverrideButton ) ; + } + + contextMenuOverrideButton.setAttribute( 'style', 'position:absolute;top:' + ( evt.$.clientY - 2 ) + + 'px;left:' + ( evt.$.clientX - 2 ) + + 'px;width:5px;height:5px;opacity:0.01' ); + + } ); + + element.on( 'mouseup', function ( evt ) + { + if ( contextMenuOverrideButton ) + { + contextMenuOverrideButton.remove(); + contextMenuOverrideButton = undefined; + // Simulate 'contextmenu' event. + element.fire( 'contextmenu', evt.data ); + } + } ); + } + + element.on( 'contextmenu', function( event ) + { + var domEvent = event.data; + + if ( nativeContextMenuOnCtrl && + // Safari on Windows always show 'ctrlKey' as true in 'contextmenu' event, + // which make this property unreliable. (#4826) + ( CKEDITOR.env.webkit ? holdCtrlKey : ( CKEDITOR.env.mac ? domEvent.$.metaKey : domEvent.$.ctrlKey ) ) ) + return; + + + // Cancel the browser context menu. + domEvent.preventDefault(); + + var offsetParent = domEvent.getTarget().getDocument().getDocumentElement(), + offsetX = domEvent.$.clientX, + offsetY = domEvent.$.clientY; + + CKEDITOR.tools.setTimeout( function() + { + this.open( offsetParent, null, offsetX, offsetY ); + }, + 0, this ); + }, + this ); + + if ( CKEDITOR.env.opera ) + { + // 'contextmenu' event triggered by Windows menu key is unpreventable, + // cancel the key event itself. (#6534) + element.on( 'keypress' , function ( evt ) + { + var domEvent = evt.data; + + if ( domEvent.$.keyCode === 0 ) + domEvent.preventDefault(); + }); + } + + if ( CKEDITOR.env.webkit ) + { + var holdCtrlKey, + onKeyDown = function( event ) + { + holdCtrlKey = CKEDITOR.env.mac ? event.data.$.metaKey : event.data.$.ctrlKey ; + }, + resetOnKeyUp = function() + { + holdCtrlKey = 0; + }; + + element.on( 'keydown', onKeyDown ); + element.on( 'keyup', resetOnKeyUp ); + element.on( 'contextmenu', resetOnKeyUp ); + } + }, + + open : function( offsetParent, corner, offsetX, offsetY ) + { + this.editor.focus(); + offsetParent = offsetParent || CKEDITOR.document.getDocumentElement(); + this.show( offsetParent, corner, offsetX, offsetY ); + } + } + }); + }, + + beforeInit : function( editor ) + { + editor.contextMenu = new CKEDITOR.plugins.contextMenu( editor ); + + editor.addCommand( 'contextMenu', + { + exec : function() + { + editor.contextMenu.open( editor.document.getBody() ); + } + }); + } +}); + +/** + * Whether to show the browser native context menu when the CTRL or the + * META (Mac) key is pressed while opening the context menu. + * @name CKEDITOR.config.browserContextMenuOnCtrl + * @since 3.0.2 + * @type Boolean + * @default true + * @example + * config.browserContextMenuOnCtrl = false; + */ diff --git a/htdocs/includes/ckeditor/_source/plugins/devtools/lang/en.js b/htdocs/includes/ckeditor/_source/plugins/devtools/lang/en.js new file mode 100755 index 00000000000..328150569e3 --- /dev/null +++ b/htdocs/includes/ckeditor/_source/plugins/devtools/lang/en.js @@ -0,0 +1,16 @@ +/* +Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +CKEDITOR.plugins.setLang( 'devtools', 'en', +{ + devTools : + { + title : 'Element Information', + dialogName : 'Dialog window name', + tabName : 'Tab name', + elementId : 'Element ID', + elementType : 'Element type' + } +}); diff --git a/htdocs/includes/ckeditor/_source/plugins/devtools/plugin.js b/htdocs/includes/ckeditor/_source/plugins/devtools/plugin.js new file mode 100755 index 00000000000..a53b5042845 --- /dev/null +++ b/htdocs/includes/ckeditor/_source/plugins/devtools/plugin.js @@ -0,0 +1,173 @@ +/* +Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +CKEDITOR.plugins.add( 'devtools', +{ + lang : [ 'en' ], + + init : function( editor ) + { + editor._.showDialogDefinitionTooltips = 1; + }, + onLoad : function() + { + CKEDITOR.document.appendStyleText( CKEDITOR.config.devtools_styles || + '#cke_tooltip { padding: 5px; border: 2px solid #333; background: #ffffff }' + + '#cke_tooltip h2 { font-size: 1.1em; border-bottom: 1px solid; margin: 0; padding: 1px; }' + + '#cke_tooltip ul { padding: 0pt; list-style-type: none; }' ); + } +}); + +(function() +{ + function defaultCallback( editor, dialog, element, tabName ) + { + var lang = editor.lang.devTools, + link = '' + ( element ? element.type : 'content' ) + '', + str = + '

      ' + lang.title + '

      ' + + '
        ' + + '
      • ' + lang.dialogName + ' : ' + dialog.getName() + '
      • ' + + '
      • ' + lang.tabName + ' : ' + tabName + '
      • '; + + if ( element ) + str += '
      • ' + lang.elementId + ' : ' + element.id + '
      • '; + + str += '
      • ' + lang.elementType + ' : ' + link + '
      • '; + + return str + '
      '; + } + + function showTooltip( callback, el, editor, dialog, obj, tabName ) + { + var pos = el.getDocumentPosition(), + styles = { 'z-index' : CKEDITOR.dialog._.currentZIndex + 10, top : ( pos.y + el.getSize( 'height' ) ) + 'px' }; + + tooltip.setHtml( callback( editor, dialog, obj, tabName ) ); + tooltip.show(); + + // Translate coordinate for RTL. + if ( editor.lang.dir == 'rtl' ) + { + var viewPaneSize = CKEDITOR.document.getWindow().getViewPaneSize(); + styles.right = ( viewPaneSize.width - pos.x - el.getSize( 'width' ) ) + 'px'; + } + else + styles.left = pos.x + 'px'; + + tooltip.setStyles( styles ); + } + + var tooltip; + CKEDITOR.on( 'reset', function() + { + tooltip && tooltip.remove(); + tooltip = null; + }); + + CKEDITOR.on( 'dialogDefinition', function( evt ) + { + var editor = evt.editor; + if ( editor._.showDialogDefinitionTooltips ) + { + if ( !tooltip ) + { + tooltip = CKEDITOR.dom.element.createFromHtml( '
      ', CKEDITOR.document ); + tooltip.hide(); + tooltip.on( 'mouseover', function(){ this.show(); } ); + tooltip.on( 'mouseout', function(){ this.hide(); } ); + tooltip.appendTo( CKEDITOR.document.getBody() ); + } + + var dialog = evt.data.definition.dialog, + callback = editor.config.devtools_textCallback || defaultCallback; + + dialog.on( 'load', function() + { + var tabs = dialog.parts.tabs.getChildren(), tab; + for ( var i = 0, len = tabs.count(); i < len; i++ ) + { + tab = tabs.getItem( i ); + tab.on( 'mouseover', function() + { + var id = this.$.id; + showTooltip( callback, this, editor, dialog, null, id.substring( 4, id.lastIndexOf( '_' ) ) ); + }); + tab.on( 'mouseout', function() + { + tooltip.hide(); + }); + } + + dialog.foreach( function( obj ) + { + if ( obj.type in { hbox : 1, vbox : 1 } ) + return; + + var el = obj.getElement(); + if ( el ) + { + el.on( 'mouseover', function() + { + showTooltip( callback, this, editor, dialog, obj, dialog._.currentTabId ); + }); + el.on( 'mouseout', function() + { + tooltip.hide(); + }); + } + }); + }); + } + }); +})(); + +/** + * A function that returns the text to be displayed inside the developer tooltip when hovering over a dialog UI element. + * There are 4 parameters that are being passed into the function: editor, dialog, element, tab name. + * @name editor.config.devtools_textCallback + * @since 3.6 + * @type Function + * @default (see example) + * @example + * // This is actually the default value. + * // Show dialog name, tab id and element id. + * config.devtools_textCallback = function( editor, dialog, element, tabName ) + * { + * var lang = editor.lang.devTools, + * link = '' + ( element ? element.type : 'content' ) + '', + * str = + * '

      ' + lang.title + '

      ' + + * '
        ' + + * '
      • ' + lang.dialogName + ' : ' + dialog.getName() + '
      • ' + + * '
      • ' + lang.tabName + ' : ' + tabName + '
      • '; + * + * if ( element ) + * str += '
      • ' + lang.elementId + ' : ' + element.id + '
      • '; + * + * str += '
      • ' + lang.elementType + ' : ' + link + '
      • '; + * + * return str + '
      '; + * } + */ + +/** + * A setting that holds CSS rules to be injected do page and contain styles to be applied to the tooltip element. + * @name CKEDITOR.config.devtools_styles + * @since 3.6 + * @type String + * @default (see example) + * @example + * // This is actually the default value. + * CKEDITOR.config.devtools_styles = " + * #cke_tooltip { padding: 5px; border: 2px solid #333; background: #ffffff } + * #cke_tooltip h2 { font-size: 1.1em; border-bottom: 1px solid; margin: 0; padding: 1px; } + * #cke_tooltip ul { padding: 0pt; list-style-type: none; } + * "; + */ diff --git a/htdocs/includes/ckeditor/_source/plugins/dialog/dialogDefinition.js b/htdocs/includes/ckeditor/_source/plugins/dialog/dialogDefinition.js new file mode 100755 index 00000000000..a3db7f8941b --- /dev/null +++ b/htdocs/includes/ckeditor/_source/plugins/dialog/dialogDefinition.js @@ -0,0 +1,1166 @@ +/* +Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @fileOverview Defines the "virtual" dialog, dialog content and dialog button + * definition classes. + */ + +/** + * The definition of a dialog window. + *
      + * This class is not really part of the API. It just illustrates the properties + * that developers can use to define and create dialogs. + *
      + * @name CKEDITOR.dialog.definition + * @constructor + * @example + * // There is no constructor for this class, the user just has to define an + * // object with the appropriate properties. + * + * CKEDITOR.dialog.add( 'testOnly', function( editor ) + * { + * return { + * title : 'Test Dialog', + * resizable : CKEDITOR.DIALOG_RESIZE_BOTH, + * minWidth : 500, + * minHeight : 400, + * contents : [ + * { + * id : 'tab1', + * label : 'First Tab', + * title : 'First Tab Title', + * accessKey : 'Q', + * elements : [ + * { + * type : 'text', + * label : 'Test Text 1', + * id : 'testText1', + * 'default' : 'hello world!' + * } + * ] + * } + * ] + * }; + * }); + */ + +/** + * The dialog title, displayed in the dialog's header. Required. + * @name CKEDITOR.dialog.definition.prototype.title + * @field + * @type String + * @example + */ + +/** + * How the dialog can be resized, must be one of the four contents defined below. + *

      + * CKEDITOR.DIALOG_RESIZE_NONE
      + * CKEDITOR.DIALOG_RESIZE_WIDTH
      + * CKEDITOR.DIALOG_RESIZE_HEIGHT
      + * CKEDITOR.DIALOG_RESIZE_BOTH
      + * @name CKEDITOR.dialog.definition.prototype.resizable + * @field + * @type Number + * @default CKEDITOR.DIALOG_RESIZE_NONE + * @example + */ + +/** + * The minimum width of the dialog, in pixels. + * @name CKEDITOR.dialog.definition.prototype.minWidth + * @field + * @type Number + * @default 600 + * @example + */ + +/** + * The minimum height of the dialog, in pixels. + * @name CKEDITOR.dialog.definition.prototype.minHeight + * @field + * @type Number + * @default 400 + * @example + */ + + +/** + * The initial width of the dialog, in pixels. + * @name CKEDITOR.dialog.definition.prototype.width + * @field + * @type Number + * @default @CKEDITOR.dialog.definition.prototype.minWidth + * @since 3.5.3 + * @example + */ + +/** + * The initial height of the dialog, in pixels. + * @name CKEDITOR.dialog.definition.prototype.height + * @field + * @type Number + * @default @CKEDITOR.dialog.definition.prototype.minHeight + * @since 3.5.3 + * @example + */ + +/** + * The buttons in the dialog, defined as an array of + * {@link CKEDITOR.dialog.definition.button} objects. + * @name CKEDITOR.dialog.definition.prototype.buttons + * @field + * @type Array + * @default [ CKEDITOR.dialog.okButton, CKEDITOR.dialog.cancelButton ] + * @example + */ + +/** + * The contents in the dialog, defined as an array of + * {@link CKEDITOR.dialog.definition.content} objects. Required. + * @name CKEDITOR.dialog.definition.prototype.contents + * @field + * @type Array + * @example + */ + +/** + * The function to execute when OK is pressed. + * @name CKEDITOR.dialog.definition.prototype.onOk + * @field + * @type Function + * @example + */ + +/** + * The function to execute when Cancel is pressed. + * @name CKEDITOR.dialog.definition.prototype.onCancel + * @field + * @type Function + * @example + */ + +/** + * The function to execute when the dialog is displayed for the first time. + * @name CKEDITOR.dialog.definition.prototype.onLoad + * @field + * @type Function + * @example + */ + +/** + * The function to execute when the dialog is loaded (executed every time the dialog is opened). + * @name CKEDITOR.dialog.definition.prototype.onShow + * @field + * @type Function + * @example + */ + +/** + *
      This class is not really part of the API. It just illustrates the properties + * that developers can use to define and create dialog content pages.
      + * @name CKEDITOR.dialog.definition.content + * @constructor + * @example + * // There is no constructor for this class, the user just has to define an + * // object with the appropriate properties. + */ + +/** + * The id of the content page. + * @name CKEDITOR.dialog.definition.content.prototype.id + * @field + * @type String + * @example + */ + +/** + * The tab label of the content page. + * @name CKEDITOR.dialog.definition.content.prototype.label + * @field + * @type String + * @example + */ + +/** + * The popup message of the tab label. + * @name CKEDITOR.dialog.definition.content.prototype.title + * @field + * @type String + * @example + */ + +/** + * The CTRL hotkey for switching to the tab. + * @name CKEDITOR.dialog.definition.content.prototype.accessKey + * @field + * @type String + * @example + * contentDefinition.accessKey = 'Q'; // Switch to this page when CTRL-Q is pressed. + */ + +/** + * The UI elements contained in this content page, defined as an array of + * {@link CKEDITOR.dialog.definition.uiElement} objects. + * @name CKEDITOR.dialog.definition.content.prototype.elements + * @field + * @type Array + * @example + */ + +/** + * The definition of user interface element (textarea, radio etc). + *
      This class is not really part of the API. It just illustrates the properties + * that developers can use to define and create dialog UI elements.
      + * @name CKEDITOR.dialog.definition.uiElement + * @constructor + * @see CKEDITOR.ui.dialog.uiElement + * @example + * // There is no constructor for this class, the user just has to define an + * // object with the appropriate properties. + */ + +/** + * The id of the UI element. + * @name CKEDITOR.dialog.definition.uiElement.prototype.id + * @field + * @type String + * @example + */ + +/** + * The type of the UI element. Required. + * @name CKEDITOR.dialog.definition.uiElement.prototype.type + * @field + * @type String + * @example + */ + +/** + * The popup label of the UI element. + * @name CKEDITOR.dialog.definition.uiElement.prototype.title + * @field + * @type String + * @example + */ + +/** + * CSS class names to append to the UI element. + * @name CKEDITOR.dialog.definition.uiElement.prototype.className + * @field + * @type String + * @example + */ + +/** + * Inline CSS classes to append to the UI element. + * @name CKEDITOR.dialog.definition.uiElement.prototype.style + * @field + * @type String + * @example + */ + +/** + * Horizontal alignment (in container) of the UI element. + * @name CKEDITOR.dialog.definition.uiElement.prototype.align + * @field + * @type String + * @example + */ + +/** + * Function to execute the first time the UI element is displayed. + * @name CKEDITOR.dialog.definition.uiElement.prototype.onLoad + * @field + * @type Function + * @example + */ + +/** + * Function to execute whenever the UI element's parent dialog is displayed. + * @name CKEDITOR.dialog.definition.uiElement.prototype.onShow + * @field + * @type Function + * @example + */ + +/** + * Function to execute whenever the UI element's parent dialog is closed. + * @name CKEDITOR.dialog.definition.uiElement.prototype.onHide + * @field + * @type Function + * @example + */ + +/** + * Function to execute whenever the UI element's parent dialog's {@link CKEDITOR.dialog.definition.setupContent} method is executed. + * It usually takes care of the respective UI element as a standalone element. + * @name CKEDITOR.dialog.definition.uiElement.prototype.setup + * @field + * @type Function + * @example + */ + +/** + * Function to execute whenever the UI element's parent dialog's {@link CKEDITOR.dialog.definition.commitContent} method is executed. + * It usually takes care of the respective UI element as a standalone element. + * @name CKEDITOR.dialog.definition.uiElement.prototype.commit + * @field + * @type Function + * @example + */ + +// ----- hbox ----- + +/** + * Horizontal layout box for dialog UI elements, auto-expends to available width of container. + *
      + * This class is not really part of the API. It just illustrates the properties + * that developers can use to define and create horizontal layouts. + *

      Once the dialog is opened, the created element becomes a {@link CKEDITOR.ui.dialog.hbox} object and can be accessed with {@link CKEDITOR.dialog#getContentElement}. + *
      + * @name CKEDITOR.dialog.definition.hbox + * @extends CKEDITOR.dialog.definition.uiElement + * @constructor + * @example + * // There is no constructor for this class, the user just has to define an + * // object with the appropriate properties. + * + * // Example: + * { + * type : 'hbox', + * widths : [ '25%', '25%', '50%' ], + * children : + * [ + * { + * type : 'text', + * id : 'id1', + * width : '40px', + * }, + * { + * type : 'text', + * id : 'id2', + * width : '40px', + * }, + * { + * type : 'text', + * id : 'id3' + * } + * ] + * } + */ + +/** + * Array of {@link CKEDITOR.ui.dialog.uiElement} objects inside this container. + * @name CKEDITOR.dialog.definition.hbox.prototype.children + * @field + * @type Array + * @example + */ + +/** + * (Optional) The widths of child cells. + * @name CKEDITOR.dialog.definition.hbox.prototype.widths + * @field + * @type Array + * @example + */ + +/** + * (Optional) The height of the layout. + * @name CKEDITOR.dialog.definition.hbox.prototype.height + * @field + * @type Number + * @example + */ + +/** + * The CSS styles to apply to this element. + * @name CKEDITOR.dialog.definition.hbox.prototype.styles + * @field + * @type String + * @example + */ + +/** + * (Optional) The padding width inside child cells. Example: 0, 1. + * @name CKEDITOR.dialog.definition.hbox.prototype.padding + * @field + * @type Number + * @example + */ + +/** + * (Optional) The alignment of the whole layout. Example: center, top. + * @name CKEDITOR.dialog.definition.hbox.prototype.align + * @field + * @type String + * @example + */ + +// ----- vbox ----- + +/** + * Vertical layout box for dialog UI elements. + *
      + * This class is not really part of the API. It just illustrates the properties + * that developers can use to define and create vertical layouts. + *

      Once the dialog is opened, the created element becomes a {@link CKEDITOR.ui.dialog.vbox} object and can be accessed with {@link CKEDITOR.dialog#getContentElement}. + *
      + * + * @name CKEDITOR.dialog.definition.vbox + * @extends CKEDITOR.dialog.definition.uiElement + * @constructor + * @example + * // There is no constructor for this class, the user just has to define an + * // object with the appropriate properties. + * + * // Example: + * { + * type : 'vbox', + * align : 'right', + * width : '200px', + * children : + * [ + * { + * type : 'text', + * id : 'age', + * label : 'Age' + * }, + * { + * type : 'text', + * id : 'sex', + * label : 'Sex' + * }, + * { + * type : 'text', + * id : 'nationality', + * label : 'Nationality' + * } + * ] + * } + */ + +/** + * Array of {@link CKEDITOR.ui.dialog.uiElement} objects inside this container. + * @name CKEDITOR.dialog.definition.vbox.prototype.children + * @field + * @type Array + * @example + */ + +/** + * (Optional) The width of the layout. + * @name CKEDITOR.dialog.definition.vbox.prototype.width + * @field + * @type Array + * @example + */ + +/** + * (Optional) The heights of individual cells. + * @name CKEDITOR.dialog.definition.vbox.prototype.heights + * @field + * @type Number + * @example + */ + +/** + * The CSS styles to apply to this element. + * @name CKEDITOR.dialog.definition.vbox.prototype.styles + * @field + * @type String + * @example + */ + +/** + * (Optional) The padding width inside child cells. Example: 0, 1. + * @name CKEDITOR.dialog.definition.vbox.prototype.padding + * @field + * @type Number + * @example + */ + +/** + * (Optional) The alignment of the whole layout. Example: center, top. + * @name CKEDITOR.dialog.definition.vbox.prototype.align + * @field + * @type String + * @example + */ + +/** + * (Optional) Whether the layout should expand vertically to fill its container. + * @name CKEDITOR.dialog.definition.vbox.prototype.expand + * @field + * @type Boolean + * @example + */ + +// ----- labeled element ------ + +/** + * The definition of labeled user interface element (textarea, textInput etc). + *
      This class is not really part of the API. It just illustrates the properties + * that developers can use to define and create dialog UI elements.
      + * @name CKEDITOR.dialog.definition.labeledElement + * @extends CKEDITOR.dialog.definition.uiElement + * @constructor + * @see CKEDITOR.ui.dialog.labeledElement + * @example + * // There is no constructor for this class, the user just has to define an + * // object with the appropriate properties. + */ + +/** + * The label of the UI element. + * @name CKEDITOR.dialog.definition.labeledElement.prototype.label + * @type String + * @field + * @example + * { + * type : 'text', + * label : 'My Label ' + * } + */ + +/** + * (Optional) Specify the layout of the label. Set to 'horizontal' for horizontal layout. + * The default layout is vertical. + * @name CKEDITOR.dialog.definition.labeledElement.prototype.labelLayout + * @type String + * @field + * @example + * { + * type : 'text', + * label : 'My Label ', + * labelLayout : 'horizontal', + * } + */ + +/** + * (Optional) Applies only to horizontal layouts: a two elements array of lengths to specify the widths of the +* label and the content element. See also {@link CKEDITOR.dialog.definition.labeledElement#labelLayout}. + * @name CKEDITOR.dialog.definition.labeledElement.prototype.widths + * @type Array + * @field + * @example + * { + * type : 'text', + * label : 'My Label ', + * labelLayout : 'horizontal', + * widths : [100, 200], + * } + */ + +/** + * Specify the inline style of the uiElement label. + * @name CKEDITOR.dialog.definition.labeledElement.prototype.labelStyle + * @type String + * @field + * @example + * { + * type : 'text', + * label : 'My Label ', + * labelStyle : 'color: red', + * } + */ + + +/** + * Specify the inline style of the input element. + * @name CKEDITOR.dialog.definition.labeledElement.prototype.inputStyle + * @type String + * @since 3.6.1 + * @field + * @example + * { + * type : 'text', + * label : 'My Label ', + * inputStyle : 'text-align:center', + * } + */ + +/** + * Specify the inline style of the input element container . + * @name CKEDITOR.dialog.definition.labeledElement.prototype.controlStyle + * @type String + * @since 3.6.1 + * @field + * @example + * { + * type : 'text', + * label : 'My Label ', + * controlStyle : 'width:3em', + * } + */ + + +// ----- button ------ + +/** + * The definition of a button. + *
      + * This class is not really part of the API. It just illustrates the properties + * that developers can use to define and create buttons. + *

      Once the dialog is opened, the created element becomes a {@link CKEDITOR.ui.dialog.button} object and can be accessed with {@link CKEDITOR.dialog#getContentElement}. + *
      + * For a complete example of dialog definition, please check {@link CKEDITOR.dialog.add}. + * @name CKEDITOR.dialog.definition.button + * @extends CKEDITOR.dialog.definition.uiElement + * @constructor + * @example + * // There is no constructor for this class, the user just has to define an + * // object with the appropriate properties. + * + * // Example: + * { + * type : 'button', + * id : 'buttonId', + * label : 'Click me', + * title : 'My title', + * onClick : function() { + * // this = CKEDITOR.ui.dialog.button + * alert( 'Clicked: ' + this.id ); + * } + * } + */ + +/** + * Whether the button is disabled. + * @name CKEDITOR.dialog.definition.button.prototype.disabled + * @type Boolean + * @field + * @example + */ + +/** + * The label of the UI element. + * @name CKEDITOR.dialog.definition.button.prototype.label + * @type String + * @field + * @example + */ + +// ----- checkbox ------ + +/** + * The definition of a checkbox element. + *
      + * This class is not really part of the API. It just illustrates the properties + * that developers can use to define and create groups of checkbox buttons. + *

      Once the dialog is opened, the created element becomes a {@link CKEDITOR.ui.dialog.checkbox} object and can be accessed with {@link CKEDITOR.dialog#getContentElement}. + *
      + * For a complete example of dialog definition, please check {@link CKEDITOR.dialog.add}. + * @name CKEDITOR.dialog.definition.checkbox + * @extends CKEDITOR.dialog.definition.uiElement + * @constructor + * @example + * // There is no constructor for this class, the user just has to define an + * // object with the appropriate properties. + * + * // Example: + * { + * type : 'checkbox', + * id : 'agree', + * label : 'I agree', + * 'default' : 'checked', + * onClick : function() { + * // this = CKEDITOR.ui.dialog.checkbox + * alert( 'Checked: ' + this.getValue() ); + * } + * } + */ + +/** + * (Optional) The validation function. + * @name CKEDITOR.dialog.definition.checkbox.prototype.validate + * @field + * @type Function + * @example + */ + +/** + * The label of the UI element. + * @name CKEDITOR.dialog.definition.checkbox.prototype.label + * @type String + * @field + * @example + */ + +/** + * The default state. + * @name CKEDITOR.dialog.definition.checkbox.prototype.default + * @type String + * @field + * @default + * '' (unchecked) + * @example + */ + +// ----- file ----- + +/** + * The definition of a file upload input. + *
      + * This class is not really part of the API. It just illustrates the properties + * that developers can use to define and create file upload elements. + *

      Once the dialog is opened, the created element becomes a {@link CKEDITOR.ui.dialog.file} object and can be accessed with {@link CKEDITOR.dialog#getContentElement}. + *
      + * For a complete example of dialog definition, please check {@link CKEDITOR.dialog.add}. + * @name CKEDITOR.dialog.definition.file + * @extends CKEDITOR.dialog.definition.labeledElement + * @constructor + * @example + * // There is no constructor for this class, the user just has to define an + * // object with the appropriate properties. + * + * // Example: + * { + * type : 'file', + * id : 'upload', + * label : 'Select file from your computer', + * size : 38 + * }, + * { + * type : 'fileButton', + * id : 'fileId', + * label : 'Upload file', + * 'for' : [ 'tab1', 'upload' ] + * filebrowser : { + * onSelect : function( fileUrl, data ) { + * alert( 'Successfully uploaded: ' + fileUrl ); + * } + * } + * } + */ + +/** + * (Optional) The validation function. + * @name CKEDITOR.dialog.definition.file.prototype.validate + * @field + * @type Function + * @example + */ + +/** + * (Optional) The action attribute of the form element associated with this file upload input. + * If empty, CKEditor will use path to server connector for currently opened folder. + * @name CKEDITOR.dialog.definition.file.prototype.action + * @type String + * @field + * @example + */ + +/** + * The size of the UI element. + * @name CKEDITOR.dialog.definition.file.prototype.size + * @type Number + * @field + * @example + */ + +// ----- fileButton ----- + +/** + * The definition of a button for submitting the file in a file upload input. + *
      + * This class is not really part of the API. It just illustrates the properties + * that developers can use to define and create a button for submitting the file in a file upload input. + *

      Once the dialog is opened, the created element becomes a {@link CKEDITOR.ui.dialog.fileButton} object and can be accessed with {@link CKEDITOR.dialog#getContentElement}. + *
      + * For a complete example of dialog definition, please check {@link CKEDITOR.dialog.add}. + * @name CKEDITOR.dialog.definition.fileButton + * @extends CKEDITOR.dialog.definition.uiElement + * @constructor + * @example + * // There is no constructor for this class, the user just has to define an + * // object with the appropriate properties. + * + * // Example: + * { + * type : 'file', + * id : 'upload', + * label : 'Select file from your computer', + * size : 38 + * }, + * { + * type : 'fileButton', + * id : 'fileId', + * label : 'Upload file', + * 'for' : [ 'tab1', 'upload' ] + * filebrowser : { + * onSelect : function( fileUrl, data ) { + * alert( 'Successfully uploaded: ' + fileUrl ); + * } + * } + * } + */ + +/** + * (Optional) The validation function. + * @name CKEDITOR.dialog.definition.fileButton.prototype.validate + * @field + * @type Function + * @example + */ + +/** + * The label of the UI element. + * @name CKEDITOR.dialog.definition.fileButton.prototype.label + * @type String + * @field + * @example + */ + +/** + * The instruction for CKEditor how to deal with file upload. + * By default, the file and fileButton elements will not work "as expected" if this attribute is not set. + * @name CKEDITOR.dialog.definition.fileButton.prototype.filebrowser + * @type String|Object + * @field + * @example + * // Update field with id 'txtUrl' in the 'tab1' tab when file is uploaded. + * filebrowser : 'tab1:txtUrl' + * + * // Call custom onSelect function when file is successfully uploaded. + * filebrowser : { + * onSelect : function( fileUrl, data ) { + * alert( 'Successfully uploaded: ' + fileUrl ); + * } + * } + */ + +/** + * An array that contains pageId and elementId of the file upload input element for which this button is created. + * @name CKEDITOR.dialog.definition.fileButton.prototype.for + * @type String + * @field + * @example + * [ pageId, elementId ] + */ + +// ----- html ----- + +/** + * The definition of a raw HTML element. + *
      + * This class is not really part of the API. It just illustrates the properties + * that developers can use to define and create elements made from raw HTML code. + *

      Once the dialog is opened, the created element becomes a {@link CKEDITOR.ui.dialog.html} object and can be accessed with {@link CKEDITOR.dialog#getContentElement}. + *
      + * For a complete example of dialog definition, please check {@link CKEDITOR.dialog.add}.
      + * To access HTML elements use {@link CKEDITOR.dom.document#getById} + * @name CKEDITOR.dialog.definition.html + * @extends CKEDITOR.dialog.definition.uiElement + * @constructor + * @example + * // There is no constructor for this class, the user just has to define an + * // object with the appropriate properties. + * + * // Example 1: + * { + * type : 'html', + * html : '<h3>This is some sample HTML content.</h3>' + * } + * @example + * // Example 2: + * // Complete sample with document.getById() call when the "Ok" button is clicked. + * var dialogDefinition = + * { + * title : 'Sample dialog', + * minWidth : 300, + * minHeight : 200, + * onOk : function() { + * // "this" is now a CKEDITOR.dialog object. + * var document = this.getElement().getDocument(); + * // document = CKEDITOR.dom.document + * var element = document.getById( 'myDiv' ); + * if ( element ) + * alert( element.getHtml() ); + * }, + * contents : [ + * { + * id : 'tab1', + * label : '', + * title : '', + * elements : + * [ + * { + * type : 'html', + * html : '<div id="myDiv">Sample <b>text</b>.</div><div id="otherId">Another div.</div>' + * }, + * ] + * } + * ], + * buttons : [ CKEDITOR.dialog.cancelButton, CKEDITOR.dialog.okButton ] + * }; + */ + +/** + * (Required) HTML code of this element. + * @name CKEDITOR.dialog.definition.html.prototype.html + * @type String + * @field + * @example + */ + +// ----- radio ------ + +/** + * The definition of a radio group. + *
      + * This class is not really part of the API. It just illustrates the properties + * that developers can use to define and create groups of radio buttons. + *

      Once the dialog is opened, the created element becomes a {@link CKEDITOR.ui.dialog.radio} object and can be accessed with {@link CKEDITOR.dialog#getContentElement}. + *
      + * For a complete example of dialog definition, please check {@link CKEDITOR.dialog.add}. + * @name CKEDITOR.dialog.definition.radio + * @extends CKEDITOR.dialog.definition.labeledElement + * @constructor + * @example + * // There is no constructor for this class, the user just has to define an + * // object with the appropriate properties. + * + * // Example: + * { + * type : 'radio', + * id : 'country', + * label : 'Which country is bigger', + * items : [ [ 'France', 'FR' ], [ 'Germany', 'DE' ] ] , + * style : 'color:green', + * 'default' : 'DE', + * onClick : function() { + * // this = CKEDITOR.ui.dialog.radio + * alert( 'Current value: ' + this.getValue() ); + * } + * } + */ + +/** + * The default value. + * @name CKEDITOR.dialog.definition.radio.prototype.default + * @type String + * @field + * @example + */ + +/** + * (Optional) The validation function. + * @name CKEDITOR.dialog.definition.radio.prototype.validate + * @field + * @type Function + * @example + */ + +/** + * An array of options. Each option is a 1- or 2-item array of format [ 'Description', 'Value' ]. If 'Value' is missing, then the value would be assumed to be the same as the description. + * @name CKEDITOR.dialog.definition.radio.prototype.items + * @field + * @type Array + * @example + */ + +// ----- selectElement ------ + +/** + * The definition of a select element. + *
      + * This class is not really part of the API. It just illustrates the properties + * that developers can use to define and create select elements. + *

      Once the dialog is opened, the created element becomes a {@link CKEDITOR.ui.dialog.select} object and can be accessed with {@link CKEDITOR.dialog#getContentElement}. + *
      + * For a complete example of dialog definition, please check {@link CKEDITOR.dialog.add}. + * @name CKEDITOR.dialog.definition.select + * @extends CKEDITOR.dialog.definition.labeledElement + * @constructor + * @example + * // There is no constructor for this class, the user just has to define an + * // object with the appropriate properties. + * + * // Example: + * { + * type : 'select', + * id : 'sport', + * label : 'Select your favourite sport', + * items : [ [ 'Basketball' ], [ 'Baseball' ], [ 'Hockey' ], [ 'Football' ] ], + * 'default' : 'Football', + * onChange : function( api ) { + * // this = CKEDITOR.ui.dialog.select + * alert( 'Current value: ' + this.getValue() ); + * } + * } + */ + +/** + * The default value. + * @name CKEDITOR.dialog.definition.select.prototype.default + * @type String + * @field + * @example + */ + +/** + * (Optional) The validation function. + * @name CKEDITOR.dialog.definition.select.prototype.validate + * @field + * @type Function + * @example + */ + +/** + * An array of options. Each option is a 1- or 2-item array of format [ 'Description', 'Value' ]. If 'Value' is missing, then the value would be assumed to be the same as the description. + * @name CKEDITOR.dialog.definition.select.prototype.items + * @field + * @type Array + * @example + */ + +/** + * (Optional) Set this to true if you'd like to have a multiple-choice select box. + * @name CKEDITOR.dialog.definition.select.prototype.multiple + * @type Boolean + * @field + * @example + * @default false + */ + +/** + * (Optional) The number of items to display in the select box. + * @name CKEDITOR.dialog.definition.select.prototype.size + * @type Number + * @field + * @example + */ + +// ----- textInput ----- + +/** + * The definition of a text field (single line). + *
      + * This class is not really part of the API. It just illustrates the properties + * that developers can use to define and create text fields. + *

      Once the dialog is opened, the created element becomes a {@link CKEDITOR.ui.dialog.textInput} object and can be accessed with {@link CKEDITOR.dialog#getContentElement}. + *
      + * For a complete example of dialog definition, please check {@link CKEDITOR.dialog.add}. + * @name CKEDITOR.dialog.definition.textInput + * @extends CKEDITOR.dialog.definition.labeledElement + * @constructor + * @example + * // There is no constructor for this class, the user just has to define an + * // object with the appropriate properties. + * + * { + * type : 'text', + * id : 'name', + * label : 'Your name', + * 'default' : '', + * validate : function() { + * if ( !this.getValue() ) + * { + * api.openMsgDialog( '', 'Name cannot be empty.' ); + * return false; + * } + * } + * } + */ + +/** + * The default value. + * @name CKEDITOR.dialog.definition.textInput.prototype.default + * @type String + * @field + * @example + */ + +/** + * (Optional) The maximum length. + * @name CKEDITOR.dialog.definition.textInput.prototype.maxLength + * @type Number + * @field + * @example + */ + +/** + * (Optional) The size of the input field. + * @name CKEDITOR.dialog.definition.textInput.prototype.size + * @type Number + * @field + * @example + */ + +/** + * (Optional) The validation function. + * @name CKEDITOR.dialog.definition.textInput.prototype.validate + * @field + * @type Function + * @example + */ + +// ----- textarea ------ + +/** + * The definition of a text field (multiple lines). + *
      + * This class is not really part of the API. It just illustrates the properties + * that developers can use to define and create textarea. + *

      Once the dialog is opened, the created element becomes a {@link CKEDITOR.ui.dialog.textarea} object and can be accessed with {@link CKEDITOR.dialog#getContentElement}. + *
      + * For a complete example of dialog definition, please check {@link CKEDITOR.dialog.add}. + * @name CKEDITOR.dialog.definition.textarea + * @extends CKEDITOR.dialog.definition.labeledElement + * @constructor + * @example + * // There is no constructor for this class, the user just has to define an + * // object with the appropriate properties. + * + * // Example: + * { + * type : 'textarea', + * id : 'message', + * label : 'Your comment', + * 'default' : '', + * validate : function() { + * if ( this.getValue().length < 5 ) + * { + * api.openMsgDialog( 'The comment is too short.' ); + * return false; + * } + * } + * } + */ + +/** + * The number of rows. + * @name CKEDITOR.dialog.definition.textarea.prototype.rows + * @type Number + * @field + * @example + */ + +/** + * The number of columns. + * @name CKEDITOR.dialog.definition.textarea.prototype.cols + * @type Number + * @field + * @example + */ + +/** + * (Optional) The validation function. + * @name CKEDITOR.dialog.definition.textarea.prototype.validate + * @field + * @type Function + * @example + */ + +/** + * The default value. + * @name CKEDITOR.dialog.definition.textarea.prototype.default + * @type String + * @field + * @example + */ diff --git a/htdocs/includes/ckeditor/_source/plugins/dialog/plugin.js b/htdocs/includes/ckeditor/_source/plugins/dialog/plugin.js new file mode 100755 index 00000000000..d24a80fc512 --- /dev/null +++ b/htdocs/includes/ckeditor/_source/plugins/dialog/plugin.js @@ -0,0 +1,3289 @@ +/* +Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @fileOverview The floating dialog plugin. + */ + +/** + * No resize for this dialog. + * @constant + */ +CKEDITOR.DIALOG_RESIZE_NONE = 0; + +/** + * Only allow horizontal resizing for this dialog, disable vertical resizing. + * @constant + */ +CKEDITOR.DIALOG_RESIZE_WIDTH = 1; + +/** + * Only allow vertical resizing for this dialog, disable horizontal resizing. + * @constant + */ +CKEDITOR.DIALOG_RESIZE_HEIGHT = 2; + +/* + * Allow the dialog to be resized in both directions. + * @constant + */ +CKEDITOR.DIALOG_RESIZE_BOTH = 3; + +(function() +{ + var cssLength = CKEDITOR.tools.cssLength; + function isTabVisible( tabId ) + { + return !!this._.tabs[ tabId ][ 0 ].$.offsetHeight; + } + + function getPreviousVisibleTab() + { + var tabId = this._.currentTabId, + length = this._.tabIdList.length, + tabIndex = CKEDITOR.tools.indexOf( this._.tabIdList, tabId ) + length; + + for ( var i = tabIndex - 1 ; i > tabIndex - length ; i-- ) + { + if ( isTabVisible.call( this, this._.tabIdList[ i % length ] ) ) + return this._.tabIdList[ i % length ]; + } + + return null; + } + + function getNextVisibleTab() + { + var tabId = this._.currentTabId, + length = this._.tabIdList.length, + tabIndex = CKEDITOR.tools.indexOf( this._.tabIdList, tabId ); + + for ( var i = tabIndex + 1 ; i < tabIndex + length ; i++ ) + { + if ( isTabVisible.call( this, this._.tabIdList[ i % length ] ) ) + return this._.tabIdList[ i % length ]; + } + + return null; + } + + + function clearOrRecoverTextInputValue( container, isRecover ) + { + var inputs = container.$.getElementsByTagName( 'input' ); + for ( var i = 0, length = inputs.length; i < length ; i++ ) + { + var item = new CKEDITOR.dom.element( inputs[ i ] ); + + if ( item.getAttribute( 'type' ).toLowerCase() == 'text' ) + { + if ( isRecover ) + { + item.setAttribute( 'value', item.getCustomData( 'fake_value' ) || '' ); + item.removeCustomData( 'fake_value' ); + } + else + { + item.setCustomData( 'fake_value', item.getAttribute( 'value' ) ); + item.setAttribute( 'value', '' ); + } + } + } + } + + // Handle dialog element validation state UI changes. + function handleFieldValidated( isValid, msg ) + { + var input = this.getInputElement(); + if ( input ) + { + isValid ? input.removeAttribute( 'aria-invalid' ) + : input.setAttribute( 'aria-invalid', true ); + } + + if ( !isValid ) + { + if ( this.select ) + this.select(); + else + this.focus(); + } + + msg && alert( msg ); + + this.fire( 'validated', { valid : isValid, msg : msg } ); + } + + function resetField() + { + var input = this.getInputElement(); + input && input.removeAttribute( 'aria-invalid' ); + } + + + /** + * This is the base class for runtime dialog objects. An instance of this + * class represents a single named dialog for a single editor instance. + * @param {Object} editor The editor which created the dialog. + * @param {String} dialogName The dialog's registered name. + * @constructor + * @example + * var dialogObj = new CKEDITOR.dialog( editor, 'smiley' ); + */ + CKEDITOR.dialog = function( editor, dialogName ) + { + // Load the dialog definition. + var definition = CKEDITOR.dialog._.dialogDefinitions[ dialogName ], + defaultDefinition = CKEDITOR.tools.clone( defaultDialogDefinition ), + buttonsOrder = editor.config.dialog_buttonsOrder || 'OS', + dir = editor.lang.dir; + + if ( ( buttonsOrder == 'OS' && CKEDITOR.env.mac ) || // The buttons in MacOS Apps are in reverse order (#4750) + ( buttonsOrder == 'rtl' && dir == 'ltr' ) || + ( buttonsOrder == 'ltr' && dir == 'rtl' ) ) + defaultDefinition.buttons.reverse(); + + + // Completes the definition with the default values. + definition = CKEDITOR.tools.extend( definition( editor ), defaultDefinition ); + + // Clone a functionally independent copy for this dialog. + definition = CKEDITOR.tools.clone( definition ); + + // Create a complex definition object, extending it with the API + // functions. + definition = new definitionObject( this, definition ); + + var doc = CKEDITOR.document; + + var themeBuilt = editor.theme.buildDialog( editor ); + + // Initialize some basic parameters. + this._ = + { + editor : editor, + element : themeBuilt.element, + name : dialogName, + contentSize : { width : 0, height : 0 }, + size : { width : 0, height : 0 }, + contents : {}, + buttons : {}, + accessKeyMap : {}, + + // Initialize the tab and page map. + tabs : {}, + tabIdList : [], + currentTabId : null, + currentTabIndex : null, + pageCount : 0, + lastTab : null, + tabBarMode : false, + + // Initialize the tab order array for input widgets. + focusList : [], + currentFocusIndex : 0, + hasFocus : false + }; + + this.parts = themeBuilt.parts; + + CKEDITOR.tools.setTimeout( function() + { + editor.fire( 'ariaWidget', this.parts.contents ); + }, + 0, this ); + + // Set the startup styles for the dialog, avoiding it enlarging the + // page size on the dialog creation. + var startStyles = { + position : CKEDITOR.env.ie6Compat ? 'absolute' : 'fixed', + top : 0, + visibility : 'hidden' + }; + + startStyles[ dir == 'rtl' ? 'right' : 'left' ] = 0; + this.parts.dialog.setStyles( startStyles ); + + + // Call the CKEDITOR.event constructor to initialize this instance. + CKEDITOR.event.call( this ); + + // Fire the "dialogDefinition" event, making it possible to customize + // the dialog definition. + this.definition = definition = CKEDITOR.fire( 'dialogDefinition', + { + name : dialogName, + definition : definition + } + , editor ).definition; + + var tabsToRemove = {}; + // Cache tabs that should be removed. + if ( !( 'removeDialogTabs' in editor._ ) && editor.config.removeDialogTabs ) + { + var removeContents = editor.config.removeDialogTabs.split( ';' ); + + for ( i = 0; i < removeContents.length; i++ ) + { + var parts = removeContents[ i ].split( ':' ); + if ( parts.length == 2 ) + { + var removeDialogName = parts[ 0 ]; + if ( !tabsToRemove[ removeDialogName ] ) + tabsToRemove[ removeDialogName ] = []; + tabsToRemove[ removeDialogName ].push( parts[ 1 ] ); + } + } + editor._.removeDialogTabs = tabsToRemove; + } + + // Remove tabs of this dialog. + if ( editor._.removeDialogTabs && ( tabsToRemove = editor._.removeDialogTabs[ dialogName ] ) ) + { + for ( i = 0; i < tabsToRemove.length; i++ ) + definition.removeContents( tabsToRemove[ i ] ); + } + + // Initialize load, show, hide, ok and cancel events. + if ( definition.onLoad ) + this.on( 'load', definition.onLoad ); + + if ( definition.onShow ) + this.on( 'show', definition.onShow ); + + if ( definition.onHide ) + this.on( 'hide', definition.onHide ); + + if ( definition.onOk ) + { + this.on( 'ok', function( evt ) + { + // Dialog confirm might probably introduce content changes (#5415). + editor.fire( 'saveSnapshot' ); + setTimeout( function () { editor.fire( 'saveSnapshot' ); }, 0 ); + if ( definition.onOk.call( this, evt ) === false ) + evt.data.hide = false; + }); + } + + if ( definition.onCancel ) + { + this.on( 'cancel', function( evt ) + { + if ( definition.onCancel.call( this, evt ) === false ) + evt.data.hide = false; + }); + } + + var me = this; + + // Iterates over all items inside all content in the dialog, calling a + // function for each of them. + var iterContents = function( func ) + { + var contents = me._.contents, + stop = false; + + for ( var i in contents ) + { + for ( var j in contents[i] ) + { + stop = func.call( this, contents[i][j] ); + if ( stop ) + return; + } + } + }; + + this.on( 'ok', function( evt ) + { + iterContents( function( item ) + { + if ( item.validate ) + { + var retval = item.validate( this ), + invalid = typeof ( retval ) == 'string' || retval === false; + + if ( invalid ) + { + evt.data.hide = false; + evt.stop(); + } + + handleFieldValidated.call( item, !invalid, typeof retval == 'string' ? retval : undefined ); + return invalid; + } + }); + }, this, null, 0 ); + + this.on( 'cancel', function( evt ) + { + iterContents( function( item ) + { + if ( item.isChanged() ) + { + if ( !confirm( editor.lang.common.confirmCancel ) ) + evt.data.hide = false; + return true; + } + }); + }, this, null, 0 ); + + this.parts.close.on( 'click', function( evt ) + { + if ( this.fire( 'cancel', { hide : true } ).hide !== false ) + this.hide(); + evt.data.preventDefault(); + }, this ); + + // Sort focus list according to tab order definitions. + function setupFocus() + { + var focusList = me._.focusList; + focusList.sort( function( a, b ) + { + // Mimics browser tab order logics; + if ( a.tabIndex != b.tabIndex ) + return b.tabIndex - a.tabIndex; + // Sort is not stable in some browsers, + // fall-back the comparator to 'focusIndex'; + else + return a.focusIndex - b.focusIndex; + }); + + var size = focusList.length; + for ( var i = 0; i < size; i++ ) + focusList[ i ].focusIndex = i; + } + + function changeFocus( forward ) + { + var focusList = me._.focusList, + offset = forward ? 1 : -1; + if ( focusList.length < 1 ) + return; + + var current = me._.currentFocusIndex; + + // Trigger the 'blur' event of any input element before anything, + // since certain UI updates may depend on it. + try + { + focusList[ current ].getInputElement().$.blur(); + } + catch( e ){} + + var startIndex = ( current + offset + focusList.length ) % focusList.length, + currentIndex = startIndex; + while ( !focusList[ currentIndex ].isFocusable() ) + { + currentIndex = ( currentIndex + offset + focusList.length ) % focusList.length; + if ( currentIndex == startIndex ) + break; + } + focusList[ currentIndex ].focus(); + + // Select whole field content. + if ( focusList[ currentIndex ].type == 'text' ) + focusList[ currentIndex ].select(); + } + + this.changeFocus = changeFocus; + + var processed; + + function focusKeydownHandler( evt ) + { + // If I'm not the top dialog, ignore. + if ( me != CKEDITOR.dialog._.currentTop ) + return; + + var keystroke = evt.data.getKeystroke(), + rtl = editor.lang.dir == 'rtl'; + + processed = 0; + if ( keystroke == 9 || keystroke == CKEDITOR.SHIFT + 9 ) + { + var shiftPressed = ( keystroke == CKEDITOR.SHIFT + 9 ); + + // Handling Tab and Shift-Tab. + if ( me._.tabBarMode ) + { + // Change tabs. + var nextId = shiftPressed ? getPreviousVisibleTab.call( me ) : getNextVisibleTab.call( me ); + me.selectPage( nextId ); + me._.tabs[ nextId ][ 0 ].focus(); + } + else + { + // Change the focus of inputs. + changeFocus( !shiftPressed ); + } + + processed = 1; + } + else if ( keystroke == CKEDITOR.ALT + 121 && !me._.tabBarMode && me.getPageCount() > 1 ) + { + // Alt-F10 puts focus into the current tab item in the tab bar. + me._.tabBarMode = true; + me._.tabs[ me._.currentTabId ][ 0 ].focus(); + processed = 1; + } + else if ( ( keystroke == 37 || keystroke == 39 ) && me._.tabBarMode ) + { + // Arrow keys - used for changing tabs. + nextId = ( keystroke == ( rtl ? 39 : 37 ) ? getPreviousVisibleTab.call( me ) : getNextVisibleTab.call( me ) ); + me.selectPage( nextId ); + me._.tabs[ nextId ][ 0 ].focus(); + processed = 1; + } + else if ( ( keystroke == 13 || keystroke == 32 ) && me._.tabBarMode ) + { + this.selectPage( this._.currentTabId ); + this._.tabBarMode = false; + this._.currentFocusIndex = -1; + changeFocus( true ); + processed = 1; + } + + if ( processed ) + { + evt.stop(); + evt.data.preventDefault(); + } + } + + function focusKeyPressHandler( evt ) + { + processed && evt.data.preventDefault(); + } + + var dialogElement = this._.element; + // Add the dialog keyboard handlers. + this.on( 'show', function() + { + dialogElement.on( 'keydown', focusKeydownHandler, this, null, 0 ); + // Some browsers instead, don't cancel key events in the keydown, but in the + // keypress. So we must do a longer trip in those cases. (#4531) + if ( CKEDITOR.env.opera || ( CKEDITOR.env.gecko && CKEDITOR.env.mac ) ) + dialogElement.on( 'keypress', focusKeyPressHandler, this ); + + } ); + this.on( 'hide', function() + { + dialogElement.removeListener( 'keydown', focusKeydownHandler ); + if ( CKEDITOR.env.opera || ( CKEDITOR.env.gecko && CKEDITOR.env.mac ) ) + dialogElement.removeListener( 'keypress', focusKeyPressHandler ); + + // Reset fields state when closing dialog. + iterContents( function( item ) { resetField.apply( item ); } ); + } ); + this.on( 'iframeAdded', function( evt ) + { + var doc = new CKEDITOR.dom.document( evt.data.iframe.$.contentWindow.document ); + doc.on( 'keydown', focusKeydownHandler, this, null, 0 ); + } ); + + // Auto-focus logic in dialog. + this.on( 'show', function() + { + // Setup tabIndex on showing the dialog instead of on loading + // to allow dynamic tab order happen in dialog definition. + setupFocus(); + + if ( editor.config.dialog_startupFocusTab + && me._.pageCount > 1 ) + { + me._.tabBarMode = true; + me._.tabs[ me._.currentTabId ][ 0 ].focus(); + } + else if ( !this._.hasFocus ) + { + this._.currentFocusIndex = -1; + + // Decide where to put the initial focus. + if ( definition.onFocus ) + { + var initialFocus = definition.onFocus.call( this ); + // Focus the field that the user specified. + initialFocus && initialFocus.focus(); + } + // Focus the first field in layout order. + else + changeFocus( true ); + + /* + * IE BUG: If the initial focus went into a non-text element (e.g. button), + * then IE would still leave the caret inside the editing area. + */ + if ( this._.editor.mode == 'wysiwyg' && CKEDITOR.env.ie ) + { + var $selection = editor.document.$.selection, + $range = $selection.createRange(); + + if ( $range ) + { + if ( $range.parentElement && $range.parentElement().ownerDocument == editor.document.$ + || $range.item && $range.item( 0 ).ownerDocument == editor.document.$ ) + { + var $myRange = document.body.createTextRange(); + $myRange.moveToElementText( this.getElement().getFirst().$ ); + $myRange.collapse( true ); + $myRange.select(); + } + } + } + } + }, this, null, 0xffffffff ); + + // IE6 BUG: Text fields and text areas are only half-rendered the first time the dialog appears in IE6 (#2661). + // This is still needed after [2708] and [2709] because text fields in hidden TR tags are still broken. + if ( CKEDITOR.env.ie6Compat ) + { + this.on( 'load', function( evt ) + { + var outer = this.getElement(), + inner = outer.getFirst(); + inner.remove(); + inner.appendTo( outer ); + }, this ); + } + + initDragAndDrop( this ); + initResizeHandles( this ); + + // Insert the title. + ( new CKEDITOR.dom.text( definition.title, CKEDITOR.document ) ).appendTo( this.parts.title ); + + // Insert the tabs and contents. + for ( var i = 0 ; i < definition.contents.length ; i++ ) + { + var page = definition.contents[i]; + page && this.addPage( page ); + } + + this.parts[ 'tabs' ].on( 'click', function( evt ) + { + var target = evt.data.getTarget(); + // If we aren't inside a tab, bail out. + if ( target.hasClass( 'cke_dialog_tab' ) ) + { + // Get the ID of the tab, without the 'cke_' prefix and the unique number suffix. + var id = target.$.id; + this.selectPage( id.substring( 4, id.lastIndexOf( '_' ) ) ); + + if ( this._.tabBarMode ) + { + this._.tabBarMode = false; + this._.currentFocusIndex = -1; + changeFocus( true ); + } + evt.data.preventDefault(); + } + }, this ); + + // Insert buttons. + var buttonsHtml = [], + buttons = CKEDITOR.dialog._.uiElementBuilders.hbox.build( this, + { + type : 'hbox', + className : 'cke_dialog_footer_buttons', + widths : [], + children : definition.buttons + }, buttonsHtml ).getChild(); + this.parts.footer.setHtml( buttonsHtml.join( '' ) ); + + for ( i = 0 ; i < buttons.length ; i++ ) + this._.buttons[ buttons[i].id ] = buttons[i]; + }; + + // Focusable interface. Use it via dialog.addFocusable. + function Focusable( dialog, element, index ) + { + this.element = element; + this.focusIndex = index; + // TODO: support tabIndex for focusables. + this.tabIndex = 0; + this.isFocusable = function() + { + return !element.getAttribute( 'disabled' ) && element.isVisible(); + }; + this.focus = function() + { + dialog._.currentFocusIndex = this.focusIndex; + this.element.focus(); + }; + // Bind events + element.on( 'keydown', function( e ) + { + if ( e.data.getKeystroke() in { 32:1, 13:1 } ) + this.fire( 'click' ); + } ); + element.on( 'focus', function() + { + this.fire( 'mouseover' ); + } ); + element.on( 'blur', function() + { + this.fire( 'mouseout' ); + } ); + } + + CKEDITOR.dialog.prototype = + { + destroy : function() + { + this.hide(); + this._.element.remove(); + }, + + /** + * Resizes the dialog. + * @param {Number} width The width of the dialog in pixels. + * @param {Number} height The height of the dialog in pixels. + * @function + * @example + * dialogObj.resize( 800, 640 ); + */ + resize : (function() + { + return function( width, height ) + { + if ( this._.contentSize && this._.contentSize.width == width && this._.contentSize.height == height ) + return; + + CKEDITOR.dialog.fire( 'resize', + { + dialog : this, + skin : this._.editor.skinName, + width : width, + height : height + }, this._.editor ); + + this.fire( 'resize', + { + skin : this._.editor.skinName, + width : width, + height : height + }, this._.editor ); + + // Update dialog position when dimension get changed in RTL. + if ( this._.editor.lang.dir == 'rtl' && this._.position ) + this._.position.x = CKEDITOR.document.getWindow().getViewPaneSize().width - + this._.contentSize.width - parseInt( this._.element.getFirst().getStyle( 'right' ), 10 ); + + this._.contentSize = { width : width, height : height }; + }; + })(), + + /** + * Gets the current size of the dialog in pixels. + * @returns {Object} An object with "width" and "height" properties. + * @example + * var width = dialogObj.getSize().width; + */ + getSize : function() + { + var element = this._.element.getFirst(); + return { width : element.$.offsetWidth || 0, height : element.$.offsetHeight || 0}; + }, + + /** + * Moves the dialog to an (x, y) coordinate relative to the window. + * @function + * @param {Number} x The target x-coordinate. + * @param {Number} y The target y-coordinate. + * @param {Boolean} save Flag indicate whether the dialog position should be remembered on next open up. + * @example + * dialogObj.move( 10, 40 ); + */ + move : (function() + { + var isFixed; + return function( x, y, save ) + { + // The dialog may be fixed positioned or absolute positioned. Ask the + // browser what is the current situation first. + var element = this._.element.getFirst(), + rtl = this._.editor.lang.dir == 'rtl'; + + if ( isFixed === undefined ) + isFixed = element.getComputedStyle( 'position' ) == 'fixed'; + + if ( isFixed && this._.position && this._.position.x == x && this._.position.y == y ) + return; + + // Save the current position. + this._.position = { x : x, y : y }; + + // If not fixed positioned, add scroll position to the coordinates. + if ( !isFixed ) + { + var scrollPosition = CKEDITOR.document.getWindow().getScrollPosition(); + x += scrollPosition.x; + y += scrollPosition.y; + } + + // Translate coordinate for RTL. + if ( rtl ) + { + var dialogSize = this.getSize(), + viewPaneSize = CKEDITOR.document.getWindow().getViewPaneSize(); + x = viewPaneSize.width - dialogSize.width - x; + } + + var styles = { 'top' : ( y > 0 ? y : 0 ) + 'px' }; + styles[ rtl ? 'right' : 'left' ] = ( x > 0 ? x : 0 ) + 'px'; + + element.setStyles( styles ); + + save && ( this._.moved = 1 ); + }; + })(), + + /** + * Gets the dialog's position in the window. + * @returns {Object} An object with "x" and "y" properties. + * @example + * var dialogX = dialogObj.getPosition().x; + */ + getPosition : function(){ return CKEDITOR.tools.extend( {}, this._.position ); }, + + /** + * Shows the dialog box. + * @example + * dialogObj.show(); + */ + show : function() + { + // Insert the dialog's element to the root document. + var element = this._.element; + var definition = this.definition; + if ( !( element.getParent() && element.getParent().equals( CKEDITOR.document.getBody() ) ) ) + element.appendTo( CKEDITOR.document.getBody() ); + else + element.setStyle( 'display', 'block' ); + + // FIREFOX BUG: Fix vanishing caret for Firefox 2 or Gecko 1.8. + if ( CKEDITOR.env.gecko && CKEDITOR.env.version < 10900 ) + { + var dialogElement = this.parts.dialog; + dialogElement.setStyle( 'position', 'absolute' ); + setTimeout( function() + { + dialogElement.setStyle( 'position', 'fixed' ); + }, 0 ); + } + + + // First, set the dialog to an appropriate size. + this.resize( this._.contentSize && this._.contentSize.width || definition.width || definition.minWidth, + this._.contentSize && this._.contentSize.height || definition.height || definition.minHeight ); + + // Reset all inputs back to their default value. + this.reset(); + + // Select the first tab by default. + this.selectPage( this.definition.contents[0].id ); + + // Set z-index. + if ( CKEDITOR.dialog._.currentZIndex === null ) + CKEDITOR.dialog._.currentZIndex = this._.editor.config.baseFloatZIndex; + this._.element.getFirst().setStyle( 'z-index', CKEDITOR.dialog._.currentZIndex += 10 ); + + // Maintain the dialog ordering and dialog cover. + // Also register key handlers if first dialog. + if ( CKEDITOR.dialog._.currentTop === null ) + { + CKEDITOR.dialog._.currentTop = this; + this._.parentDialog = null; + showCover( this._.editor ); + + element.on( 'keydown', accessKeyDownHandler ); + element.on( CKEDITOR.env.opera ? 'keypress' : 'keyup', accessKeyUpHandler ); + + // Prevent some keys from bubbling up. (#4269) + for ( var event in { keyup :1, keydown :1, keypress :1 } ) + element.on( event, preventKeyBubbling ); + } + else + { + this._.parentDialog = CKEDITOR.dialog._.currentTop; + var parentElement = this._.parentDialog.getElement().getFirst(); + parentElement.$.style.zIndex -= Math.floor( this._.editor.config.baseFloatZIndex / 2 ); + CKEDITOR.dialog._.currentTop = this; + } + + // Register the Esc hotkeys. + registerAccessKey( this, this, '\x1b', null, function() + { + this.getButton( 'cancel' ) && this.getButton( 'cancel' ).click(); + } ); + + // Reset the hasFocus state. + this._.hasFocus = false; + + CKEDITOR.tools.setTimeout( function() + { + this.layout(); + this.parts.dialog.setStyle( 'visibility', '' ); + + // Execute onLoad for the first show. + this.fireOnce( 'load', {} ); + CKEDITOR.ui.fire( 'ready', this ); + + this.fire( 'show', {} ); + this._.editor.fire( 'dialogShow', this ); + + // Save the initial values of the dialog. + this.foreach( function( contentObj ) { contentObj.setInitValue && contentObj.setInitValue(); } ); + + }, + 100, this ); + }, + + /** + * Rearrange the dialog to its previous position or the middle of the window. + * @since 3.5 + */ + layout : function() + { + var viewSize = CKEDITOR.document.getWindow().getViewPaneSize(), + dialogSize = this.getSize(); + + this.move( this._.moved ? this._.position.x : ( viewSize.width - dialogSize.width ) / 2, + this._.moved ? this._.position.y : ( viewSize.height - dialogSize.height ) / 2 ); + }, + + /** + * Executes a function for each UI element. + * @param {Function} fn Function to execute for each UI element. + * @returns {CKEDITOR.dialog} The current dialog object. + */ + foreach : function( fn ) + { + for ( var i in this._.contents ) + { + for ( var j in this._.contents[i] ) + fn.call( this, this._.contents[i][j] ); + } + return this; + }, + + /** + * Resets all input values in the dialog. + * @example + * dialogObj.reset(); + * @returns {CKEDITOR.dialog} The current dialog object. + */ + reset : (function() + { + var fn = function( widget ){ if ( widget.reset ) widget.reset( 1 ); }; + return function(){ this.foreach( fn ); return this; }; + })(), + + + /** + * Calls the {@link CKEDITOR.dialog.definition.uiElement#setup} method of each of the UI elements, with the arguments passed through it. + * It is usually being called when the dialog is opened, to put the initial value inside the field. + * @example + * dialogObj.setupContent(); + * @example + * var timestamp = ( new Date() ).valueOf(); + * dialogObj.setupContent( timestamp ); + */ + setupContent : function() + { + var args = arguments; + this.foreach( function( widget ) + { + if ( widget.setup ) + widget.setup.apply( widget, args ); + }); + }, + + /** + * Calls the {@link CKEDITOR.dialog.definition.uiElement#commit} method of each of the UI elements, with the arguments passed through it. + * It is usually being called when the user confirms the dialog, to process the values. + * @example + * dialogObj.commitContent(); + * @example + * var timestamp = ( new Date() ).valueOf(); + * dialogObj.commitContent( timestamp ); + */ + commitContent : function() + { + var args = arguments; + this.foreach( function( widget ) + { + // Make sure IE triggers "change" event on last focused input before closing the dialog. (#7915) + if ( CKEDITOR.env.ie && this._.currentFocusIndex == widget.focusIndex ) + widget.getInputElement().$.blur(); + + if ( widget.commit ) + widget.commit.apply( widget, args ); + }); + }, + + /** + * Hides the dialog box. + * @example + * dialogObj.hide(); + */ + hide : function() + { + if ( !this.parts.dialog.isVisible() ) + return; + + this.fire( 'hide', {} ); + this._.editor.fire( 'dialogHide', this ); + var element = this._.element; + element.setStyle( 'display', 'none' ); + this.parts.dialog.setStyle( 'visibility', 'hidden' ); + // Unregister all access keys associated with this dialog. + unregisterAccessKey( this ); + + // Close any child(top) dialogs first. + while( CKEDITOR.dialog._.currentTop != this ) + CKEDITOR.dialog._.currentTop.hide(); + + // Maintain dialog ordering and remove cover if needed. + if ( !this._.parentDialog ) + hideCover(); + else + { + var parentElement = this._.parentDialog.getElement().getFirst(); + parentElement.setStyle( 'z-index', parseInt( parentElement.$.style.zIndex, 10 ) + Math.floor( this._.editor.config.baseFloatZIndex / 2 ) ); + } + CKEDITOR.dialog._.currentTop = this._.parentDialog; + + // Deduct or clear the z-index. + if ( !this._.parentDialog ) + { + CKEDITOR.dialog._.currentZIndex = null; + + // Remove access key handlers. + element.removeListener( 'keydown', accessKeyDownHandler ); + element.removeListener( CKEDITOR.env.opera ? 'keypress' : 'keyup', accessKeyUpHandler ); + + // Remove bubbling-prevention handler. (#4269) + for ( var event in { keyup :1, keydown :1, keypress :1 } ) + element.removeListener( event, preventKeyBubbling ); + + var editor = this._.editor; + editor.focus(); + + if ( editor.mode == 'wysiwyg' && CKEDITOR.env.ie ) + { + var selection = editor.getSelection(); + selection && selection.unlock( true ); + } + } + else + CKEDITOR.dialog._.currentZIndex -= 10; + + delete this._.parentDialog; + // Reset the initial values of the dialog. + this.foreach( function( contentObj ) { contentObj.resetInitValue && contentObj.resetInitValue(); } ); + }, + + /** + * Adds a tabbed page into the dialog. + * @param {Object} contents Content definition. + * @example + */ + addPage : function( contents ) + { + var pageHtml = [], + titleHtml = contents.label ? ' title="' + CKEDITOR.tools.htmlEncode( contents.label ) + '"' : '', + elements = contents.elements, + vbox = CKEDITOR.dialog._.uiElementBuilders.vbox.build( this, + { + type : 'vbox', + className : 'cke_dialog_page_contents', + children : contents.elements, + expand : !!contents.expand, + padding : contents.padding, + style : contents.style || 'width: 100%;height:100%' + }, pageHtml ); + + // Create the HTML for the tab and the content block. + var page = CKEDITOR.dom.element.createFromHtml( pageHtml.join( '' ) ); + page.setAttribute( 'role', 'tabpanel' ); + + var env = CKEDITOR.env; + var tabId = 'cke_' + contents.id + '_' + CKEDITOR.tools.getNextNumber(), + tab = CKEDITOR.dom.element.createFromHtml( [ + ' 0 ? ' cke_last' : 'cke_first' ), + titleHtml, + ( !!contents.hidden ? ' style="display:none"' : '' ), + ' id="', tabId, '"', + env.gecko && env.version >= 10900 && !env.hc ? '' : ' href="javascript:void(0)"', + ' tabIndex="-1"', + ' hidefocus="true"', + ' role="tab">', + contents.label, + '' + ].join( '' ) ); + + page.setAttribute( 'aria-labelledby', tabId ); + + // Take records for the tabs and elements created. + this._.tabs[ contents.id ] = [ tab, page ]; + this._.tabIdList.push( contents.id ); + !contents.hidden && this._.pageCount++; + this._.lastTab = tab; + this.updateStyle(); + + var contentMap = this._.contents[ contents.id ] = {}, + cursor, + children = vbox.getChild(); + + while ( ( cursor = children.shift() ) ) + { + contentMap[ cursor.id ] = cursor; + if ( typeof( cursor.getChild ) == 'function' ) + children.push.apply( children, cursor.getChild() ); + } + + // Attach the DOM nodes. + + page.setAttribute( 'name', contents.id ); + page.appendTo( this.parts.contents ); + + tab.unselectable(); + this.parts.tabs.append( tab ); + + // Add access key handlers if access key is defined. + if ( contents.accessKey ) + { + registerAccessKey( this, this, 'CTRL+' + contents.accessKey, + tabAccessKeyDown, tabAccessKeyUp ); + this._.accessKeyMap[ 'CTRL+' + contents.accessKey ] = contents.id; + } + }, + + /** + * Activates a tab page in the dialog by its id. + * @param {String} id The id of the dialog tab to be activated. + * @example + * dialogObj.selectPage( 'tab_1' ); + */ + selectPage : function( id ) + { + if ( this._.currentTabId == id ) + return; + + // Returning true means that the event has been canceled + if ( this.fire( 'selectPage', { page : id, currentPage : this._.currentTabId } ) === true ) + return; + + // Hide the non-selected tabs and pages. + for ( var i in this._.tabs ) + { + var tab = this._.tabs[i][0], + page = this._.tabs[i][1]; + if ( i != id ) + { + tab.removeClass( 'cke_dialog_tab_selected' ); + page.hide(); + } + page.setAttribute( 'aria-hidden', i != id ); + } + + var selected = this._.tabs[ id ]; + selected[ 0 ].addClass( 'cke_dialog_tab_selected' ); + + // [IE] an invisible input[type='text'] will enlarge it's width + // if it's value is long when it shows, so we clear it's value + // before it shows and then recover it (#5649) + if ( CKEDITOR.env.ie6Compat || CKEDITOR.env.ie7Compat ) + { + clearOrRecoverTextInputValue( selected[ 1 ] ); + selected[ 1 ].show(); + setTimeout( function() + { + clearOrRecoverTextInputValue( selected[ 1 ], 1 ); + }, 0 ); + } + else + selected[ 1 ].show(); + + this._.currentTabId = id; + this._.currentTabIndex = CKEDITOR.tools.indexOf( this._.tabIdList, id ); + }, + + // Dialog state-specific style updates. + updateStyle : function() + { + // If only a single page shown, a different style is used in the central pane. + this.parts.dialog[ ( this._.pageCount === 1 ? 'add' : 'remove' ) + 'Class' ]( 'cke_single_page' ); + }, + + /** + * Hides a page's tab away from the dialog. + * @param {String} id The page's Id. + * @example + * dialog.hidePage( 'tab_3' ); + */ + hidePage : function( id ) + { + var tab = this._.tabs[id] && this._.tabs[id][0]; + if ( !tab || this._.pageCount == 1 || !tab.isVisible() ) + return; + // Switch to other tab first when we're hiding the active tab. + else if ( id == this._.currentTabId ) + this.selectPage( getPreviousVisibleTab.call( this ) ); + + tab.hide(); + this._.pageCount--; + this.updateStyle(); + }, + + /** + * Unhides a page's tab. + * @param {String} id The page's Id. + * @example + * dialog.showPage( 'tab_2' ); + */ + showPage : function( id ) + { + var tab = this._.tabs[id] && this._.tabs[id][0]; + if ( !tab ) + return; + tab.show(); + this._.pageCount++; + this.updateStyle(); + }, + + /** + * Gets the root DOM element of the dialog. + * @returns {CKEDITOR.dom.element} The <span> element containing this dialog. + * @example + * var dialogElement = dialogObj.getElement().getFirst(); + * dialogElement.setStyle( 'padding', '5px' ); + */ + getElement : function() + { + return this._.element; + }, + + /** + * Gets the name of the dialog. + * @returns {String} The name of this dialog. + * @example + * var dialogName = dialogObj.getName(); + */ + getName : function() + { + return this._.name; + }, + + /** + * Gets a dialog UI element object from a dialog page. + * @param {String} pageId id of dialog page. + * @param {String} elementId id of UI element. + * @example + * dialogObj.getContentElement( 'tabId', 'elementId' ).setValue( 'Example' ); + * @returns {CKEDITOR.ui.dialog.uiElement} The dialog UI element. + */ + getContentElement : function( pageId, elementId ) + { + var page = this._.contents[ pageId ]; + return page && page[ elementId ]; + }, + + /** + * Gets the value of a dialog UI element. + * @param {String} pageId id of dialog page. + * @param {String} elementId id of UI element. + * @example + * alert( dialogObj.getValueOf( 'tabId', 'elementId' ) ); + * @returns {Object} The value of the UI element. + */ + getValueOf : function( pageId, elementId ) + { + return this.getContentElement( pageId, elementId ).getValue(); + }, + + /** + * Sets the value of a dialog UI element. + * @param {String} pageId id of the dialog page. + * @param {String} elementId id of the UI element. + * @param {Object} value The new value of the UI element. + * @example + * dialogObj.setValueOf( 'tabId', 'elementId', 'Example' ); + */ + setValueOf : function( pageId, elementId, value ) + { + return this.getContentElement( pageId, elementId ).setValue( value ); + }, + + /** + * Gets the UI element of a button in the dialog's button row. + * @param {String} id The id of the button. + * @example + * @returns {CKEDITOR.ui.dialog.button} The button object. + */ + getButton : function( id ) + { + return this._.buttons[ id ]; + }, + + /** + * Simulates a click to a dialog button in the dialog's button row. + * @param {String} id The id of the button. + * @example + * @returns The return value of the dialog's "click" event. + */ + click : function( id ) + { + return this._.buttons[ id ].click(); + }, + + /** + * Disables a dialog button. + * @param {String} id The id of the button. + * @example + */ + disableButton : function( id ) + { + return this._.buttons[ id ].disable(); + }, + + /** + * Enables a dialog button. + * @param {String} id The id of the button. + * @example + */ + enableButton : function( id ) + { + return this._.buttons[ id ].enable(); + }, + + /** + * Gets the number of pages in the dialog. + * @returns {Number} Page count. + */ + getPageCount : function() + { + return this._.pageCount; + }, + + /** + * Gets the editor instance which opened this dialog. + * @returns {CKEDITOR.editor} Parent editor instances. + */ + getParentEditor : function() + { + return this._.editor; + }, + + /** + * Gets the element that was selected when opening the dialog, if any. + * @returns {CKEDITOR.dom.element} The element that was selected, or null. + */ + getSelectedElement : function() + { + return this.getParentEditor().getSelection().getSelectedElement(); + }, + + /** + * Adds element to dialog's focusable list. + * + * @param {CKEDITOR.dom.element} element + * @param {Number} [index] + */ + addFocusable: function( element, index ) { + if ( typeof index == 'undefined' ) + { + index = this._.focusList.length; + this._.focusList.push( new Focusable( this, element, index ) ); + } + else + { + this._.focusList.splice( index, 0, new Focusable( this, element, index ) ); + for ( var i = index + 1 ; i < this._.focusList.length ; i++ ) + this._.focusList[ i ].focusIndex++; + } + } + }; + + CKEDITOR.tools.extend( CKEDITOR.dialog, + /** + * @lends CKEDITOR.dialog + */ + { + /** + * Registers a dialog. + * @param {String} name The dialog's name. + * @param {Function|String} dialogDefinition + * A function returning the dialog's definition, or the URL to the .js file holding the function. + * The function should accept an argument "editor" which is the current editor instance, and + * return an object conforming to {@link CKEDITOR.dialog.definition}. + * @see CKEDITOR.dialog.definition + * @example + * // Full sample plugin, which does not only register a dialog window but also adds an item to the context menu. + * // To open the dialog window, choose "Open dialog" in the context menu. + * CKEDITOR.plugins.add( 'myplugin', + * { + * init: function( editor ) + * { + * editor.addCommand( 'mydialog',new CKEDITOR.dialogCommand( 'mydialog' ) ); + * + * if ( editor.contextMenu ) + * { + * editor.addMenuGroup( 'mygroup', 10 ); + * editor.addMenuItem( 'My Dialog', + * { + * label : 'Open dialog', + * command : 'mydialog', + * group : 'mygroup' + * }); + * editor.contextMenu.addListener( function( element ) + * { + * return { 'My Dialog' : CKEDITOR.TRISTATE_OFF }; + * }); + * } + * + * CKEDITOR.dialog.add( 'mydialog', function( api ) + * { + * // CKEDITOR.dialog.definition + * var dialogDefinition = + * { + * title : 'Sample dialog', + * minWidth : 390, + * minHeight : 130, + * contents : [ + * { + * id : 'tab1', + * label : 'Label', + * title : 'Title', + * expand : true, + * padding : 0, + * elements : + * [ + * { + * type : 'html', + * html : '<p>This is some sample HTML content.</p>' + * }, + * { + * type : 'textarea', + * id : 'textareaId', + * rows : 4, + * cols : 40 + * } + * ] + * } + * ], + * buttons : [ CKEDITOR.dialog.okButton, CKEDITOR.dialog.cancelButton ], + * onOk : function() { + * // "this" is now a CKEDITOR.dialog object. + * // Accessing dialog elements: + * var textareaObj = this.getContentElement( 'tab1', 'textareaId' ); + * alert( "You have entered: " + textareaObj.getValue() ); + * } + * }; + * + * return dialogDefinition; + * } ); + * } + * } ); + * + * CKEDITOR.replace( 'editor1', { extraPlugins : 'myplugin' } ); + */ + add : function( name, dialogDefinition ) + { + // Avoid path registration from multiple instances override definition. + if ( !this._.dialogDefinitions[name] + || typeof dialogDefinition == 'function' ) + this._.dialogDefinitions[name] = dialogDefinition; + }, + + exists : function( name ) + { + return !!this._.dialogDefinitions[ name ]; + }, + + getCurrent : function() + { + return CKEDITOR.dialog._.currentTop; + }, + + /** + * The default OK button for dialogs. Fires the "ok" event and closes the dialog if the event succeeds. + * @static + * @field + * @example + * @type Function + */ + okButton : (function() + { + var retval = function( editor, override ) + { + override = override || {}; + return CKEDITOR.tools.extend( { + id : 'ok', + type : 'button', + label : editor.lang.common.ok, + 'class' : 'cke_dialog_ui_button_ok', + onClick : function( evt ) + { + var dialog = evt.data.dialog; + if ( dialog.fire( 'ok', { hide : true } ).hide !== false ) + dialog.hide(); + } + }, override, true ); + }; + retval.type = 'button'; + retval.override = function( override ) + { + return CKEDITOR.tools.extend( function( editor ){ return retval( editor, override ); }, + { type : 'button' }, true ); + }; + return retval; + })(), + + /** + * The default cancel button for dialogs. Fires the "cancel" event and closes the dialog if no UI element value changed. + * @static + * @field + * @example + * @type Function + */ + cancelButton : (function() + { + var retval = function( editor, override ) + { + override = override || {}; + return CKEDITOR.tools.extend( { + id : 'cancel', + type : 'button', + label : editor.lang.common.cancel, + 'class' : 'cke_dialog_ui_button_cancel', + onClick : function( evt ) + { + var dialog = evt.data.dialog; + if ( dialog.fire( 'cancel', { hide : true } ).hide !== false ) + dialog.hide(); + } + }, override, true ); + }; + retval.type = 'button'; + retval.override = function( override ) + { + return CKEDITOR.tools.extend( function( editor ){ return retval( editor, override ); }, + { type : 'button' }, true ); + }; + return retval; + })(), + + /** + * Registers a dialog UI element. + * @param {String} typeName The name of the UI element. + * @param {Function} builder The function to build the UI element. + * @example + */ + addUIElement : function( typeName, builder ) + { + this._.uiElementBuilders[ typeName ] = builder; + } + }); + + CKEDITOR.dialog._ = + { + uiElementBuilders : {}, + + dialogDefinitions : {}, + + currentTop : null, + + currentZIndex : null + }; + + // "Inherit" (copy actually) from CKEDITOR.event. + CKEDITOR.event.implementOn( CKEDITOR.dialog ); + CKEDITOR.event.implementOn( CKEDITOR.dialog.prototype, true ); + + var defaultDialogDefinition = + { + resizable : CKEDITOR.DIALOG_RESIZE_BOTH, + minWidth : 600, + minHeight : 400, + buttons : [ CKEDITOR.dialog.okButton, CKEDITOR.dialog.cancelButton ] + }; + + // Tool function used to return an item from an array based on its id + // property. + var getById = function( array, id, recurse ) + { + for ( var i = 0, item ; ( item = array[ i ] ) ; i++ ) + { + if ( item.id == id ) + return item; + if ( recurse && item[ recurse ] ) + { + var retval = getById( item[ recurse ], id, recurse ) ; + if ( retval ) + return retval; + } + } + return null; + }; + + // Tool function used to add an item into an array. + var addById = function( array, newItem, nextSiblingId, recurse, nullIfNotFound ) + { + if ( nextSiblingId ) + { + for ( var i = 0, item ; ( item = array[ i ] ) ; i++ ) + { + if ( item.id == nextSiblingId ) + { + array.splice( i, 0, newItem ); + return newItem; + } + + if ( recurse && item[ recurse ] ) + { + var retval = addById( item[ recurse ], newItem, nextSiblingId, recurse, true ); + if ( retval ) + return retval; + } + } + + if ( nullIfNotFound ) + return null; + } + + array.push( newItem ); + return newItem; + }; + + // Tool function used to remove an item from an array based on its id. + var removeById = function( array, id, recurse ) + { + for ( var i = 0, item ; ( item = array[ i ] ) ; i++ ) + { + if ( item.id == id ) + return array.splice( i, 1 ); + if ( recurse && item[ recurse ] ) + { + var retval = removeById( item[ recurse ], id, recurse ); + if ( retval ) + return retval; + } + } + return null; + }; + + /** + * This class is not really part of the API. It is the "definition" property value + * passed to "dialogDefinition" event handlers. + * @constructor + * @name CKEDITOR.dialog.definitionObject + * @extends CKEDITOR.dialog.definition + * @example + * CKEDITOR.on( 'dialogDefinition', function( evt ) + * { + * var definition = evt.data.definition; + * var content = definition.getContents( 'page1' ); + * ... + * } ); + */ + var definitionObject = function( dialog, dialogDefinition ) + { + // TODO : Check if needed. + this.dialog = dialog; + + // Transform the contents entries in contentObjects. + var contents = dialogDefinition.contents; + for ( var i = 0, content ; ( content = contents[i] ) ; i++ ) + contents[ i ] = content && new contentObject( dialog, content ); + + CKEDITOR.tools.extend( this, dialogDefinition ); + }; + + definitionObject.prototype = + /** @lends CKEDITOR.dialog.definitionObject.prototype */ + { + /** + * Gets a content definition. + * @param {String} id The id of the content definition. + * @returns {CKEDITOR.dialog.definition.content} The content definition + * matching id. + */ + getContents : function( id ) + { + return getById( this.contents, id ); + }, + + /** + * Gets a button definition. + * @param {String} id The id of the button definition. + * @returns {CKEDITOR.dialog.definition.button} The button definition + * matching id. + */ + getButton : function( id ) + { + return getById( this.buttons, id ); + }, + + /** + * Adds a content definition object under this dialog definition. + * @param {CKEDITOR.dialog.definition.content} contentDefinition The + * content definition. + * @param {String} [nextSiblingId] The id of an existing content + * definition which the new content definition will be inserted + * before. Omit if the new content definition is to be inserted as + * the last item. + * @returns {CKEDITOR.dialog.definition.content} The inserted content + * definition. + */ + addContents : function( contentDefinition, nextSiblingId ) + { + return addById( this.contents, contentDefinition, nextSiblingId ); + }, + + /** + * Adds a button definition object under this dialog definition. + * @param {CKEDITOR.dialog.definition.button} buttonDefinition The + * button definition. + * @param {String} [nextSiblingId] The id of an existing button + * definition which the new button definition will be inserted + * before. Omit if the new button definition is to be inserted as + * the last item. + * @returns {CKEDITOR.dialog.definition.button} The inserted button + * definition. + */ + addButton : function( buttonDefinition, nextSiblingId ) + { + return addById( this.buttons, buttonDefinition, nextSiblingId ); + }, + + /** + * Removes a content definition from this dialog definition. + * @param {String} id The id of the content definition to be removed. + * @returns {CKEDITOR.dialog.definition.content} The removed content + * definition. + */ + removeContents : function( id ) + { + removeById( this.contents, id ); + }, + + /** + * Removes a button definition from the dialog definition. + * @param {String} id The id of the button definition to be removed. + * @returns {CKEDITOR.dialog.definition.button} The removed button + * definition. + */ + removeButton : function( id ) + { + removeById( this.buttons, id ); + } + }; + + /** + * This class is not really part of the API. It is the template of the + * objects representing content pages inside the + * CKEDITOR.dialog.definitionObject. + * @constructor + * @name CKEDITOR.dialog.definition.contentObject + * @example + * CKEDITOR.on( 'dialogDefinition', function( evt ) + * { + * var definition = evt.data.definition; + * var content = definition.getContents( 'page1' ); + * content.remove( 'textInput1' ); + * ... + * } ); + */ + function contentObject( dialog, contentDefinition ) + { + this._ = + { + dialog : dialog + }; + + CKEDITOR.tools.extend( this, contentDefinition ); + } + + contentObject.prototype = + /** @lends CKEDITOR.dialog.definition.contentObject.prototype */ + { + /** + * Gets a UI element definition under the content definition. + * @param {String} id The id of the UI element definition. + * @returns {CKEDITOR.dialog.definition.uiElement} + */ + get : function( id ) + { + return getById( this.elements, id, 'children' ); + }, + + /** + * Adds a UI element definition to the content definition. + * @param {CKEDITOR.dialog.definition.uiElement} elementDefinition The + * UI elemnet definition to be added. + * @param {String} nextSiblingId The id of an existing UI element + * definition which the new UI element definition will be inserted + * before. Omit if the new button definition is to be inserted as + * the last item. + * @returns {CKEDITOR.dialog.definition.uiElement} The element + * definition inserted. + */ + add : function( elementDefinition, nextSiblingId ) + { + return addById( this.elements, elementDefinition, nextSiblingId, 'children' ); + }, + + /** + * Removes a UI element definition from the content definition. + * @param {String} id The id of the UI element definition to be + * removed. + * @returns {CKEDITOR.dialog.definition.uiElement} The element + * definition removed. + * @example + */ + remove : function( id ) + { + removeById( this.elements, id, 'children' ); + } + }; + + function initDragAndDrop( dialog ) + { + var lastCoords = null, + abstractDialogCoords = null, + element = dialog.getElement().getFirst(), + editor = dialog.getParentEditor(), + magnetDistance = editor.config.dialog_magnetDistance, + margins = editor.skin.margins || [ 0, 0, 0, 0 ]; + + if ( typeof magnetDistance == 'undefined' ) + magnetDistance = 20; + + function mouseMoveHandler( evt ) + { + var dialogSize = dialog.getSize(), + viewPaneSize = CKEDITOR.document.getWindow().getViewPaneSize(), + x = evt.data.$.screenX, + y = evt.data.$.screenY, + dx = x - lastCoords.x, + dy = y - lastCoords.y, + realX, realY; + + lastCoords = { x : x, y : y }; + abstractDialogCoords.x += dx; + abstractDialogCoords.y += dy; + + if ( abstractDialogCoords.x + margins[3] < magnetDistance ) + realX = - margins[3]; + else if ( abstractDialogCoords.x - margins[1] > viewPaneSize.width - dialogSize.width - magnetDistance ) + realX = viewPaneSize.width - dialogSize.width + ( editor.lang.dir == 'rtl' ? 0 : margins[1] ); + else + realX = abstractDialogCoords.x; + + if ( abstractDialogCoords.y + margins[0] < magnetDistance ) + realY = - margins[0]; + else if ( abstractDialogCoords.y - margins[2] > viewPaneSize.height - dialogSize.height - magnetDistance ) + realY = viewPaneSize.height - dialogSize.height + margins[2]; + else + realY = abstractDialogCoords.y; + + dialog.move( realX, realY, 1 ); + + evt.data.preventDefault(); + } + + function mouseUpHandler( evt ) + { + CKEDITOR.document.removeListener( 'mousemove', mouseMoveHandler ); + CKEDITOR.document.removeListener( 'mouseup', mouseUpHandler ); + + if ( CKEDITOR.env.ie6Compat ) + { + var coverDoc = currentCover.getChild( 0 ).getFrameDocument(); + coverDoc.removeListener( 'mousemove', mouseMoveHandler ); + coverDoc.removeListener( 'mouseup', mouseUpHandler ); + } + } + + dialog.parts.title.on( 'mousedown', function( evt ) + { + lastCoords = { x : evt.data.$.screenX, y : evt.data.$.screenY }; + + CKEDITOR.document.on( 'mousemove', mouseMoveHandler ); + CKEDITOR.document.on( 'mouseup', mouseUpHandler ); + abstractDialogCoords = dialog.getPosition(); + + if ( CKEDITOR.env.ie6Compat ) + { + var coverDoc = currentCover.getChild( 0 ).getFrameDocument(); + coverDoc.on( 'mousemove', mouseMoveHandler ); + coverDoc.on( 'mouseup', mouseUpHandler ); + } + + evt.data.preventDefault(); + }, dialog ); + } + + function initResizeHandles( dialog ) + { + var def = dialog.definition, + resizable = def.resizable; + + if ( resizable == CKEDITOR.DIALOG_RESIZE_NONE ) + return; + + var editor = dialog.getParentEditor(); + var wrapperWidth, wrapperHeight, + viewSize, origin, startSize, + dialogCover; + + var mouseDownFn = CKEDITOR.tools.addFunction( function( $event ) + { + startSize = dialog.getSize(); + + var content = dialog.parts.contents, + iframeDialog = content.$.getElementsByTagName( 'iframe' ).length; + + // Shim to help capturing "mousemove" over iframe. + if ( iframeDialog ) + { + dialogCover = CKEDITOR.dom.element.createFromHtml( '
      ' ); + content.append( dialogCover ); + } + + // Calculate the offset between content and chrome size. + wrapperHeight = startSize.height - dialog.parts.contents.getSize( 'height', ! ( CKEDITOR.env.gecko || CKEDITOR.env.opera || CKEDITOR.env.ie && CKEDITOR.env.quirks ) ); + wrapperWidth = startSize.width - dialog.parts.contents.getSize( 'width', 1 ); + + origin = { x : $event.screenX, y : $event.screenY }; + + viewSize = CKEDITOR.document.getWindow().getViewPaneSize(); + + CKEDITOR.document.on( 'mousemove', mouseMoveHandler ); + CKEDITOR.document.on( 'mouseup', mouseUpHandler ); + + if ( CKEDITOR.env.ie6Compat ) + { + var coverDoc = currentCover.getChild( 0 ).getFrameDocument(); + coverDoc.on( 'mousemove', mouseMoveHandler ); + coverDoc.on( 'mouseup', mouseUpHandler ); + } + + $event.preventDefault && $event.preventDefault(); + }); + + // Prepend the grip to the dialog. + dialog.on( 'load', function() + { + var direction = ''; + if ( resizable == CKEDITOR.DIALOG_RESIZE_WIDTH ) + direction = ' cke_resizer_horizontal'; + else if ( resizable == CKEDITOR.DIALOG_RESIZE_HEIGHT ) + direction = ' cke_resizer_vertical'; + var resizer = CKEDITOR.dom.element.createFromHtml( '
' ); + dialog.parts.footer.append( resizer, 1 ); + }); + editor.on( 'destroy', function() { CKEDITOR.tools.removeFunction( mouseDownFn ); } ); + + function mouseMoveHandler( evt ) + { + var rtl = editor.lang.dir == 'rtl', + dx = ( evt.data.$.screenX - origin.x ) * ( rtl ? -1 : 1 ), + dy = evt.data.$.screenY - origin.y, + width = startSize.width, + height = startSize.height, + internalWidth = width + dx * ( dialog._.moved ? 1 : 2 ), + internalHeight = height + dy * ( dialog._.moved ? 1 : 2 ), + element = dialog._.element.getFirst(), + right = rtl && element.getComputedStyle( 'right' ), + position = dialog.getPosition(); + + if ( position.y + internalHeight > viewSize.height ) + internalHeight = viewSize.height - position.y; + + if ( ( rtl ? right : position.x ) + internalWidth > viewSize.width ) + internalWidth = viewSize.width - ( rtl ? right : position.x ); + + // Make sure the dialog will not be resized to the wrong side when it's in the leftmost position for RTL. + if ( ( resizable == CKEDITOR.DIALOG_RESIZE_WIDTH || resizable == CKEDITOR.DIALOG_RESIZE_BOTH ) ) + width = Math.max( def.minWidth || 0, internalWidth - wrapperWidth ); + + if ( resizable == CKEDITOR.DIALOG_RESIZE_HEIGHT || resizable == CKEDITOR.DIALOG_RESIZE_BOTH ) + height = Math.max( def.minHeight || 0, internalHeight - wrapperHeight ); + + dialog.resize( width, height ); + + if ( !dialog._.moved ) + dialog.layout(); + + evt.data.preventDefault(); + } + + function mouseUpHandler() + { + CKEDITOR.document.removeListener( 'mouseup', mouseUpHandler ); + CKEDITOR.document.removeListener( 'mousemove', mouseMoveHandler ); + + if ( dialogCover ) + { + dialogCover.remove(); + dialogCover = null; + } + + if ( CKEDITOR.env.ie6Compat ) + { + var coverDoc = currentCover.getChild( 0 ).getFrameDocument(); + coverDoc.removeListener( 'mouseup', mouseUpHandler ); + coverDoc.removeListener( 'mousemove', mouseMoveHandler ); + } + } + } + + var resizeCover; + // Caching resuable covers and allowing only one cover + // on screen. + var covers = {}, + currentCover; + + function cancelEvent( ev ) + { + ev.data.preventDefault(1); + } + + function showCover( editor ) + { + var win = CKEDITOR.document.getWindow(); + var config = editor.config, + backgroundColorStyle = config.dialog_backgroundCoverColor || 'white', + backgroundCoverOpacity = config.dialog_backgroundCoverOpacity, + baseFloatZIndex = config.baseFloatZIndex, + coverKey = CKEDITOR.tools.genKey( + backgroundColorStyle, + backgroundCoverOpacity, + baseFloatZIndex ), + coverElement = covers[ coverKey ]; + + if ( !coverElement ) + { + var html = [ + '
' + ]; + + if ( CKEDITOR.env.ie6Compat ) + { + // Support for custom document.domain in IE. + var isCustomDomain = CKEDITOR.env.isCustomDomain(), + iframeHtml = ''; + + html.push( + '' + + '' ); + } + + html.push( '
' ); + + coverElement = CKEDITOR.dom.element.createFromHtml( html.join( '' ) ); + coverElement.setOpacity( backgroundCoverOpacity != undefined ? backgroundCoverOpacity : 0.5 ); + + coverElement.on( 'keydown', cancelEvent ); + coverElement.on( 'keypress', cancelEvent ); + coverElement.on( 'keyup', cancelEvent ); + + coverElement.appendTo( CKEDITOR.document.getBody() ); + covers[ coverKey ] = coverElement; + } + else + coverElement. show(); + + currentCover = coverElement; + var resizeFunc = function() + { + var size = win.getViewPaneSize(); + coverElement.setStyles( + { + width : size.width + 'px', + height : size.height + 'px' + } ); + }; + + var scrollFunc = function() + { + var pos = win.getScrollPosition(), + cursor = CKEDITOR.dialog._.currentTop; + coverElement.setStyles( + { + left : pos.x + 'px', + top : pos.y + 'px' + }); + + if ( cursor ) + { + do + { + var dialogPos = cursor.getPosition(); + cursor.move( dialogPos.x, dialogPos.y ); + } while ( ( cursor = cursor._.parentDialog ) ); + } + }; + + resizeCover = resizeFunc; + win.on( 'resize', resizeFunc ); + resizeFunc(); + // Using Safari/Mac, focus must be kept where it is (#7027) + if ( !( CKEDITOR.env.mac && CKEDITOR.env.webkit ) ) + coverElement.focus(); + + if ( CKEDITOR.env.ie6Compat ) + { + // IE BUG: win.$.onscroll assignment doesn't work.. it must be window.onscroll. + // So we need to invent a really funny way to make it work. + var myScrollHandler = function() + { + scrollFunc(); + arguments.callee.prevScrollHandler.apply( this, arguments ); + }; + win.$.setTimeout( function() + { + myScrollHandler.prevScrollHandler = window.onscroll || function(){}; + window.onscroll = myScrollHandler; + }, 0 ); + scrollFunc(); + } + } + + function hideCover() + { + if ( !currentCover ) + return; + + var win = CKEDITOR.document.getWindow(); + currentCover.hide(); + win.removeListener( 'resize', resizeCover ); + + if ( CKEDITOR.env.ie6Compat ) + { + win.$.setTimeout( function() + { + var prevScrollHandler = window.onscroll && window.onscroll.prevScrollHandler; + window.onscroll = prevScrollHandler || null; + }, 0 ); + } + resizeCover = null; + } + + function removeCovers() + { + for ( var coverId in covers ) + covers[ coverId ].remove(); + covers = {}; + } + + var accessKeyProcessors = {}; + + var accessKeyDownHandler = function( evt ) + { + var ctrl = evt.data.$.ctrlKey || evt.data.$.metaKey, + alt = evt.data.$.altKey, + shift = evt.data.$.shiftKey, + key = String.fromCharCode( evt.data.$.keyCode ), + keyProcessor = accessKeyProcessors[( ctrl ? 'CTRL+' : '' ) + ( alt ? 'ALT+' : '') + ( shift ? 'SHIFT+' : '' ) + key]; + + if ( !keyProcessor || !keyProcessor.length ) + return; + + keyProcessor = keyProcessor[keyProcessor.length - 1]; + keyProcessor.keydown && keyProcessor.keydown.call( keyProcessor.uiElement, keyProcessor.dialog, keyProcessor.key ); + evt.data.preventDefault(); + }; + + var accessKeyUpHandler = function( evt ) + { + var ctrl = evt.data.$.ctrlKey || evt.data.$.metaKey, + alt = evt.data.$.altKey, + shift = evt.data.$.shiftKey, + key = String.fromCharCode( evt.data.$.keyCode ), + keyProcessor = accessKeyProcessors[( ctrl ? 'CTRL+' : '' ) + ( alt ? 'ALT+' : '') + ( shift ? 'SHIFT+' : '' ) + key]; + + if ( !keyProcessor || !keyProcessor.length ) + return; + + keyProcessor = keyProcessor[keyProcessor.length - 1]; + if ( keyProcessor.keyup ) + { + keyProcessor.keyup.call( keyProcessor.uiElement, keyProcessor.dialog, keyProcessor.key ); + evt.data.preventDefault(); + } + }; + + var registerAccessKey = function( uiElement, dialog, key, downFunc, upFunc ) + { + var procList = accessKeyProcessors[key] || ( accessKeyProcessors[key] = [] ); + procList.push( { + uiElement : uiElement, + dialog : dialog, + key : key, + keyup : upFunc || uiElement.accessKeyUp, + keydown : downFunc || uiElement.accessKeyDown + } ); + }; + + var unregisterAccessKey = function( obj ) + { + for ( var i in accessKeyProcessors ) + { + var list = accessKeyProcessors[i]; + for ( var j = list.length - 1 ; j >= 0 ; j-- ) + { + if ( list[j].dialog == obj || list[j].uiElement == obj ) + list.splice( j, 1 ); + } + if ( list.length === 0 ) + delete accessKeyProcessors[i]; + } + }; + + var tabAccessKeyUp = function( dialog, key ) + { + if ( dialog._.accessKeyMap[key] ) + dialog.selectPage( dialog._.accessKeyMap[key] ); + }; + + var tabAccessKeyDown = function( dialog, key ) + { + }; + + // ESC, ENTER + var preventKeyBubblingKeys = { 27 :1, 13 :1 }; + var preventKeyBubbling = function( e ) + { + if ( e.data.getKeystroke() in preventKeyBubblingKeys ) + e.data.stopPropagation(); + }; + + (function() + { + CKEDITOR.ui.dialog = + { + /** + * The base class of all dialog UI elements. + * @constructor + * @param {CKEDITOR.dialog} dialog Parent dialog object. + * @param {CKEDITOR.dialog.definition.uiElement} elementDefinition Element + * definition. Accepted fields: + *
    + *
  • id (Required) The id of the UI element. See {@link + * CKEDITOR.dialog#getContentElement}
  • + *
  • type (Required) The type of the UI element. The + * value to this field specifies which UI element class will be used to + * generate the final widget.
  • + *
  • title (Optional) The popup tooltip for the UI + * element.
  • + *
  • hidden (Optional) A flag that tells if the element + * should be initially visible.
  • + *
  • className (Optional) Additional CSS class names + * to add to the UI element. Separated by space.
  • + *
  • style (Optional) Additional CSS inline styles + * to add to the UI element. A semicolon (;) is required after the last + * style declaration.
  • + *
  • accessKey (Optional) The alphanumeric access key + * for this element. Access keys are automatically prefixed by CTRL.
  • + *
  • on* (Optional) Any UI element definition field that + * starts with on followed immediately by a capital letter and + * probably more letters is an event handler. Event handlers may be further + * divided into registered event handlers and DOM event handlers. Please + * refer to {@link CKEDITOR.ui.dialog.uiElement#registerEvents} and + * {@link CKEDITOR.ui.dialog.uiElement#eventProcessors} for more + * information.
  • + *
+ * @param {Array} htmlList + * List of HTML code to be added to the dialog's content area. + * @param {Function|String} nodeNameArg + * A function returning a string, or a simple string for the node name for + * the root DOM node. Default is 'div'. + * @param {Function|Object} stylesArg + * A function returning an object, or a simple object for CSS styles applied + * to the DOM node. Default is empty object. + * @param {Function|Object} attributesArg + * A fucntion returning an object, or a simple object for attributes applied + * to the DOM node. Default is empty object. + * @param {Function|String} contentsArg + * A function returning a string, or a simple string for the HTML code inside + * the root DOM node. Default is empty string. + * @example + */ + uiElement : function( dialog, elementDefinition, htmlList, nodeNameArg, stylesArg, attributesArg, contentsArg ) + { + if ( arguments.length < 4 ) + return; + + var nodeName = ( nodeNameArg.call ? nodeNameArg( elementDefinition ) : nodeNameArg ) || 'div', + html = [ '<', nodeName, ' ' ], + styles = ( stylesArg && stylesArg.call ? stylesArg( elementDefinition ) : stylesArg ) || {}, + attributes = ( attributesArg && attributesArg.call ? attributesArg( elementDefinition ) : attributesArg ) || {}, + innerHTML = ( contentsArg && contentsArg.call ? contentsArg.call( this, dialog, elementDefinition ) : contentsArg ) || '', + domId = this.domId = attributes.id || CKEDITOR.tools.getNextId() + '_uiElement', + id = this.id = elementDefinition.id, + i; + + // Set the id, a unique id is required for getElement() to work. + attributes.id = domId; + + // Set the type and definition CSS class names. + var classes = {}; + if ( elementDefinition.type ) + classes[ 'cke_dialog_ui_' + elementDefinition.type ] = 1; + if ( elementDefinition.className ) + classes[ elementDefinition.className ] = 1; + if ( elementDefinition.disabled ) + classes[ 'cke_disabled' ] = 1; + + var attributeClasses = ( attributes['class'] && attributes['class'].split ) ? attributes['class'].split( ' ' ) : []; + for ( i = 0 ; i < attributeClasses.length ; i++ ) + { + if ( attributeClasses[i] ) + classes[ attributeClasses[i] ] = 1; + } + var finalClasses = []; + for ( i in classes ) + finalClasses.push( i ); + attributes['class'] = finalClasses.join( ' ' ); + + // Set the popup tooltop. + if ( elementDefinition.title ) + attributes.title = elementDefinition.title; + + // Write the inline CSS styles. + var styleStr = ( elementDefinition.style || '' ).split( ';' ); + + // Element alignment support. + if ( elementDefinition.align ) + { + var align = elementDefinition.align; + styles[ 'margin-left' ] = align == 'left' ? 0 : 'auto'; + styles[ 'margin-right' ] = align == 'right' ? 0 : 'auto'; + } + + for ( i in styles ) + styleStr.push( i + ':' + styles[i] ); + if ( elementDefinition.hidden ) + styleStr.push( 'display:none' ); + for ( i = styleStr.length - 1 ; i >= 0 ; i-- ) + { + if ( styleStr[i] === '' ) + styleStr.splice( i, 1 ); + } + if ( styleStr.length > 0 ) + attributes.style = ( attributes.style ? ( attributes.style + '; ' ) : '' ) + styleStr.join( '; ' ); + + // Write the attributes. + for ( i in attributes ) + html.push( i + '="' + CKEDITOR.tools.htmlEncode( attributes[i] ) + '" '); + + // Write the content HTML. + html.push( '>', innerHTML, '' ); + + // Add contents to the parent HTML array. + htmlList.push( html.join( '' ) ); + + ( this._ || ( this._ = {} ) ).dialog = dialog; + + // Override isChanged if it is defined in element definition. + if ( typeof( elementDefinition.isChanged ) == 'boolean' ) + this.isChanged = function(){ return elementDefinition.isChanged; }; + if ( typeof( elementDefinition.isChanged ) == 'function' ) + this.isChanged = elementDefinition.isChanged; + + // Overload 'get(set)Value' on definition. + if ( typeof( elementDefinition.setValue ) == 'function' ) + { + this.setValue = CKEDITOR.tools.override( this.setValue, function( org ) + { + return function( val ){ org.call( this, elementDefinition.setValue.call( this, val ) ); }; + } ); + } + + if ( typeof( elementDefinition.getValue ) == 'function' ) + { + this.getValue = CKEDITOR.tools.override( this.getValue, function( org ) + { + return function(){ return elementDefinition.getValue.call( this, org.call( this ) ); }; + } ); + } + + // Add events. + CKEDITOR.event.implementOn( this ); + + this.registerEvents( elementDefinition ); + if ( this.accessKeyUp && this.accessKeyDown && elementDefinition.accessKey ) + registerAccessKey( this, dialog, 'CTRL+' + elementDefinition.accessKey ); + + var me = this; + dialog.on( 'load', function() + { + if ( me.getInputElement() ) + { + me.getInputElement().on( 'focus', function() + { + dialog._.tabBarMode = false; + dialog._.hasFocus = true; + me.fire( 'focus' ); + }, me ); + } + } ); + + // Register the object as a tab focus if it can be included. + if ( this.keyboardFocusable ) + { + this.tabIndex = elementDefinition.tabIndex || 0; + + this.focusIndex = dialog._.focusList.push( this ) - 1; + this.on( 'focus', function() + { + dialog._.currentFocusIndex = me.focusIndex; + } ); + } + + // Completes this object with everything we have in the + // definition. + CKEDITOR.tools.extend( this, elementDefinition ); + }, + + /** + * Horizontal layout box for dialog UI elements, auto-expends to available width of container. + * @constructor + * @extends CKEDITOR.ui.dialog.uiElement + * @param {CKEDITOR.dialog} dialog + * Parent dialog object. + * @param {Array} childObjList + * Array of {@link CKEDITOR.ui.dialog.uiElement} objects inside this + * container. + * @param {Array} childHtmlList + * Array of HTML code that correspond to the HTML output of all the + * objects in childObjList. + * @param {Array} htmlList + * Array of HTML code that this element will output to. + * @param {CKEDITOR.dialog.definition.uiElement} elementDefinition + * The element definition. Accepted fields: + *
    + *
  • widths (Optional) The widths of child cells.
  • + *
  • height (Optional) The height of the layout.
  • + *
  • padding (Optional) The padding width inside child + * cells.
  • + *
  • align (Optional) The alignment of the whole layout + *
  • + *
+ * @example + */ + hbox : function( dialog, childObjList, childHtmlList, htmlList, elementDefinition ) + { + if ( arguments.length < 4 ) + return; + + this._ || ( this._ = {} ); + + var children = this._.children = childObjList, + widths = elementDefinition && elementDefinition.widths || null, + height = elementDefinition && elementDefinition.height || null, + styles = {}, + i; + /** @ignore */ + var innerHTML = function() + { + var html = [ '' ]; + for ( i = 0 ; i < childHtmlList.length ; i++ ) + { + var className = 'cke_dialog_ui_hbox_child', + styles = []; + if ( i === 0 ) + className = 'cke_dialog_ui_hbox_first'; + if ( i == childHtmlList.length - 1 ) + className = 'cke_dialog_ui_hbox_last'; + html.push( ' 0 ) + html.push( 'style="' + styles.join('; ') + '" ' ); + html.push( '>', childHtmlList[i], '' ); + } + html.push( '' ); + return html.join( '' ); + }; + + var attribs = { role : 'presentation' }; + elementDefinition && elementDefinition.align && ( attribs.align = elementDefinition.align ); + + CKEDITOR.ui.dialog.uiElement.call( + this, + dialog, + elementDefinition || { type : 'hbox' }, + htmlList, + 'table', + styles, + attribs, + innerHTML ); + }, + + /** + * Vertical layout box for dialog UI elements. + * @constructor + * @extends CKEDITOR.ui.dialog.hbox + * @param {CKEDITOR.dialog} dialog + * Parent dialog object. + * @param {Array} childObjList + * Array of {@link CKEDITOR.ui.dialog.uiElement} objects inside this + * container. + * @param {Array} childHtmlList + * Array of HTML code that correspond to the HTML output of all the + * objects in childObjList. + * @param {Array} htmlList + * Array of HTML code that this element will output to. + * @param {CKEDITOR.dialog.definition.uiElement} elementDefinition + * The element definition. Accepted fields: + *
    + *
  • width (Optional) The width of the layout.
  • + *
  • heights (Optional) The heights of individual cells. + *
  • + *
  • align (Optional) The alignment of the layout.
  • + *
  • padding (Optional) The padding width inside child + * cells.
  • + *
  • expand (Optional) Whether the layout should expand + * vertically to fill its container.
  • + *
+ * @example + */ + vbox : function( dialog, childObjList, childHtmlList, htmlList, elementDefinition ) + { + if ( arguments.length < 3 ) + return; + + this._ || ( this._ = {} ); + + var children = this._.children = childObjList, + width = elementDefinition && elementDefinition.width || null, + heights = elementDefinition && elementDefinition.heights || null; + /** @ignore */ + var innerHTML = function() + { + var html = [ '' ); + for ( var i = 0 ; i < childHtmlList.length ; i++ ) + { + var styles = []; + html.push( '' ); + } + html.push( '
0 ) + html.push( 'style="', styles.join( '; ' ), '" ' ); + html.push( ' class="cke_dialog_ui_vbox_child">', childHtmlList[i], '
' ); + return html.join( '' ); + }; + CKEDITOR.ui.dialog.uiElement.call( this, dialog, elementDefinition || { type : 'vbox' }, htmlList, 'div', null, { role : 'presentation' }, innerHTML ); + } + }; + })(); + + CKEDITOR.ui.dialog.uiElement.prototype = + { + /** + * Gets the root DOM element of this dialog UI object. + * @returns {CKEDITOR.dom.element} Root DOM element of UI object. + * @example + * uiElement.getElement().hide(); + */ + getElement : function() + { + return CKEDITOR.document.getById( this.domId ); + }, + + /** + * Gets the DOM element that the user inputs values. + * This function is used by setValue(), getValue() and focus(). It should + * be overrided in child classes where the input element isn't the root + * element. + * @returns {CKEDITOR.dom.element} The element where the user input values. + * @example + * var rawValue = textInput.getInputElement().$.value; + */ + getInputElement : function() + { + return this.getElement(); + }, + + /** + * Gets the parent dialog object containing this UI element. + * @returns {CKEDITOR.dialog} Parent dialog object. + * @example + * var dialog = uiElement.getDialog(); + */ + getDialog : function() + { + return this._.dialog; + }, + + /** + * Sets the value of this dialog UI object. + * @param {Object} value The new value. + * @param {Boolean} noChangeEvent Internal commit, to supress 'change' event on this element. + * @returns {CKEDITOR.dialog.uiElement} The current UI element. + * @example + * uiElement.setValue( 'Dingo' ); + */ + setValue : function( value, noChangeEvent ) + { + this.getInputElement().setValue( value ); + !noChangeEvent && this.fire( 'change', { value : value } ); + return this; + }, + + /** + * Gets the current value of this dialog UI object. + * @returns {Object} The current value. + * @example + * var myValue = uiElement.getValue(); + */ + getValue : function() + { + return this.getInputElement().getValue(); + }, + + /** + * Tells whether the UI object's value has changed. + * @returns {Boolean} true if changed, false if not changed. + * @example + * if ( uiElement.isChanged() ) + *   confirm( 'Value changed! Continue?' ); + */ + isChanged : function() + { + // Override in input classes. + return false; + }, + + /** + * Selects the parent tab of this element. Usually called by focus() or overridden focus() methods. + * @returns {CKEDITOR.dialog.uiElement} The current UI element. + * @example + * focus : function() + * { + * this.selectParentTab(); + * // do something else. + * } + */ + selectParentTab : function() + { + var element = this.getInputElement(), + cursor = element, + tabId; + while ( ( cursor = cursor.getParent() ) && cursor.$.className.search( 'cke_dialog_page_contents' ) == -1 ) + { /*jsl:pass*/ } + + // Some widgets don't have parent tabs (e.g. OK and Cancel buttons). + if ( !cursor ) + return this; + + tabId = cursor.getAttribute( 'name' ); + // Avoid duplicate select. + if ( this._.dialog._.currentTabId != tabId ) + this._.dialog.selectPage( tabId ); + return this; + }, + + /** + * Puts the focus to the UI object. Switches tabs if the UI object isn't in the active tab page. + * @returns {CKEDITOR.dialog.uiElement} The current UI element. + * @example + * uiElement.focus(); + */ + focus : function() + { + this.selectParentTab().getInputElement().focus(); + return this; + }, + + /** + * Registers the on* event handlers defined in the element definition. + * The default behavior of this function is: + *
    + *
  1. + * If the on* event is defined in the class's eventProcesors list, + * then the registration is delegated to the corresponding function + * in the eventProcessors list. + *
  2. + *
  3. + * If the on* event is not defined in the eventProcessors list, then + * register the event handler under the corresponding DOM event of + * the UI element's input DOM element (as defined by the return value + * of {@link CKEDITOR.ui.dialog.uiElement#getInputElement}). + *
  4. + *
+ * This function is only called at UI element instantiation, but can + * be overridded in child classes if they require more flexibility. + * @param {CKEDITOR.dialog.definition.uiElement} definition The UI element + * definition. + * @returns {CKEDITOR.dialog.uiElement} The current UI element. + * @example + */ + registerEvents : function( definition ) + { + var regex = /^on([A-Z]\w+)/, + match; + + var registerDomEvent = function( uiElement, dialog, eventName, func ) + { + dialog.on( 'load', function() + { + uiElement.getInputElement().on( eventName, func, uiElement ); + }); + }; + + for ( var i in definition ) + { + if ( !( match = i.match( regex ) ) ) + continue; + if ( this.eventProcessors[i] ) + this.eventProcessors[i].call( this, this._.dialog, definition[i] ); + else + registerDomEvent( this, this._.dialog, match[1].toLowerCase(), definition[i] ); + } + + return this; + }, + + /** + * The event processor list used by + * {@link CKEDITOR.ui.dialog.uiElement#getInputElement} at UI element + * instantiation. The default list defines three on* events: + *
    + *
  1. onLoad - Called when the element's parent dialog opens for the + * first time
  2. + *
  3. onShow - Called whenever the element's parent dialog opens.
  4. + *
  5. onHide - Called whenever the element's parent dialog closes.
  6. + *
+ * @field + * @type Object + * @example + * // This connects the 'click' event in CKEDITOR.ui.dialog.button to onClick + * // handlers in the UI element's definitions. + * CKEDITOR.ui.dialog.button.eventProcessors = CKEDITOR.tools.extend( {}, + *   CKEDITOR.ui.dialog.uiElement.prototype.eventProcessors, + *   { onClick : function( dialog, func ) { this.on( 'click', func ); } }, + *   true ); + */ + eventProcessors : + { + onLoad : function( dialog, func ) + { + dialog.on( 'load', func, this ); + }, + + onShow : function( dialog, func ) + { + dialog.on( 'show', func, this ); + }, + + onHide : function( dialog, func ) + { + dialog.on( 'hide', func, this ); + } + }, + + /** + * The default handler for a UI element's access key down event, which + * tries to put focus to the UI element.
+ * Can be overridded in child classes for more sophisticaed behavior. + * @param {CKEDITOR.dialog} dialog The parent dialog object. + * @param {String} key The key combination pressed. Since access keys + * are defined to always include the CTRL key, its value should always + * include a 'CTRL+' prefix. + * @example + */ + accessKeyDown : function( dialog, key ) + { + this.focus(); + }, + + /** + * The default handler for a UI element's access key up event, which + * does nothing.
+ * Can be overridded in child classes for more sophisticated behavior. + * @param {CKEDITOR.dialog} dialog The parent dialog object. + * @param {String} key The key combination pressed. Since access keys + * are defined to always include the CTRL key, its value should always + * include a 'CTRL+' prefix. + * @example + */ + accessKeyUp : function( dialog, key ) + { + }, + + /** + * Disables a UI element. + * @example + */ + disable : function() + { + var element = this.getElement(), + input = this.getInputElement(); + input.setAttribute( 'disabled', 'true' ); + element.addClass( 'cke_disabled' ); + }, + + /** + * Enables a UI element. + * @example + */ + enable : function() + { + var element = this.getElement(), + input = this.getInputElement(); + input.removeAttribute( 'disabled' ); + element.removeClass( 'cke_disabled' ); + }, + + /** + * Determines whether an UI element is enabled or not. + * @returns {Boolean} Whether the UI element is enabled. + * @example + */ + isEnabled : function() + { + return !this.getElement().hasClass( 'cke_disabled' ); + }, + + /** + * Determines whether an UI element is visible or not. + * @returns {Boolean} Whether the UI element is visible. + * @example + */ + isVisible : function() + { + return this.getInputElement().isVisible(); + }, + + /** + * Determines whether an UI element is focus-able or not. + * Focus-able is defined as being both visible and enabled. + * @returns {Boolean} Whether the UI element can be focused. + * @example + */ + isFocusable : function() + { + if ( !this.isEnabled() || !this.isVisible() ) + return false; + return true; + } + }; + + CKEDITOR.ui.dialog.hbox.prototype = CKEDITOR.tools.extend( new CKEDITOR.ui.dialog.uiElement, + /** + * @lends CKEDITOR.ui.dialog.hbox.prototype + */ + { + /** + * Gets a child UI element inside this container. + * @param {Array|Number} indices An array or a single number to indicate the child's + * position in the container's descendant tree. Omit to get all the children in an array. + * @returns {Array|CKEDITOR.ui.dialog.uiElement} Array of all UI elements in the container + * if no argument given, or the specified UI element if indices is given. + * @example + * var checkbox = hbox.getChild( [0,1] ); + * checkbox.setValue( true ); + */ + getChild : function( indices ) + { + // If no arguments, return a clone of the children array. + if ( arguments.length < 1 ) + return this._.children.concat(); + + // If indices isn't array, make it one. + if ( !indices.splice ) + indices = [ indices ]; + + // Retrieve the child element according to tree position. + if ( indices.length < 2 ) + return this._.children[ indices[0] ]; + else + return ( this._.children[ indices[0] ] && this._.children[ indices[0] ].getChild ) ? + this._.children[ indices[0] ].getChild( indices.slice( 1, indices.length ) ) : + null; + } + }, true ); + + CKEDITOR.ui.dialog.vbox.prototype = new CKEDITOR.ui.dialog.hbox(); + + + + (function() + { + var commonBuilder = { + build : function( dialog, elementDefinition, output ) + { + var children = elementDefinition.children, + child, + childHtmlList = [], + childObjList = []; + for ( var i = 0 ; ( i < children.length && ( child = children[i] ) ) ; i++ ) + { + var childHtml = []; + childHtmlList.push( childHtml ); + childObjList.push( CKEDITOR.dialog._.uiElementBuilders[ child.type ].build( dialog, child, childHtml ) ); + } + return new CKEDITOR.ui.dialog[elementDefinition.type]( dialog, childObjList, childHtmlList, output, elementDefinition ); + } + }; + + CKEDITOR.dialog.addUIElement( 'hbox', commonBuilder ); + CKEDITOR.dialog.addUIElement( 'vbox', commonBuilder ); + })(); + + /** + * Generic dialog command. It opens a specific dialog when executed. + * @constructor + * @augments CKEDITOR.commandDefinition + * @param {string} dialogName The name of the dialog to open when executing + * this command. + * @example + * // Register the "link" command, which opens the "link" dialog. + * editor.addCommand( 'link', new CKEDITOR.dialogCommand( 'link' ) ); + */ + CKEDITOR.dialogCommand = function( dialogName ) + { + this.dialogName = dialogName; + }; + + CKEDITOR.dialogCommand.prototype = + { + /** @ignore */ + exec : function( editor ) + { + editor.openDialog( this.dialogName ); + }, + + // Dialog commands just open a dialog ui, thus require no undo logic, + // undo support should dedicate to specific dialog implementation. + canUndo: false, + + editorFocus : CKEDITOR.env.ie || CKEDITOR.env.webkit + }; + + (function() + { + var notEmptyRegex = /^([a]|[^a])+$/, + integerRegex = /^\d*$/, + numberRegex = /^\d*(?:\.\d+)?$/, + htmlLengthRegex = /^(((\d*(\.\d+))|(\d*))(px|\%)?)?$/, + cssLengthRegex = /^(((\d*(\.\d+))|(\d*))(px|em|ex|in|cm|mm|pt|pc|\%)?)?$/i; + + CKEDITOR.VALIDATE_OR = 1; + CKEDITOR.VALIDATE_AND = 2; + + CKEDITOR.dialog.validate = + { + functions : function() + { + var args = arguments; + return function() + { + /** + * It's important for validate functions to be able to accept the value + * as argument in addition to this.getValue(), so that it is possible to + * combine validate functions together to make more sophisticated + * validators. + */ + var value = this && this.getValue ? this.getValue() : args[ 0 ]; + + var msg = undefined, + relation = CKEDITOR.VALIDATE_AND, + functions = [], i; + + for ( i = 0 ; i < args.length ; i++ ) + { + if ( typeof( args[i] ) == 'function' ) + functions.push( args[i] ); + else + break; + } + + if ( i < args.length && typeof( args[i] ) == 'string' ) + { + msg = args[i]; + i++; + } + + if ( i < args.length && typeof( args[i]) == 'number' ) + relation = args[i]; + + var passed = ( relation == CKEDITOR.VALIDATE_AND ? true : false ); + for ( i = 0 ; i < functions.length ; i++ ) + { + if ( relation == CKEDITOR.VALIDATE_AND ) + passed = passed && functions[i]( value ); + else + passed = passed || functions[i]( value ); + } + + return !passed ? msg : true; + }; + }, + + regex : function( regex, msg ) + { + /* + * Can be greatly shortened by deriving from functions validator if code size + * turns out to be more important than performance. + */ + return function() + { + var value = this && this.getValue ? this.getValue() : arguments[0]; + return !regex.test( value ) ? msg : true; + }; + }, + + notEmpty : function( msg ) + { + return this.regex( notEmptyRegex, msg ); + }, + + integer : function( msg ) + { + return this.regex( integerRegex, msg ); + }, + + 'number' : function( msg ) + { + return this.regex( numberRegex, msg ); + }, + + 'cssLength' : function( msg ) + { + return this.functions( function( val ){ return cssLengthRegex.test( CKEDITOR.tools.trim( val ) ); }, msg ); + }, + + 'htmlLength' : function( msg ) + { + return this.functions( function( val ){ return htmlLengthRegex.test( CKEDITOR.tools.trim( val ) ); }, msg ); + }, + + equals : function( value, msg ) + { + return this.functions( function( val ){ return val == value; }, msg ); + }, + + notEqual : function( value, msg ) + { + return this.functions( function( val ){ return val != value; }, msg ); + } + }; + + CKEDITOR.on( 'instanceDestroyed', function( evt ) + { + // Remove dialog cover on last instance destroy. + if ( CKEDITOR.tools.isEmpty( CKEDITOR.instances ) ) + { + var currentTopDialog; + while ( ( currentTopDialog = CKEDITOR.dialog._.currentTop ) ) + currentTopDialog.hide(); + removeCovers(); + } + + var dialogs = evt.editor._.storedDialogs; + for ( var name in dialogs ) + dialogs[ name ].destroy(); + + }); + + })(); + + // Extend the CKEDITOR.editor class with dialog specific functions. + CKEDITOR.tools.extend( CKEDITOR.editor.prototype, + /** @lends CKEDITOR.editor.prototype */ + { + /** + * Loads and opens a registered dialog. + * @param {String} dialogName The registered name of the dialog. + * @param {Function} callback The function to be invoked after dialog instance created. + * @see CKEDITOR.dialog.add + * @example + * CKEDITOR.instances.editor1.openDialog( 'smiley' ); + * @returns {CKEDITOR.dialog} The dialog object corresponding to the dialog displayed. null if the dialog name is not registered. + */ + openDialog : function( dialogName, callback ) + { + if ( this.mode == 'wysiwyg' && CKEDITOR.env.ie ) + { + var selection = this.getSelection(); + selection && selection.lock(); + } + + var dialogDefinitions = CKEDITOR.dialog._.dialogDefinitions[ dialogName ], + dialogSkin = this.skin.dialog; + + if ( CKEDITOR.dialog._.currentTop === null ) + showCover( this ); + + // If the dialogDefinition is already loaded, open it immediately. + if ( typeof dialogDefinitions == 'function' && dialogSkin._isLoaded ) + { + var storedDialogs = this._.storedDialogs || + ( this._.storedDialogs = {} ); + + var dialog = storedDialogs[ dialogName ] || + ( storedDialogs[ dialogName ] = new CKEDITOR.dialog( this, dialogName ) ); + + callback && callback.call( dialog, dialog ); + dialog.show(); + + return dialog; + } + else if ( dialogDefinitions == 'failed' ) + { + hideCover(); + throw new Error( '[CKEDITOR.dialog.openDialog] Dialog "' + dialogName + '" failed when loading definition.' ); + } + + var me = this; + + function onDialogFileLoaded( success ) + { + var dialogDefinition = CKEDITOR.dialog._.dialogDefinitions[ dialogName ], + skin = me.skin.dialog; + + // Check if both skin part and definition is loaded. + if ( !skin._isLoaded || loadDefinition && typeof success == 'undefined' ) + return; + + // In case of plugin error, mark it as loading failed. + if ( typeof dialogDefinition != 'function' ) + CKEDITOR.dialog._.dialogDefinitions[ dialogName ] = 'failed'; + + me.openDialog( dialogName, callback ); + } + + if ( typeof dialogDefinitions == 'string' ) + { + var loadDefinition = 1; + CKEDITOR.scriptLoader.load( CKEDITOR.getUrl( dialogDefinitions ), onDialogFileLoaded, null, 0, 1 ); + } + + CKEDITOR.skins.load( this, 'dialog', onDialogFileLoaded ); + + return null; + } + }); +})(); + +CKEDITOR.plugins.add( 'dialog', + { + requires : [ 'dialogui' ] + }); + +// Dialog related configurations. + +/** + * The color of the dialog background cover. It should be a valid CSS color + * string. + * @name CKEDITOR.config.dialog_backgroundCoverColor + * @type String + * @default 'white' + * @example + * config.dialog_backgroundCoverColor = 'rgb(255, 254, 253)'; + */ + +/** + * The opacity of the dialog background cover. It should be a number within the + * range [0.0, 1.0]. + * @name CKEDITOR.config.dialog_backgroundCoverOpacity + * @type Number + * @default 0.5 + * @example + * config.dialog_backgroundCoverOpacity = 0.7; + */ + +/** + * If the dialog has more than one tab, put focus into the first tab as soon as dialog is opened. + * @name CKEDITOR.config.dialog_startupFocusTab + * @type Boolean + * @default false + * @example + * config.dialog_startupFocusTab = true; + */ + +/** + * The distance of magnetic borders used in moving and resizing dialogs, + * measured in pixels. + * @name CKEDITOR.config.dialog_magnetDistance + * @type Number + * @default 20 + * @example + * config.dialog_magnetDistance = 30; + */ + +/** + * The guideline to follow when generating the dialog buttons. There are 3 possible options: + *
    + *
  • 'OS' - the buttons will be displayed in the default order of the user's OS;
  • + *
  • 'ltr' - for Left-To-Right order;
  • + *
  • 'rtl' - for Right-To-Left order.
  • + *
+ * @name CKEDITOR.config.dialog_buttonsOrder + * @type String + * @default 'OS' + * @since 3.5 + * @example + * config.dialog_buttonsOrder = 'rtl'; + */ + +/** + * The dialog contents to removed. It's a string composed by dialog name and tab name with a colon between them. + * Separate each pair with semicolon (see example). + * Note: All names are case-sensitive. + * Note: Be cautious when specifying dialog tabs that are mandatory, like "info", dialog functionality might be broken because of this! + * @name CKEDITOR.config.removeDialogTabs + * @type String + * @since 3.5 + * @default '' + * @example + * config.removeDialogTabs = 'flash:advanced;image:Link'; + */ + +/** + * Fired when a dialog definition is about to be used to create a dialog into + * an editor instance. This event makes it possible to customize the definition + * before creating it. + *

Note that this event is called only the first time a specific dialog is + * opened. Successive openings will use the cached dialog, and this event will + * not get fired.

+ * @name CKEDITOR#dialogDefinition + * @event + * @param {CKEDITOR.dialog.definition} data The dialog defination that + * is being loaded. + * @param {CKEDITOR.editor} editor The editor instance that will use the + * dialog. + */ + +/** + * Fired when a tab is going to be selected in a dialog + * @name CKEDITOR.dialog#selectPage + * @event + * @param {String} page The id of the page that it's gonna be selected. + * @param {String} currentPage The id of the current page. + */ + +/** + * Fired when the user tries to dismiss a dialog + * @name CKEDITOR.dialog#cancel + * @event + * @param {Boolean} hide Whether the event should proceed or not. + */ + +/** + * Fired when the user tries to confirm a dialog + * @name CKEDITOR.dialog#ok + * @event + * @param {Boolean} hide Whether the event should proceed or not. + */ + +/** + * Fired when a dialog is shown + * @name CKEDITOR.dialog#show + * @event + */ + +/** + * Fired when a dialog is shown + * @name CKEDITOR.editor#dialogShow + * @event + */ + +/** + * Fired when a dialog is hidden + * @name CKEDITOR.dialog#hide + * @event + */ + +/** + * Fired when a dialog is hidden + * @name CKEDITOR.editor#dialogHide + * @event + */ + +/** + * Fired when a dialog is being resized. The event is fired on + * both the 'CKEDITOR.dialog' object and the dialog instance + * since 3.5.3, previously it's available only in the global object. + * @name CKEDITOR.dialog#resize + * @since 3.5 + * @event + * @param {CKEDITOR.dialog} dialog The dialog being resized (if + * it's fired on the dialog itself, this parameter isn't sent). + * @param {String} skin The skin name. + * @param {Number} width The new width. + * @param {Number} height The new height. + */ diff --git a/htdocs/includes/ckeditor/_source/plugins/dialogadvtab/plugin.js b/htdocs/includes/ckeditor/_source/plugins/dialogadvtab/plugin.js new file mode 100755 index 00000000000..ad111ffc480 --- /dev/null +++ b/htdocs/includes/ckeditor/_source/plugins/dialogadvtab/plugin.js @@ -0,0 +1,207 @@ +/* +Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +(function() +{ + +function setupAdvParams( element ) +{ + var attrName = this.att; + + var value = element && element.hasAttribute( attrName ) && element.getAttribute( attrName ) || ''; + + if ( value !== undefined ) + this.setValue( value ); +} + +function commitAdvParams() +{ + // Dialogs may use different parameters in the commit list, so, by + // definition, we take the first CKEDITOR.dom.element available. + var element; + + for ( var i = 0 ; i < arguments.length ; i++ ) + { + if ( arguments[ i ] instanceof CKEDITOR.dom.element ) + { + element = arguments[ i ]; + break; + } + } + + if ( element ) + { + var attrName = this.att, + value = this.getValue(); + + if ( value ) + element.setAttribute( attrName, value ); + else + element.removeAttribute( attrName, value ); + } +} + +CKEDITOR.plugins.add( 'dialogadvtab', +{ + /** + * + * @param tabConfig + * id, dir, classes, styles + */ + createAdvancedTab : function( editor, tabConfig ) + { + if ( !tabConfig ) + tabConfig = { id:1, dir:1, classes:1, styles:1 }; + + var lang = editor.lang.common; + + var result = + { + id : 'advanced', + label : lang.advancedTab, + title : lang.advancedTab, + elements : + [ + { + type : 'vbox', + padding : 1, + children : [] + } + ] + }; + + var contents = []; + + if ( tabConfig.id || tabConfig.dir ) + { + if ( tabConfig.id ) + { + contents.push( + { + id : 'advId', + att : 'id', + type : 'text', + label : lang.id, + setup : setupAdvParams, + commit : commitAdvParams + }); + } + + if ( tabConfig.dir ) + { + contents.push( + { + id : 'advLangDir', + att : 'dir', + type : 'select', + label : lang.langDir, + 'default' : '', + style : 'width:100%', + items : + [ + [ lang.notSet, '' ], + [ lang.langDirLTR, 'ltr' ], + [ lang.langDirRTL, 'rtl' ] + ], + setup : setupAdvParams, + commit : commitAdvParams + }); + } + + result.elements[ 0 ].children.push( + { + type : 'hbox', + widths : [ '50%', '50%' ], + children : [].concat( contents ) + }); + } + + if ( tabConfig.styles || tabConfig.classes ) + { + contents = []; + + if ( tabConfig.styles ) + { + contents.push( + { + id : 'advStyles', + att : 'style', + type : 'text', + label : lang.styles, + 'default' : '', + + onChange : function(){}, + + getStyle : function( name, defaultValue ) + { + var match = this.getValue().match( new RegExp( name + '\\s*:\\s*([^;]*)', 'i') ); + return match ? match[ 1 ] : defaultValue; + }, + + updateStyle : function( name, value ) + { + var styles = this.getValue(); + + // Remove the current value. + if ( styles ) + { + styles = styles + .replace( new RegExp( '\\s*' + name + '\s*:[^;]*(?:$|;\s*)', 'i' ), '' ) + .replace( /^[;\s]+/, '' ) + .replace( /\s+$/, '' ); + } + + if ( value ) + { + styles && !(/;\s*$/).test( styles ) && ( styles += '; ' ); + styles += name + ': ' + value; + } + + this.setValue( styles, 1 ); + + }, + + setup : setupAdvParams, + + commit : commitAdvParams + + }); + } + + if ( tabConfig.classes ) + { + contents.push( + { + type : 'hbox', + widths : [ '45%', '55%' ], + children : + [ + { + id : 'advCSSClasses', + att : 'class', + type : 'text', + label : lang.cssClasses, + 'default' : '', + setup : setupAdvParams, + commit : commitAdvParams + + } + ] + }); + } + + result.elements[ 0 ].children.push( + { + type : 'hbox', + widths : [ '50%', '50%' ], + children : [].concat( contents ) + }); + } + + return result; + } +}); + +})(); diff --git a/htdocs/includes/ckeditor/_source/plugins/dialogui/plugin.js b/htdocs/includes/ckeditor/_source/plugins/dialogui/plugin.js new file mode 100755 index 00000000000..c68c35bbef8 --- /dev/null +++ b/htdocs/includes/ckeditor/_source/plugins/dialogui/plugin.js @@ -0,0 +1,1541 @@ +/* +Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** @fileoverview The "dialogui" plugin. */ + +CKEDITOR.plugins.add( 'dialogui' ); + +(function() +{ + var initPrivateObject = function( elementDefinition ) + { + this._ || ( this._ = {} ); + this._['default'] = this._.initValue = elementDefinition['default'] || ''; + this._.required = elementDefinition[ 'required' ] || false; + var args = [ this._ ]; + for ( var i = 1 ; i < arguments.length ; i++ ) + args.push( arguments[i] ); + args.push( true ); + CKEDITOR.tools.extend.apply( CKEDITOR.tools, args ); + return this._; + }, + textBuilder = + { + build : function( dialog, elementDefinition, output ) + { + return new CKEDITOR.ui.dialog.textInput( dialog, elementDefinition, output ); + } + }, + commonBuilder = + { + build : function( dialog, elementDefinition, output ) + { + return new CKEDITOR.ui.dialog[elementDefinition.type]( dialog, elementDefinition, output ); + } + }, + containerBuilder = + { + build : function( dialog, elementDefinition, output ) + { + var children = elementDefinition.children, + child, + childHtmlList = [], + childObjList = []; + for ( var i = 0 ; ( i < children.length && ( child = children[i] ) ) ; i++ ) + { + var childHtml = []; + childHtmlList.push( childHtml ); + childObjList.push( CKEDITOR.dialog._.uiElementBuilders[ child.type ].build( dialog, child, childHtml ) ); + } + return new CKEDITOR.ui.dialog[ elementDefinition.type ]( dialog, childObjList, childHtmlList, output, elementDefinition ); + } + }, + commonPrototype = + { + isChanged : function() + { + return this.getValue() != this.getInitValue(); + }, + + reset : function( noChangeEvent ) + { + this.setValue( this.getInitValue(), noChangeEvent ); + }, + + setInitValue : function() + { + this._.initValue = this.getValue(); + }, + + resetInitValue : function() + { + this._.initValue = this._['default']; + }, + + getInitValue : function() + { + return this._.initValue; + } + }, + commonEventProcessors = CKEDITOR.tools.extend( {}, CKEDITOR.ui.dialog.uiElement.prototype.eventProcessors, + { + onChange : function( dialog, func ) + { + if ( !this._.domOnChangeRegistered ) + { + dialog.on( 'load', function() + { + this.getInputElement().on( 'change', function() + { + // Make sure 'onchange' doesn't get fired after dialog closed. (#5719) + if ( !dialog.parts.dialog.isVisible() ) + return; + + this.fire( 'change', { value : this.getValue() } ); + }, this ); + }, this ); + this._.domOnChangeRegistered = true; + } + + this.on( 'change', func ); + } + }, true ), + eventRegex = /^on([A-Z]\w+)/, + cleanInnerDefinition = function( def ) + { + // An inner UI element should not have the parent's type, title or events. + for ( var i in def ) + { + if ( eventRegex.test( i ) || i == 'title' || i == 'type' ) + delete def[i]; + } + return def; + }; + + CKEDITOR.tools.extend( CKEDITOR.ui.dialog, + /** @lends CKEDITOR.ui.dialog */ + { + /** + * Base class for all dialog elements with a textual label on the left. + * @constructor + * @example + * @extends CKEDITOR.ui.dialog.uiElement + * @param {CKEDITOR.dialog} dialog + * Parent dialog object. + * @param {CKEDITOR.dialog.definition.uiElement} elementDefinition + * The element definition. Accepted fields: + *
    + *
  • label (Required) The label string.
  • + *
  • labelLayout (Optional) Put 'horizontal' here if the + * label element is to be layed out horizontally. Otherwise a vertical + * layout will be used.
  • + *
  • widths (Optional) This applies only for horizontal + * layouts - an 2-element array of lengths to specify the widths of the + * label and the content element.
  • + *
+ * @param {Array} htmlList + * List of HTML code to output to. + * @param {Function} contentHtml + * A function returning the HTML code string to be added inside the content + * cell. + */ + labeledElement : function( dialog, elementDefinition, htmlList, contentHtml ) + { + if ( arguments.length < 4 ) + return; + + var _ = initPrivateObject.call( this, elementDefinition ); + _.labelId = CKEDITOR.tools.getNextId() + '_label'; + var children = this._.children = []; + /** @ignore */ + var innerHTML = function() + { + var html = [], + requiredClass = elementDefinition.required ? ' cke_required' : '' ; + if ( elementDefinition.labelLayout != 'horizontal' ) + html.push( '', + '' ); + else + { + var hboxDefinition = { + type : 'hbox', + widths : elementDefinition.widths, + padding : 0, + children : + [ + { + type : 'html', + html : '